You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
534 lines
16 KiB
534 lines
16 KiB
import errno
|
|
import socket
|
|
import unittest
|
|
|
|
dummy_app = object()
|
|
|
|
|
|
class TestWSGIServer(unittest.TestCase):
|
|
def _makeOne(
|
|
self,
|
|
application=dummy_app,
|
|
host="127.0.0.1",
|
|
port=0,
|
|
_dispatcher=None,
|
|
adj=None,
|
|
map=None,
|
|
_start=True,
|
|
_sock=None,
|
|
_server=None,
|
|
):
|
|
from waitress.server import create_server
|
|
|
|
self.inst = create_server(
|
|
application,
|
|
host=host,
|
|
port=port,
|
|
map=map,
|
|
_dispatcher=_dispatcher,
|
|
_start=_start,
|
|
_sock=_sock,
|
|
)
|
|
return self.inst
|
|
|
|
def _makeOneWithMap(
|
|
self, adj=None, _start=True, host="127.0.0.1", port=0, app=dummy_app
|
|
):
|
|
sock = DummySock()
|
|
task_dispatcher = DummyTaskDispatcher()
|
|
map = {}
|
|
return self._makeOne(
|
|
app,
|
|
host=host,
|
|
port=port,
|
|
map=map,
|
|
_sock=sock,
|
|
_dispatcher=task_dispatcher,
|
|
_start=_start,
|
|
)
|
|
|
|
def _makeOneWithMulti(
|
|
self, adj=None, _start=True, app=dummy_app, listen="127.0.0.1:0 127.0.0.1:0"
|
|
):
|
|
sock = DummySock()
|
|
task_dispatcher = DummyTaskDispatcher()
|
|
map = {}
|
|
from waitress.server import create_server
|
|
|
|
self.inst = create_server(
|
|
app,
|
|
listen=listen,
|
|
map=map,
|
|
_dispatcher=task_dispatcher,
|
|
_start=_start,
|
|
_sock=sock,
|
|
)
|
|
return self.inst
|
|
|
|
def _makeWithSockets(
|
|
self,
|
|
application=dummy_app,
|
|
_dispatcher=None,
|
|
map=None,
|
|
_start=True,
|
|
_sock=None,
|
|
_server=None,
|
|
sockets=None,
|
|
):
|
|
from waitress.server import create_server
|
|
|
|
_sockets = []
|
|
if sockets is not None:
|
|
_sockets = sockets
|
|
self.inst = create_server(
|
|
application,
|
|
map=map,
|
|
_dispatcher=_dispatcher,
|
|
_start=_start,
|
|
_sock=_sock,
|
|
sockets=_sockets,
|
|
)
|
|
return self.inst
|
|
|
|
def tearDown(self):
|
|
if self.inst is not None:
|
|
self.inst.close()
|
|
|
|
def test_ctor_app_is_None(self):
|
|
self.inst = None
|
|
self.assertRaises(ValueError, self._makeOneWithMap, app=None)
|
|
|
|
def test_ctor_start_true(self):
|
|
inst = self._makeOneWithMap(_start=True)
|
|
self.assertEqual(inst.accepting, True)
|
|
self.assertEqual(inst.socket.listened, 1024)
|
|
|
|
def test_ctor_makes_dispatcher(self):
|
|
inst = self._makeOne(_start=False, map={})
|
|
self.assertEqual(
|
|
inst.task_dispatcher.__class__.__name__, "ThreadedTaskDispatcher"
|
|
)
|
|
|
|
def test_ctor_start_false(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
self.assertEqual(inst.accepting, False)
|
|
|
|
def test_get_server_name_empty(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
self.assertRaises(ValueError, inst.get_server_name, "")
|
|
|
|
def test_get_server_name_with_ip(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
result = inst.get_server_name("127.0.0.1")
|
|
self.assertTrue(result)
|
|
|
|
def test_get_server_name_with_hostname(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
result = inst.get_server_name("fred.flintstone.com")
|
|
self.assertEqual(result, "fred.flintstone.com")
|
|
|
|
def test_get_server_name_0000(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
result = inst.get_server_name("0.0.0.0")
|
|
self.assertTrue(len(result) != 0)
|
|
|
|
def test_get_server_name_double_colon(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
result = inst.get_server_name("::")
|
|
self.assertTrue(len(result) != 0)
|
|
|
|
def test_get_server_name_ipv6(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
result = inst.get_server_name("2001:DB8::ffff")
|
|
self.assertEqual("[2001:DB8::ffff]", result)
|
|
|
|
def test_get_server_multi(self):
|
|
inst = self._makeOneWithMulti()
|
|
self.assertEqual(inst.__class__.__name__, "MultiSocketServer")
|
|
|
|
def test_run(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
inst.asyncore = DummyAsyncore()
|
|
inst.task_dispatcher = DummyTaskDispatcher()
|
|
inst.run()
|
|
self.assertTrue(inst.task_dispatcher.was_shutdown)
|
|
|
|
def test_run_base_server(self):
|
|
inst = self._makeOneWithMulti(_start=False)
|
|
inst.asyncore = DummyAsyncore()
|
|
inst.task_dispatcher = DummyTaskDispatcher()
|
|
inst.run()
|
|
self.assertTrue(inst.task_dispatcher.was_shutdown)
|
|
|
|
def test_pull_trigger(self):
|
|
inst = self._makeOneWithMap(_start=False)
|
|
inst.trigger.close()
|
|
inst.trigger = DummyTrigger()
|
|
inst.pull_trigger()
|
|
self.assertEqual(inst.trigger.pulled, True)
|
|
|
|
def test_add_task(self):
|
|
task = DummyTask()
|
|
inst = self._makeOneWithMap()
|
|
inst.add_task(task)
|
|
self.assertEqual(inst.task_dispatcher.tasks, [task])
|
|
self.assertFalse(task.serviced)
|
|
|
|
def test_readable_not_accepting(self):
|
|
inst = self._makeOneWithMap()
|
|
inst.accepting = False
|
|
self.assertFalse(inst.readable())
|
|
|
|
def test_readable_maplen_gt_connection_limit(self):
|
|
inst = self._makeOneWithMap()
|
|
inst.accepting = True
|
|
inst.adj = DummyAdj
|
|
inst._map = {"a": 1, "b": 2}
|
|
self.assertFalse(inst.readable())
|
|
|
|
def test_readable_maplen_lt_connection_limit(self):
|
|
inst = self._makeOneWithMap()
|
|
inst.accepting = True
|
|
inst.adj = DummyAdj
|
|
inst._map = {}
|
|
self.assertTrue(inst.readable())
|
|
|
|
def test_readable_maintenance_false(self):
|
|
import time
|
|
|
|
inst = self._makeOneWithMap()
|
|
then = time.time() + 1000
|
|
inst.next_channel_cleanup = then
|
|
L = []
|
|
inst.maintenance = lambda t: L.append(t)
|
|
inst.readable()
|
|
self.assertEqual(L, [])
|
|
self.assertEqual(inst.next_channel_cleanup, then)
|
|
|
|
def test_readable_maintenance_true(self):
|
|
inst = self._makeOneWithMap()
|
|
inst.next_channel_cleanup = 0
|
|
L = []
|
|
inst.maintenance = lambda t: L.append(t)
|
|
inst.readable()
|
|
self.assertEqual(len(L), 1)
|
|
self.assertNotEqual(inst.next_channel_cleanup, 0)
|
|
|
|
def test_writable(self):
|
|
inst = self._makeOneWithMap()
|
|
self.assertFalse(inst.writable())
|
|
|
|
def test_handle_read(self):
|
|
inst = self._makeOneWithMap()
|
|
self.assertEqual(inst.handle_read(), None)
|
|
|
|
def test_handle_connect(self):
|
|
inst = self._makeOneWithMap()
|
|
self.assertEqual(inst.handle_connect(), None)
|
|
|
|
def test_handle_accept_wouldblock_socket_error(self):
|
|
inst = self._makeOneWithMap()
|
|
ewouldblock = socket.error(errno.EWOULDBLOCK)
|
|
inst.socket = DummySock(toraise=ewouldblock)
|
|
inst.handle_accept()
|
|
self.assertEqual(inst.socket.accepted, False)
|
|
|
|
def test_handle_accept_other_socket_error(self):
|
|
inst = self._makeOneWithMap()
|
|
eaborted = socket.error(errno.ECONNABORTED)
|
|
inst.socket = DummySock(toraise=eaborted)
|
|
inst.adj = DummyAdj
|
|
|
|
def foo():
|
|
raise socket.error
|
|
|
|
inst.accept = foo
|
|
inst.logger = DummyLogger()
|
|
inst.handle_accept()
|
|
self.assertEqual(inst.socket.accepted, False)
|
|
self.assertEqual(len(inst.logger.logged), 1)
|
|
|
|
def test_handle_accept_noerror(self):
|
|
inst = self._makeOneWithMap()
|
|
innersock = DummySock()
|
|
inst.socket = DummySock(acceptresult=(innersock, None))
|
|
inst.adj = DummyAdj
|
|
L = []
|
|
inst.channel_class = lambda *arg, **kw: L.append(arg)
|
|
inst.handle_accept()
|
|
self.assertEqual(inst.socket.accepted, True)
|
|
self.assertEqual(innersock.opts, [("level", "optname", "value")])
|
|
self.assertEqual(L, [(inst, innersock, None, inst.adj)])
|
|
|
|
def test_maintenance(self):
|
|
inst = self._makeOneWithMap()
|
|
|
|
class DummyChannel(object):
|
|
requests = []
|
|
|
|
zombie = DummyChannel()
|
|
zombie.last_activity = 0
|
|
zombie.running_tasks = False
|
|
inst.active_channels[100] = zombie
|
|
inst.maintenance(10000)
|
|
self.assertEqual(zombie.will_close, True)
|
|
|
|
def test_backward_compatibility(self):
|
|
from waitress.server import WSGIServer, TcpWSGIServer
|
|
from waitress.adjustments import Adjustments
|
|
|
|
self.assertTrue(WSGIServer is TcpWSGIServer)
|
|
self.inst = WSGIServer(None, _start=False, port=1234)
|
|
# Ensure the adjustment was actually applied.
|
|
self.assertNotEqual(Adjustments.port, 1234)
|
|
self.assertEqual(self.inst.adj.port, 1234)
|
|
|
|
def test_create_with_one_tcp_socket(self):
|
|
from waitress.server import TcpWSGIServer
|
|
|
|
sockets = [socket.socket(socket.AF_INET, socket.SOCK_STREAM)]
|
|
sockets[0].bind(("127.0.0.1", 0))
|
|
inst = self._makeWithSockets(_start=False, sockets=sockets)
|
|
self.assertTrue(isinstance(inst, TcpWSGIServer))
|
|
|
|
def test_create_with_multiple_tcp_sockets(self):
|
|
from waitress.server import MultiSocketServer
|
|
|
|
sockets = [
|
|
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
|
|
socket.socket(socket.AF_INET, socket.SOCK_STREAM),
|
|
]
|
|
sockets[0].bind(("127.0.0.1", 0))
|
|
sockets[1].bind(("127.0.0.1", 0))
|
|
inst = self._makeWithSockets(_start=False, sockets=sockets)
|
|
self.assertTrue(isinstance(inst, MultiSocketServer))
|
|
self.assertEqual(len(inst.effective_listen), 2)
|
|
|
|
def test_create_with_one_socket_should_not_bind_socket(self):
|
|
innersock = DummySock()
|
|
sockets = [DummySock(acceptresult=(innersock, None))]
|
|
sockets[0].bind(("127.0.0.1", 80))
|
|
sockets[0].bind_called = False
|
|
inst = self._makeWithSockets(_start=False, sockets=sockets)
|
|
self.assertEqual(inst.socket.bound, ("127.0.0.1", 80))
|
|
self.assertFalse(inst.socket.bind_called)
|
|
|
|
def test_create_with_one_socket_handle_accept_noerror(self):
|
|
innersock = DummySock()
|
|
sockets = [DummySock(acceptresult=(innersock, None))]
|
|
sockets[0].bind(("127.0.0.1", 80))
|
|
inst = self._makeWithSockets(sockets=sockets)
|
|
L = []
|
|
inst.channel_class = lambda *arg, **kw: L.append(arg)
|
|
inst.adj = DummyAdj
|
|
inst.handle_accept()
|
|
self.assertEqual(sockets[0].accepted, True)
|
|
self.assertEqual(innersock.opts, [("level", "optname", "value")])
|
|
self.assertEqual(L, [(inst, innersock, None, inst.adj)])
|
|
|
|
|
|
if hasattr(socket, "AF_UNIX"):
|
|
|
|
class TestUnixWSGIServer(unittest.TestCase):
|
|
unix_socket = "/tmp/waitress.test.sock"
|
|
|
|
def _makeOne(self, _start=True, _sock=None):
|
|
from waitress.server import create_server
|
|
|
|
self.inst = create_server(
|
|
dummy_app,
|
|
map={},
|
|
_start=_start,
|
|
_sock=_sock,
|
|
_dispatcher=DummyTaskDispatcher(),
|
|
unix_socket=self.unix_socket,
|
|
unix_socket_perms="600",
|
|
)
|
|
return self.inst
|
|
|
|
def _makeWithSockets(
|
|
self,
|
|
application=dummy_app,
|
|
_dispatcher=None,
|
|
map=None,
|
|
_start=True,
|
|
_sock=None,
|
|
_server=None,
|
|
sockets=None,
|
|
):
|
|
from waitress.server import create_server
|
|
|
|
_sockets = []
|
|
if sockets is not None:
|
|
_sockets = sockets
|
|
self.inst = create_server(
|
|
application,
|
|
map=map,
|
|
_dispatcher=_dispatcher,
|
|
_start=_start,
|
|
_sock=_sock,
|
|
sockets=_sockets,
|
|
)
|
|
return self.inst
|
|
|
|
def tearDown(self):
|
|
self.inst.close()
|
|
|
|
def _makeDummy(self, *args, **kwargs):
|
|
sock = DummySock(*args, **kwargs)
|
|
sock.family = socket.AF_UNIX
|
|
return sock
|
|
|
|
def test_unix(self):
|
|
inst = self._makeOne(_start=False)
|
|
self.assertEqual(inst.socket.family, socket.AF_UNIX)
|
|
self.assertEqual(inst.socket.getsockname(), self.unix_socket)
|
|
|
|
def test_handle_accept(self):
|
|
# Working on the assumption that we only have to test the happy path
|
|
# for Unix domain sockets as the other paths should've been covered
|
|
# by inet sockets.
|
|
client = self._makeDummy()
|
|
listen = self._makeDummy(acceptresult=(client, None))
|
|
inst = self._makeOne(_sock=listen)
|
|
self.assertEqual(inst.accepting, True)
|
|
self.assertEqual(inst.socket.listened, 1024)
|
|
L = []
|
|
inst.channel_class = lambda *arg, **kw: L.append(arg)
|
|
inst.handle_accept()
|
|
self.assertEqual(inst.socket.accepted, True)
|
|
self.assertEqual(client.opts, [])
|
|
self.assertEqual(L, [(inst, client, ("localhost", None), inst.adj)])
|
|
|
|
def test_creates_new_sockinfo(self):
|
|
from waitress.server import UnixWSGIServer
|
|
|
|
self.inst = UnixWSGIServer(
|
|
dummy_app, unix_socket=self.unix_socket, unix_socket_perms="600"
|
|
)
|
|
|
|
self.assertEqual(self.inst.sockinfo[0], socket.AF_UNIX)
|
|
|
|
def test_create_with_unix_socket(self):
|
|
from waitress.server import (
|
|
MultiSocketServer,
|
|
BaseWSGIServer,
|
|
TcpWSGIServer,
|
|
UnixWSGIServer,
|
|
)
|
|
|
|
sockets = [
|
|
socket.socket(socket.AF_UNIX, socket.SOCK_STREAM),
|
|
socket.socket(socket.AF_UNIX, socket.SOCK_STREAM),
|
|
]
|
|
inst = self._makeWithSockets(sockets=sockets, _start=False)
|
|
self.assertTrue(isinstance(inst, MultiSocketServer))
|
|
server = list(
|
|
filter(lambda s: isinstance(s, BaseWSGIServer), inst.map.values())
|
|
)
|
|
self.assertTrue(isinstance(server[0], UnixWSGIServer))
|
|
self.assertTrue(isinstance(server[1], UnixWSGIServer))
|
|
|
|
|
|
class DummySock(socket.socket):
|
|
accepted = False
|
|
blocking = False
|
|
family = socket.AF_INET
|
|
type = socket.SOCK_STREAM
|
|
proto = 0
|
|
|
|
def __init__(self, toraise=None, acceptresult=(None, None)):
|
|
self.toraise = toraise
|
|
self.acceptresult = acceptresult
|
|
self.bound = None
|
|
self.opts = []
|
|
self.bind_called = False
|
|
|
|
def bind(self, addr):
|
|
self.bind_called = True
|
|
self.bound = addr
|
|
|
|
def accept(self):
|
|
if self.toraise:
|
|
raise self.toraise
|
|
self.accepted = True
|
|
return self.acceptresult
|
|
|
|
def setblocking(self, x):
|
|
self.blocking = True
|
|
|
|
def fileno(self):
|
|
return 10
|
|
|
|
def getpeername(self):
|
|
return "127.0.0.1"
|
|
|
|
def setsockopt(self, *arg):
|
|
self.opts.append(arg)
|
|
|
|
def getsockopt(self, *arg):
|
|
return 1
|
|
|
|
def listen(self, num):
|
|
self.listened = num
|
|
|
|
def getsockname(self):
|
|
return self.bound
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
|
|
class DummyTaskDispatcher(object):
|
|
def __init__(self):
|
|
self.tasks = []
|
|
|
|
def add_task(self, task):
|
|
self.tasks.append(task)
|
|
|
|
def shutdown(self):
|
|
self.was_shutdown = True
|
|
|
|
|
|
class DummyTask(object):
|
|
serviced = False
|
|
start_response_called = False
|
|
wrote_header = False
|
|
status = "200 OK"
|
|
|
|
def __init__(self):
|
|
self.response_headers = {}
|
|
self.written = ""
|
|
|
|
def service(self): # pragma: no cover
|
|
self.serviced = True
|
|
|
|
|
|
class DummyAdj:
|
|
connection_limit = 1
|
|
log_socket_errors = True
|
|
socket_options = [("level", "optname", "value")]
|
|
cleanup_interval = 900
|
|
channel_timeout = 300
|
|
|
|
|
|
class DummyAsyncore(object):
|
|
def loop(self, timeout=30.0, use_poll=False, map=None, count=None):
|
|
raise SystemExit
|
|
|
|
|
|
class DummyTrigger(object):
|
|
def pull_trigger(self):
|
|
self.pulled = True
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
|
|
class DummyLogger(object):
|
|
def __init__(self):
|
|
self.logged = []
|
|
|
|
def warning(self, msg, **kw):
|
|
self.logged.append(msg)
|