summaryrefslogtreecommitdiffhomepage
path: root/libs/waitress
diff options
context:
space:
mode:
authormorpheus65535 <[email protected]>2018-11-26 23:47:27 -0500
committermorpheus65535 <[email protected]>2018-11-26 23:47:27 -0500
commitbcf56504545c5d5e76fd3815ec8ae68d4e7c5863 (patch)
tree224648ceda121e102df6d631a46bdfdeff6b3338 /libs/waitress
parent3c59b9a17fed93042b63e3d6eae4f7c5657c58f4 (diff)
downloadbazarr-bcf56504545c5d5e76fd3815ec8ae68d4e7c5863.tar.gz
bazarr-bcf56504545c5d5e76fd3815ec8ae68d4e7c5863.zip
Removing unused waitress server library
Diffstat (limited to 'libs/waitress')
-rw-r--r--libs/waitress/__init__.py41
-rw-r--r--libs/waitress/__main__.py2
-rw-r--r--libs/waitress/adjustments.py340
-rw-r--r--libs/waitress/buffers.py298
-rw-r--r--libs/waitress/channel.py386
-rw-r--r--libs/waitress/compat.py140
-rw-r--r--libs/waitress/parser.py313
-rw-r--r--libs/waitress/receiver.py149
-rw-r--r--libs/waitress/runner.py275
-rw-r--r--libs/waitress/server.py357
-rw-r--r--libs/waitress/task.py528
-rw-r--r--libs/waitress/tests/__init__.py2
-rw-r--r--libs/waitress/tests/fixtureapps/__init__.py1
-rw-r--r--libs/waitress/tests/fixtureapps/badcl.py12
-rw-r--r--libs/waitress/tests/fixtureapps/echo.py11
-rw-r--r--libs/waitress/tests/fixtureapps/error.py20
-rw-r--r--libs/waitress/tests/fixtureapps/filewrapper.py70
-rw-r--r--libs/waitress/tests/fixtureapps/getline.py17
-rw-r--r--libs/waitress/tests/fixtureapps/groundhog1.jpgbin45448 -> 0 bytes
-rw-r--r--libs/waitress/tests/fixtureapps/nocl.py24
-rw-r--r--libs/waitress/tests/fixtureapps/runner.py5
-rw-r--r--libs/waitress/tests/fixtureapps/sleepy.py14
-rw-r--r--libs/waitress/tests/fixtureapps/toolarge.py8
-rw-r--r--libs/waitress/tests/fixtureapps/writecb.py14
-rw-r--r--libs/waitress/tests/test_adjustments.py294
-rw-r--r--libs/waitress/tests/test_buffers.py453
-rw-r--r--libs/waitress/tests/test_channel.py727
-rw-r--r--libs/waitress/tests/test_compat.py20
-rw-r--r--libs/waitress/tests/test_functional.py1551
-rw-r--r--libs/waitress/tests/test_init.py47
-rw-r--r--libs/waitress/tests/test_parser.py452
-rw-r--r--libs/waitress/tests/test_receiver.py169
-rw-r--r--libs/waitress/tests/test_regression.py144
-rw-r--r--libs/waitress/tests/test_runner.py205
-rw-r--r--libs/waitress/tests/test_server.py367
-rw-r--r--libs/waitress/tests/test_task.py931
-rw-r--r--libs/waitress/tests/test_trigger.py105
-rw-r--r--libs/waitress/tests/test_utilities.py121
-rw-r--r--libs/waitress/trigger.py198
-rw-r--r--libs/waitress/utilities.py216
40 files changed, 0 insertions, 9027 deletions
diff --git a/libs/waitress/__init__.py b/libs/waitress/__init__.py
deleted file mode 100644
index 775fe3a50..000000000
--- a/libs/waitress/__init__.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from waitress.server import create_server
-import logging
-
-def serve(app, **kw):
- _server = kw.pop('_server', create_server) # test shim
- _quiet = kw.pop('_quiet', False) # test shim
- _profile = kw.pop('_profile', False) # test shim
- if not _quiet: # pragma: no cover
- # idempotent if logging has already been set up
- logging.basicConfig()
- server = _server(app, **kw)
- if not _quiet: # pragma: no cover
- server.print_listen('Serving on http://{}:{}')
- if _profile: # pragma: no cover
- profile('server.run()', globals(), locals(), (), False)
- else:
- server.run()
-
-def serve_paste(app, global_conf, **kw):
- serve(app, **kw)
- return 0
-
-def profile(cmd, globals, locals, sort_order, callers): # pragma: no cover
- # runs a command under the profiler and print profiling output at shutdown
- import os
- import profile
- import pstats
- import tempfile
- fd, fn = tempfile.mkstemp()
- try:
- profile.runctx(cmd, globals, locals, fn)
- stats = pstats.Stats(fn)
- stats.strip_dirs()
- # calls,time,cumulative and cumulative,calls,time are useful
- stats.sort_stats(*(sort_order or ('cumulative', 'calls', 'time')))
- if callers:
- stats.print_callers(.3)
- else:
- stats.print_stats(.3)
- finally:
- os.remove(fn)
diff --git a/libs/waitress/__main__.py b/libs/waitress/__main__.py
deleted file mode 100644
index e484f40a7..000000000
--- a/libs/waitress/__main__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-from waitress.runner import run # pragma nocover
-run() # pragma nocover
diff --git a/libs/waitress/adjustments.py b/libs/waitress/adjustments.py
deleted file mode 100644
index 1a5662198..000000000
--- a/libs/waitress/adjustments.py
+++ /dev/null
@@ -1,340 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Adjustments are tunable parameters.
-"""
-import getopt
-import socket
-
-from waitress.compat import (
- PY2,
- WIN,
- string_types,
- HAS_IPV6,
- )
-
-truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1'))
-
-def asbool(s):
- """ Return the boolean value ``True`` if the case-lowered value of string
- input ``s`` is any of ``t``, ``true``, ``y``, ``on``, or ``1``, otherwise
- return the boolean value ``False``. If ``s`` is the value ``None``,
- return ``False``. If ``s`` is already one of the boolean values ``True``
- or ``False``, return it."""
- if s is None:
- return False
- if isinstance(s, bool):
- return s
- s = str(s).strip()
- return s.lower() in truthy
-
-def asoctal(s):
- """Convert the given octal string to an actual number."""
- return int(s, 8)
-
-def aslist_cronly(value):
- if isinstance(value, string_types):
- value = filter(None, [x.strip() for x in value.splitlines()])
- return list(value)
-
-def aslist(value):
- """ Return a list of strings, separating the input based on newlines
- and, if flatten=True (the default), also split on spaces within
- each line."""
- values = aslist_cronly(value)
- result = []
- for value in values:
- subvalues = value.split()
- result.extend(subvalues)
- return result
-
-def slash_fixed_str(s):
- s = s.strip()
- if s:
- # always have a leading slash, replace any number of leading slashes
- # with a single slash, and strip any trailing slashes
- s = '/' + s.lstrip('/').rstrip('/')
- return s
-
-class _str_marker(str):
- pass
-
-class _int_marker(int):
- pass
-
-class Adjustments(object):
- """This class contains tunable parameters.
- """
-
- _params = (
- ('host', str),
- ('port', int),
- ('ipv4', asbool),
- ('ipv6', asbool),
- ('listen', aslist),
- ('threads', int),
- ('trusted_proxy', str),
- ('url_scheme', str),
- ('url_prefix', slash_fixed_str),
- ('backlog', int),
- ('recv_bytes', int),
- ('send_bytes', int),
- ('outbuf_overflow', int),
- ('inbuf_overflow', int),
- ('connection_limit', int),
- ('cleanup_interval', int),
- ('channel_timeout', int),
- ('log_socket_errors', asbool),
- ('max_request_header_size', int),
- ('max_request_body_size', int),
- ('expose_tracebacks', asbool),
- ('ident', str),
- ('asyncore_loop_timeout', int),
- ('asyncore_use_poll', asbool),
- ('unix_socket', str),
- ('unix_socket_perms', asoctal),
- )
-
- _param_map = dict(_params)
-
- # hostname or IP address to listen on
- host = _str_marker('0.0.0.0')
-
- # TCP port to listen on
- port = _int_marker(8080)
-
- listen = ['{}:{}'.format(host, port)]
-
- # mumber of threads available for tasks
- threads = 4
-
- # Host allowed to overrid ``wsgi.url_scheme`` via header
- trusted_proxy = None
-
- # default ``wsgi.url_scheme`` value
- url_scheme = 'http'
-
- # default ``SCRIPT_NAME`` value, also helps reset ``PATH_INFO``
- # when nonempty
- url_prefix = ''
-
- # server identity (sent in Server: header)
- ident = 'waitress'
-
- # backlog is the value waitress passes to pass to socket.listen() This is
- # the maximum number of incoming TCP connections that will wait in an OS
- # queue for an available channel. From listen(1): "If a connection
- # request arrives when the queue is full, the client may receive an error
- # with an indication of ECONNREFUSED or, if the underlying protocol
- # supports retransmission, the request may be ignored so that a later
- # reattempt at connection succeeds."
- backlog = 1024
-
- # recv_bytes is the argument to pass to socket.recv().
- recv_bytes = 8192
-
- # send_bytes is the number of bytes to send to socket.send(). Multiples
- # of 9000 should avoid partly-filled packets, but don't set this larger
- # than the TCP write buffer size. In Linux, /proc/sys/net/ipv4/tcp_wmem
- # controls the minimum, default, and maximum sizes of TCP write buffers.
- send_bytes = 18000
-
- # A tempfile should be created if the pending output is larger than
- # outbuf_overflow, which is measured in bytes. The default is 1MB. This
- # is conservative.
- outbuf_overflow = 1048576
-
- # A tempfile should be created if the pending input is larger than
- # inbuf_overflow, which is measured in bytes. The default is 512K. This
- # is conservative.
- inbuf_overflow = 524288
-
- # Stop creating new channels if too many are already active (integer).
- # Each channel consumes at least one file descriptor, and, depending on
- # the input and output body sizes, potentially up to three. The default
- # is conservative, but you may need to increase the number of file
- # descriptors available to the Waitress process on most platforms in
- # order to safely change it (see ``ulimit -a`` "open files" setting).
- # Note that this doesn't control the maximum number of TCP connections
- # that can be waiting for processing; the ``backlog`` argument controls
- # that.
- connection_limit = 100
-
- # Minimum seconds between cleaning up inactive channels.
- cleanup_interval = 30
-
- # Maximum seconds to leave an inactive connection open.
- channel_timeout = 120
-
- # Boolean: turn off to not log premature client disconnects.
- log_socket_errors = True
-
- # maximum number of bytes of all request headers combined (256K default)
- max_request_header_size = 262144
-
- # maximum number of bytes in request body (1GB default)
- max_request_body_size = 1073741824
-
- # expose tracebacks of uncaught exceptions
- expose_tracebacks = False
-
- # Path to a Unix domain socket to use.
- unix_socket = None
-
- # Path to a Unix domain socket to use.
- unix_socket_perms = 0o600
-
- # The socket options to set on receiving a connection. It is a list of
- # (level, optname, value) tuples. TCP_NODELAY disables the Nagle
- # algorithm for writes (Waitress already buffers its writes).
- socket_options = [
- (socket.SOL_TCP, socket.TCP_NODELAY, 1),
- ]
-
- # The asyncore.loop timeout value
- asyncore_loop_timeout = 1
-
- # The asyncore.loop flag to use poll() instead of the default select().
- asyncore_use_poll = False
-
- # Enable IPv4 by default
- ipv4 = True
-
- # Enable IPv6 by default
- ipv6 = True
-
- def __init__(self, **kw):
-
- if 'listen' in kw and ('host' in kw or 'port' in kw):
- raise ValueError('host and or port may not be set if listen is set.')
-
- for k, v in kw.items():
- if k not in self._param_map:
- raise ValueError('Unknown adjustment %r' % k)
- setattr(self, k, self._param_map[k](v))
-
- if (not isinstance(self.host, _str_marker) or
- not isinstance(self.port, _int_marker)):
- self.listen = ['{}:{}'.format(self.host, self.port)]
-
- enabled_families = socket.AF_UNSPEC
-
- if not self.ipv4 and not HAS_IPV6: # pragma: no cover
- raise ValueError(
- 'IPv4 is disabled but IPv6 is not available. Cowardly refusing to start.'
- )
-
- if self.ipv4 and not self.ipv6:
- enabled_families = socket.AF_INET
-
- if not self.ipv4 and self.ipv6 and HAS_IPV6:
- enabled_families = socket.AF_INET6
-
- wanted_sockets = []
- hp_pairs = []
- for i in self.listen:
- if ':' in i:
- (host, port) = i.rsplit(":", 1)
-
- # IPv6 we need to make sure that we didn't split on the address
- if ']' in port: # pragma: nocover
- (host, port) = (i, str(self.port))
- else:
- (host, port) = (i, str(self.port))
-
- if WIN and PY2: # pragma: no cover
- try:
- # Try turning the port into an integer
- port = int(port)
- except:
- raise ValueError(
- 'Windows does not support service names instead of port numbers'
- )
-
- try:
- if '[' in host and ']' in host: # pragma: nocover
- host = host.strip('[').rstrip(']')
-
- if host == '*':
- host = None
-
- for s in socket.getaddrinfo(
- host,
- port,
- enabled_families,
- socket.SOCK_STREAM,
- socket.IPPROTO_TCP,
- socket.AI_PASSIVE
- ):
- (family, socktype, proto, _, sockaddr) = s
-
- # It seems that getaddrinfo() may sometimes happily return
- # the same result multiple times, this of course makes
- # bind() very unhappy...
- #
- # Split on %, and drop the zone-index from the host in the
- # sockaddr. Works around a bug in OS X whereby
- # getaddrinfo() returns the same link-local interface with
- # two different zone-indices (which makes no sense what so
- # ever...) yet treats them equally when we attempt to bind().
- if (
- sockaddr[1] == 0 or
- (sockaddr[0].split('%', 1)[0], sockaddr[1]) not in hp_pairs
- ):
- wanted_sockets.append((family, socktype, proto, sockaddr))
- hp_pairs.append((sockaddr[0].split('%', 1)[0], sockaddr[1]))
- except:
- raise ValueError('Invalid host/port specified.')
-
- self.listen = wanted_sockets
-
- @classmethod
- def parse_args(cls, argv):
- """Pre-parse command line arguments for input into __init__. Note that
- this does not cast values into adjustment types, it just creates a
- dictionary suitable for passing into __init__, where __init__ does the
- casting.
- """
- long_opts = ['help', 'call']
- for opt, cast in cls._params:
- opt = opt.replace('_', '-')
- if cast is asbool:
- long_opts.append(opt)
- long_opts.append('no-' + opt)
- else:
- long_opts.append(opt + '=')
-
- kw = {
- 'help': False,
- 'call': False,
- }
-
- opts, args = getopt.getopt(argv, '', long_opts)
- for opt, value in opts:
- param = opt.lstrip('-').replace('-', '_')
-
- if param == 'listen':
- kw['listen'] = '{} {}'.format(kw.get('listen', ''), value)
- continue
-
- if param.startswith('no_'):
- param = param[3:]
- kw[param] = 'false'
- elif param in ('help', 'call'):
- kw[param] = True
- elif cls._param_map[param] is asbool:
- kw[param] = 'true'
- else:
- kw[param] = value
-
- return kw, args
diff --git a/libs/waitress/buffers.py b/libs/waitress/buffers.py
deleted file mode 100644
index cacc09474..000000000
--- a/libs/waitress/buffers.py
+++ /dev/null
@@ -1,298 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001-2004 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Buffers
-"""
-from io import BytesIO
-
-# copy_bytes controls the size of temp. strings for shuffling data around.
-COPY_BYTES = 1 << 18 # 256K
-
-# The maximum number of bytes to buffer in a simple string.
-STRBUF_LIMIT = 8192
-
-class FileBasedBuffer(object):
-
- remain = 0
-
- def __init__(self, file, from_buffer=None):
- self.file = file
- if from_buffer is not None:
- from_file = from_buffer.getfile()
- read_pos = from_file.tell()
- from_file.seek(0)
- while True:
- data = from_file.read(COPY_BYTES)
- if not data:
- break
- file.write(data)
- self.remain = int(file.tell() - read_pos)
- from_file.seek(read_pos)
- file.seek(read_pos)
-
- def __len__(self):
- return self.remain
-
- def __nonzero__(self):
- return True
-
- __bool__ = __nonzero__ # py3
-
- def append(self, s):
- file = self.file
- read_pos = file.tell()
- file.seek(0, 2)
- file.write(s)
- file.seek(read_pos)
- self.remain = self.remain + len(s)
-
- def get(self, numbytes=-1, skip=False):
- file = self.file
- if not skip:
- read_pos = file.tell()
- if numbytes < 0:
- # Read all
- res = file.read()
- else:
- res = file.read(numbytes)
- if skip:
- self.remain -= len(res)
- else:
- file.seek(read_pos)
- return res
-
- def skip(self, numbytes, allow_prune=0):
- if self.remain < numbytes:
- raise ValueError("Can't skip %d bytes in buffer of %d bytes" % (
- numbytes, self.remain)
- )
- self.file.seek(numbytes, 1)
- self.remain = self.remain - numbytes
-
- def newfile(self):
- raise NotImplementedError()
-
- def prune(self):
- file = self.file
- if self.remain == 0:
- read_pos = file.tell()
- file.seek(0, 2)
- sz = file.tell()
- file.seek(read_pos)
- if sz == 0:
- # Nothing to prune.
- return
- nf = self.newfile()
- while True:
- data = file.read(COPY_BYTES)
- if not data:
- break
- nf.write(data)
- self.file = nf
-
- def getfile(self):
- return self.file
-
- def close(self):
- if hasattr(self.file, 'close'):
- self.file.close()
- self.remain = 0
-
-class TempfileBasedBuffer(FileBasedBuffer):
-
- def __init__(self, from_buffer=None):
- FileBasedBuffer.__init__(self, self.newfile(), from_buffer)
-
- def newfile(self):
- from tempfile import TemporaryFile
- return TemporaryFile('w+b')
-
-class BytesIOBasedBuffer(FileBasedBuffer):
-
- def __init__(self, from_buffer=None):
- if from_buffer is not None:
- FileBasedBuffer.__init__(self, BytesIO(), from_buffer)
- else:
- # Shortcut. :-)
- self.file = BytesIO()
-
- def newfile(self):
- return BytesIO()
-
-class ReadOnlyFileBasedBuffer(FileBasedBuffer):
- # used as wsgi.file_wrapper
-
- def __init__(self, file, block_size=32768):
- self.file = file
- self.block_size = block_size # for __iter__
-
- def prepare(self, size=None):
- if hasattr(self.file, 'seek') and hasattr(self.file, 'tell'):
- start_pos = self.file.tell()
- self.file.seek(0, 2)
- end_pos = self.file.tell()
- self.file.seek(start_pos)
- fsize = end_pos - start_pos
- if size is None:
- self.remain = fsize
- else:
- self.remain = min(fsize, size)
- return self.remain
-
- def get(self, numbytes=-1, skip=False):
- # never read more than self.remain (it can be user-specified)
- if numbytes == -1 or numbytes > self.remain:
- numbytes = self.remain
- file = self.file
- if not skip:
- read_pos = file.tell()
- res = file.read(numbytes)
- if skip:
- self.remain -= len(res)
- else:
- file.seek(read_pos)
- return res
-
- def __iter__(self): # called by task if self.filelike has no seek/tell
- return self
-
- def next(self):
- val = self.file.read(self.block_size)
- if not val:
- raise StopIteration
- return val
-
- __next__ = next # py3
-
- def append(self, s):
- raise NotImplementedError
-
-class OverflowableBuffer(object):
- """
- This buffer implementation has four stages:
- - No data
- - Bytes-based buffer
- - BytesIO-based buffer
- - Temporary file storage
- The first two stages are fastest for simple transfers.
- """
-
- overflowed = False
- buf = None
- strbuf = b'' # Bytes-based buffer.
-
- def __init__(self, overflow):
- # overflow is the maximum to be stored in a StringIO buffer.
- self.overflow = overflow
-
- def __len__(self):
- buf = self.buf
- if buf is not None:
- # use buf.__len__ rather than len(buf) FBO of not getting
- # OverflowError on Python 2
- return buf.__len__()
- else:
- return self.strbuf.__len__()
-
- def __nonzero__(self):
- # use self.__len__ rather than len(self) FBO of not getting
- # OverflowError on Python 2
- return self.__len__() > 0
-
- __bool__ = __nonzero__ # py3
-
- def _create_buffer(self):
- strbuf = self.strbuf
- if len(strbuf) >= self.overflow:
- self._set_large_buffer()
- else:
- self._set_small_buffer()
- buf = self.buf
- if strbuf:
- buf.append(self.strbuf)
- self.strbuf = b''
- return buf
-
- def _set_small_buffer(self):
- self.buf = BytesIOBasedBuffer(self.buf)
- self.overflowed = False
-
- def _set_large_buffer(self):
- self.buf = TempfileBasedBuffer(self.buf)
- self.overflowed = True
-
- def append(self, s):
- buf = self.buf
- if buf is None:
- strbuf = self.strbuf
- if len(strbuf) + len(s) < STRBUF_LIMIT:
- self.strbuf = strbuf + s
- return
- buf = self._create_buffer()
- buf.append(s)
- # use buf.__len__ rather than len(buf) FBO of not getting
- # OverflowError on Python 2
- sz = buf.__len__()
- if not self.overflowed:
- if sz >= self.overflow:
- self._set_large_buffer()
-
- def get(self, numbytes=-1, skip=False):
- buf = self.buf
- if buf is None:
- strbuf = self.strbuf
- if not skip:
- return strbuf
- buf = self._create_buffer()
- return buf.get(numbytes, skip)
-
- def skip(self, numbytes, allow_prune=False):
- buf = self.buf
- if buf is None:
- if allow_prune and numbytes == len(self.strbuf):
- # We could slice instead of converting to
- # a buffer, but that would eat up memory in
- # large transfers.
- self.strbuf = b''
- return
- buf = self._create_buffer()
- buf.skip(numbytes, allow_prune)
-
- def prune(self):
- """
- A potentially expensive operation that removes all data
- already retrieved from the buffer.
- """
- buf = self.buf
- if buf is None:
- self.strbuf = b''
- return
- buf.prune()
- if self.overflowed:
- # use buf.__len__ rather than len(buf) FBO of not getting
- # OverflowError on Python 2
- sz = buf.__len__()
- if sz < self.overflow:
- # Revert to a faster buffer.
- self._set_small_buffer()
-
- def getfile(self):
- buf = self.buf
- if buf is None:
- buf = self._create_buffer()
- return buf.getfile()
-
- def close(self):
- buf = self.buf
- if buf is not None:
- buf.close()
diff --git a/libs/waitress/channel.py b/libs/waitress/channel.py
deleted file mode 100644
index ca0251126..000000000
--- a/libs/waitress/channel.py
+++ /dev/null
@@ -1,386 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-import asyncore
-import socket
-import threading
-import time
-import traceback
-
-from waitress.buffers import (
- OverflowableBuffer,
- ReadOnlyFileBasedBuffer,
-)
-
-from waitress.parser import HTTPRequestParser
-
-from waitress.task import (
- ErrorTask,
- WSGITask,
-)
-
-from waitress.utilities import (
- logging_dispatcher,
- InternalServerError,
-)
-
-class HTTPChannel(logging_dispatcher, object):
- """
- Setting self.requests = [somerequest] prevents more requests from being
- received until the out buffers have been flushed.
-
- Setting self.requests = [] allows more requests to be received.
- """
-
- task_class = WSGITask
- error_task_class = ErrorTask
- parser_class = HTTPRequestParser
-
- request = None # A request parser instance
- last_activity = 0 # Time of last activity
- will_close = False # set to True to close the socket.
- close_when_flushed = False # set to True to close the socket when flushed
- requests = () # currently pending requests
- sent_continue = False # used as a latch after sending 100 continue
- force_flush = False # indicates a need to flush the outbuf
-
- #
- # ASYNCHRONOUS METHODS (including __init__)
- #
-
- def __init__(
- self,
- server,
- sock,
- addr,
- adj,
- map=None,
- ):
- self.server = server
- self.adj = adj
- self.outbufs = [OverflowableBuffer(adj.outbuf_overflow)]
- self.creation_time = self.last_activity = time.time()
-
- # task_lock used to push/pop requests
- self.task_lock = threading.Lock()
- # outbuf_lock used to access any outbuf
- self.outbuf_lock = threading.Lock()
-
- asyncore.dispatcher.__init__(self, sock, map=map)
-
- # Don't let asyncore.dispatcher throttle self.addr on us.
- self.addr = addr
-
- def any_outbuf_has_data(self):
- for outbuf in self.outbufs:
- if bool(outbuf):
- return True
- return False
-
- def total_outbufs_len(self):
- # genexpr == more funccalls
- # use b.__len__ rather than len(b) FBO of not getting OverflowError
- # on Python 2
- return sum([b.__len__() for b in self.outbufs])
-
- def writable(self):
- # if there's data in the out buffer or we've been instructed to close
- # the channel (possibly by our server maintenance logic), run
- # handle_write
- return self.any_outbuf_has_data() or self.will_close
-
- def handle_write(self):
- # Precondition: there's data in the out buffer to be sent, or
- # there's a pending will_close request
- if not self.connected:
- # we dont want to close the channel twice
- return
-
- # try to flush any pending output
- if not self.requests:
- # 1. There are no running tasks, so we don't need to try to lock
- # the outbuf before sending
- # 2. The data in the out buffer should be sent as soon as possible
- # because it's either data left over from task output
- # or a 100 Continue line sent within "received".
- flush = self._flush_some
- elif self.force_flush:
- # 1. There's a running task, so we need to try to lock
- # the outbuf before sending
- # 2. This is the last chunk sent by the Nth of M tasks in a
- # sequence on this channel, so flush it regardless of whether
- # it's >= self.adj.send_bytes. We need to do this now, or it
- # won't get done.
- flush = self._flush_some_if_lockable
- self.force_flush = False
- elif (self.total_outbufs_len() >= self.adj.send_bytes):
- # 1. There's a running task, so we need to try to lock
- # the outbuf before sending
- # 2. Only try to send if the data in the out buffer is larger
- # than self.adj_bytes to avoid TCP fragmentation
- flush = self._flush_some_if_lockable
- else:
- # 1. There's not enough data in the out buffer to bother to send
- # right now.
- flush = None
-
- if flush:
- try:
- flush()
- except socket.error:
- if self.adj.log_socket_errors:
- self.logger.exception('Socket error')
- self.will_close = True
- except:
- self.logger.exception('Unexpected exception when flushing')
- self.will_close = True
-
- if self.close_when_flushed and not self.any_outbuf_has_data():
- self.close_when_flushed = False
- self.will_close = True
-
- if self.will_close:
- self.handle_close()
-
- def readable(self):
- # We might want to create a new task. We can only do this if:
- # 1. We're not already about to close the connection.
- # 2. There's no already currently running task(s).
- # 3. There's no data in the output buffer that needs to be sent
- # before we potentially create a new task.
- return not (self.will_close or self.requests or
- self.any_outbuf_has_data())
-
- def handle_read(self):
- try:
- data = self.recv(self.adj.recv_bytes)
- except socket.error:
- if self.adj.log_socket_errors:
- self.logger.exception('Socket error')
- self.handle_close()
- return
- if data:
- self.last_activity = time.time()
- self.received(data)
-
- def received(self, data):
- """
- Receives input asynchronously and assigns one or more requests to the
- channel.
- """
- # Preconditions: there's no task(s) already running
- request = self.request
- requests = []
-
- if not data:
- return False
-
- while data:
- if request is None:
- request = self.parser_class(self.adj)
- n = request.received(data)
- if request.expect_continue and request.headers_finished:
- # guaranteed by parser to be a 1.1 request
- request.expect_continue = False
- if not self.sent_continue:
- # there's no current task, so we don't need to try to
- # lock the outbuf to append to it.
- self.outbufs[-1].append(b'HTTP/1.1 100 Continue\r\n\r\n')
- self.sent_continue = True
- self._flush_some()
- request.completed = False
- if request.completed:
- # The request (with the body) is ready to use.
- self.request = None
- if not request.empty:
- requests.append(request)
- request = None
- else:
- self.request = request
- if n >= len(data):
- break
- data = data[n:]
-
- if requests:
- self.requests = requests
- self.server.add_task(self)
-
- return True
-
- def _flush_some_if_lockable(self):
- # Since our task may be appending to the outbuf, we try to acquire
- # the lock, but we don't block if we can't.
- locked = self.outbuf_lock.acquire(False)
- if locked:
- try:
- self._flush_some()
- finally:
- self.outbuf_lock.release()
-
- def _flush_some(self):
- # Send as much data as possible to our client
-
- sent = 0
- dobreak = False
-
- while True:
- outbuf = self.outbufs[0]
- # use outbuf.__len__ rather than len(outbuf) FBO of not getting
- # OverflowError on Python 2
- outbuflen = outbuf.__len__()
- if outbuflen <= 0:
- # self.outbufs[-1] must always be a writable outbuf
- if len(self.outbufs) > 1:
- toclose = self.outbufs.pop(0)
- try:
- toclose.close()
- except:
- self.logger.exception(
- 'Unexpected error when closing an outbuf')
- continue # pragma: no cover (coverage bug, it is hit)
- else:
- if hasattr(outbuf, 'prune'):
- outbuf.prune()
- dobreak = True
-
- while outbuflen > 0:
- chunk = outbuf.get(self.adj.send_bytes)
- num_sent = self.send(chunk)
- if num_sent:
- outbuf.skip(num_sent, True)
- outbuflen -= num_sent
- sent += num_sent
- else:
- dobreak = True
- break
-
- if dobreak:
- break
-
- if sent:
- self.last_activity = time.time()
- return True
-
- return False
-
- def handle_close(self):
- for outbuf in self.outbufs:
- try:
- outbuf.close()
- except:
- self.logger.exception(
- 'Unknown exception while trying to close outbuf')
- self.connected = False
- asyncore.dispatcher.close(self)
-
- def add_channel(self, map=None):
- """See asyncore.dispatcher
-
- This hook keeps track of opened channels.
- """
- asyncore.dispatcher.add_channel(self, map)
- self.server.active_channels[self._fileno] = self
-
- def del_channel(self, map=None):
- """See asyncore.dispatcher
-
- This hook keeps track of closed channels.
- """
- fd = self._fileno # next line sets this to None
- asyncore.dispatcher.del_channel(self, map)
- ac = self.server.active_channels
- if fd in ac:
- del ac[fd]
-
- #
- # SYNCHRONOUS METHODS
- #
-
- def write_soon(self, data):
- if data:
- # the async mainloop might be popping data off outbuf; we can
- # block here waiting for it because we're in a task thread
- with self.outbuf_lock:
- if data.__class__ is ReadOnlyFileBasedBuffer:
- # they used wsgi.file_wrapper
- self.outbufs.append(data)
- nextbuf = OverflowableBuffer(self.adj.outbuf_overflow)
- self.outbufs.append(nextbuf)
- else:
- self.outbufs[-1].append(data)
- # XXX We might eventually need to pull the trigger here (to
- # instruct select to stop blocking), but it slows things down so
- # much that I'll hold off for now; "server push" on otherwise
- # unbusy systems may suffer.
- return len(data)
- return 0
-
- def service(self):
- """Execute all pending requests """
- with self.task_lock:
- while self.requests:
- request = self.requests[0]
- if request.error:
- task = self.error_task_class(self, request)
- else:
- task = self.task_class(self, request)
- try:
- task.service()
- except:
- self.logger.exception('Exception when serving %s' %
- task.request.path)
- if not task.wrote_header:
- if self.adj.expose_tracebacks:
- body = traceback.format_exc()
- else:
- body = ('The server encountered an unexpected '
- 'internal server error')
- req_version = request.version
- req_headers = request.headers
- request = self.parser_class(self.adj)
- request.error = InternalServerError(body)
- # copy some original request attributes to fulfill
- # HTTP 1.1 requirements
- request.version = req_version
- try:
- request.headers['CONNECTION'] = req_headers[
- 'CONNECTION']
- except KeyError:
- pass
- task = self.error_task_class(self, request)
- task.service() # must not fail
- else:
- task.close_on_finish = True
- # we cannot allow self.requests to drop to empty til
- # here; otherwise the mainloop gets confused
- if task.close_on_finish:
- self.close_when_flushed = True
- for request in self.requests:
- request.close()
- self.requests = []
- else:
- request = self.requests.pop(0)
- request.close()
-
- self.force_flush = True
- self.server.pull_trigger()
- self.last_activity = time.time()
-
- def cancel(self):
- """ Cancels all pending requests """
- self.force_flush = True
- self.last_activity = time.time()
- self.requests = []
-
- def defer(self):
- pass
diff --git a/libs/waitress/compat.py b/libs/waitress/compat.py
deleted file mode 100644
index 700f7a1e7..000000000
--- a/libs/waitress/compat.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import sys
-import types
-import platform
-import warnings
-
-try:
- import urlparse
-except ImportError: # pragma: no cover
- from urllib import parse as urlparse
-
-# True if we are running on Python 3.
-PY2 = sys.version_info[0] == 2
-PY3 = sys.version_info[0] == 3
-
-# True if we are running on Windows
-WIN = platform.system() == 'Windows'
-
-if PY3: # pragma: no cover
- string_types = str,
- integer_types = int,
- class_types = type,
- text_type = str
- binary_type = bytes
- long = int
-else:
- string_types = basestring,
- integer_types = (int, long)
- class_types = (type, types.ClassType)
- text_type = unicode
- binary_type = str
- long = long
-
-if PY3: # pragma: no cover
- from urllib.parse import unquote_to_bytes
- def unquote_bytes_to_wsgi(bytestring):
- return unquote_to_bytes(bytestring).decode('latin-1')
-else:
- from urlparse import unquote as unquote_to_bytes
- def unquote_bytes_to_wsgi(bytestring):
- return unquote_to_bytes(bytestring)
-
-def text_(s, encoding='latin-1', errors='strict'):
- """ If ``s`` is an instance of ``binary_type``, return
- ``s.decode(encoding, errors)``, otherwise return ``s``"""
- if isinstance(s, binary_type):
- return s.decode(encoding, errors)
- return s # pragma: no cover
-
-if PY3: # pragma: no cover
- def tostr(s):
- if isinstance(s, text_type):
- s = s.encode('latin-1')
- return str(s, 'latin-1', 'strict')
-
- def tobytes(s):
- return bytes(s, 'latin-1')
-else:
- tostr = str
-
- def tobytes(s):
- return s
-
-try:
- from Queue import (
- Queue,
- Empty,
- )
-except ImportError: # pragma: no cover
- from queue import (
- Queue,
- Empty,
- )
-
-if PY3: # pragma: no cover
- import builtins
- exec_ = getattr(builtins, "exec")
-
- def reraise(tp, value, tb=None):
- if value is None:
- value = tp
- if value.__traceback__ is not tb:
- raise value.with_traceback(tb)
- raise value
-
- del builtins
-
-else: # pragma: no cover
- def exec_(code, globs=None, locs=None):
- """Execute code in a namespace."""
- if globs is None:
- frame = sys._getframe(1)
- globs = frame.f_globals
- if locs is None:
- locs = frame.f_locals
- del frame
- elif locs is None:
- locs = globs
- exec("""exec code in globs, locs""")
-
- exec_("""def reraise(tp, value, tb=None):
- raise tp, value, tb
-""")
-
-try:
- from StringIO import StringIO as NativeIO
-except ImportError: # pragma: no cover
- from io import StringIO as NativeIO
-
-try:
- import httplib
-except ImportError: # pragma: no cover
- from http import client as httplib
-
-try:
- MAXINT = sys.maxint
-except AttributeError: # pragma: no cover
- MAXINT = sys.maxsize
-
-
-# Fix for issue reported in https://github.com/Pylons/waitress/issues/138,
-# Python on Windows may not define IPPROTO_IPV6 in socket.
-import socket
-
-HAS_IPV6 = socket.has_ipv6
-
-if hasattr(socket, 'IPPROTO_IPV6') and hasattr(socket, 'IPV6_V6ONLY'):
- IPPROTO_IPV6 = socket.IPPROTO_IPV6
- IPV6_V6ONLY = socket.IPV6_V6ONLY
-else: # pragma: no cover
- if WIN:
- IPPROTO_IPV6 = 41
- IPV6_V6ONLY = 27
- else:
- warnings.warn(
- 'OS does not support required IPv6 socket flags. This is requirement '
- 'for Waitress. Please open an issue at https://github.com/Pylons/waitress. '
- 'IPv6 support has been disabled.',
- RuntimeWarning
- )
- HAS_IPV6 = False
diff --git a/libs/waitress/parser.py b/libs/waitress/parser.py
deleted file mode 100644
index 6d2f3409d..000000000
--- a/libs/waitress/parser.py
+++ /dev/null
@@ -1,313 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""HTTP Request Parser
-
-This server uses asyncore to accept connections and do initial
-processing but threads to do work.
-"""
-import re
-from io import BytesIO
-
-from waitress.compat import (
- tostr,
- urlparse,
- unquote_bytes_to_wsgi,
-)
-
-from waitress.buffers import OverflowableBuffer
-
-from waitress.receiver import (
- FixedStreamReceiver,
- ChunkedReceiver,
-)
-
-from waitress.utilities import (
- find_double_newline,
- RequestEntityTooLarge,
- RequestHeaderFieldsTooLarge,
- BadRequest,
-)
-
-class ParsingError(Exception):
- pass
-
-class HTTPRequestParser(object):
- """A structure that collects the HTTP request.
-
- Once the stream is completed, the instance is passed to
- a server task constructor.
- """
- completed = False # Set once request is completed.
- empty = False # Set if no request was made.
- expect_continue = False # client sent "Expect: 100-continue" header
- headers_finished = False # True when headers have been read
- header_plus = b''
- chunked = False
- content_length = 0
- header_bytes_received = 0
- body_bytes_received = 0
- body_rcv = None
- version = '1.0'
- error = None
- connection_close = False
-
- # Other attributes: first_line, header, headers, command, uri, version,
- # path, query, fragment
-
- def __init__(self, adj):
- """
- adj is an Adjustments object.
- """
- # headers is a mapping containing keys translated to uppercase
- # with dashes turned into underscores.
- self.headers = {}
- self.adj = adj
-
- def received(self, data):
- """
- Receives the HTTP stream for one request. Returns the number of
- bytes consumed. Sets the completed flag once both the header and the
- body have been received.
- """
- if self.completed:
- return 0 # Can't consume any more.
- datalen = len(data)
- br = self.body_rcv
- if br is None:
- # In header.
- s = self.header_plus + data
- index = find_double_newline(s)
- if index >= 0:
- # Header finished.
- header_plus = s[:index]
- consumed = len(data) - (len(s) - index)
- # Remove preceeding blank lines.
- header_plus = header_plus.lstrip()
- if not header_plus:
- self.empty = True
- self.completed = True
- else:
- try:
- self.parse_header(header_plus)
- except ParsingError as e:
- self.error = BadRequest(e.args[0])
- self.completed = True
- else:
- if self.body_rcv is None:
- # no content-length header and not a t-e: chunked
- # request
- self.completed = True
- if self.content_length > 0:
- max_body = self.adj.max_request_body_size
- # we won't accept this request if the content-length
- # is too large
- if self.content_length >= max_body:
- self.error = RequestEntityTooLarge(
- 'exceeds max_body of %s' % max_body)
- self.completed = True
- self.headers_finished = True
- return consumed
- else:
- # Header not finished yet.
- self.header_bytes_received += datalen
- max_header = self.adj.max_request_header_size
- if self.header_bytes_received >= max_header:
- # malformed header, we need to construct some request
- # on our own. we disregard the incoming(?) requests HTTP
- # version and just use 1.0. IOW someone just sent garbage
- # over the wire
- self.parse_header(b'GET / HTTP/1.0\n')
- self.error = RequestHeaderFieldsTooLarge(
- 'exceeds max_header of %s' % max_header)
- self.completed = True
- self.header_plus = s
- return datalen
- else:
- # In body.
- consumed = br.received(data)
- self.body_bytes_received += consumed
- max_body = self.adj.max_request_body_size
- if self.body_bytes_received >= max_body:
- # this will only be raised during t-e: chunked requests
- self.error = RequestEntityTooLarge(
- 'exceeds max_body of %s' % max_body)
- self.completed = True
- elif br.error:
- # garbage in chunked encoding input probably
- self.error = br.error
- self.completed = True
- elif br.completed:
- # The request (with the body) is ready to use.
- self.completed = True
- if self.chunked:
- # We've converted the chunked transfer encoding request
- # body into a normal request body, so we know its content
- # length; set the header here. We already popped the
- # TRANSFER_ENCODING header in parse_header, so this will
- # appear to the client to be an entirely non-chunked HTTP
- # request with a valid content-length.
- self.headers['CONTENT_LENGTH'] = str(br.__len__())
- return consumed
-
- def parse_header(self, header_plus):
- """
- Parses the header_plus block of text (the headers plus the
- first line of the request).
- """
- index = header_plus.find(b'\n')
- if index >= 0:
- first_line = header_plus[:index].rstrip()
- header = header_plus[index + 1:]
- else:
- first_line = header_plus.rstrip()
- header = b''
-
- self.first_line = first_line # for testing
-
- lines = get_header_lines(header)
-
- headers = self.headers
- for line in lines:
- index = line.find(b':')
- if index > 0:
- key = line[:index]
- if b'_' in key:
- continue
- value = line[index + 1:].strip()
- key1 = tostr(key.upper().replace(b'-', b'_'))
- # If a header already exists, we append subsequent values
- # seperated by a comma. Applications already need to handle
- # the comma seperated values, as HTTP front ends might do
- # the concatenation for you (behavior specified in RFC2616).
- try:
- headers[key1] += tostr(b', ' + value)
- except KeyError:
- headers[key1] = tostr(value)
- # else there's garbage in the headers?
-
- # command, uri, version will be bytes
- command, uri, version = crack_first_line(first_line)
- version = tostr(version)
- command = tostr(command)
- self.command = command
- self.version = version
- (self.proxy_scheme,
- self.proxy_netloc,
- self.path,
- self.query, self.fragment) = split_uri(uri)
- self.url_scheme = self.adj.url_scheme
- connection = headers.get('CONNECTION', '')
-
- if version == '1.0':
- if connection.lower() != 'keep-alive':
- self.connection_close = True
-
- if version == '1.1':
- # since the server buffers data from chunked transfers and clients
- # never need to deal with chunked requests, downstream clients
- # should not see the HTTP_TRANSFER_ENCODING header; we pop it
- # here
- te = headers.pop('TRANSFER_ENCODING', '')
- if te.lower() == 'chunked':
- self.chunked = True
- buf = OverflowableBuffer(self.adj.inbuf_overflow)
- self.body_rcv = ChunkedReceiver(buf)
- expect = headers.get('EXPECT', '').lower()
- self.expect_continue = expect == '100-continue'
- if connection.lower() == 'close':
- self.connection_close = True
-
- if not self.chunked:
- try:
- cl = int(headers.get('CONTENT_LENGTH', 0))
- except ValueError:
- cl = 0
- self.content_length = cl
- if cl > 0:
- buf = OverflowableBuffer(self.adj.inbuf_overflow)
- self.body_rcv = FixedStreamReceiver(cl, buf)
-
- def get_body_stream(self):
- body_rcv = self.body_rcv
- if body_rcv is not None:
- return body_rcv.getfile()
- else:
- return BytesIO()
-
- def close(self):
- body_rcv = self.body_rcv
- if body_rcv is not None:
- body_rcv.getbuf().close()
-
-def split_uri(uri):
- # urlsplit handles byte input by returning bytes on py3, so
- # scheme, netloc, path, query, and fragment are bytes
- try:
- scheme, netloc, path, query, fragment = urlparse.urlsplit(uri)
- except UnicodeError:
- raise ParsingError('Bad URI')
- return (
- tostr(scheme),
- tostr(netloc),
- unquote_bytes_to_wsgi(path),
- tostr(query),
- tostr(fragment),
- )
-
-def get_header_lines(header):
- """
- Splits the header into lines, putting multi-line headers together.
- """
- r = []
- lines = header.split(b'\n')
- for line in lines:
- if line.startswith((b' ', b'\t')):
- if not r:
- # http://corte.si/posts/code/pathod/pythonservers/index.html
- raise ParsingError('Malformed header line "%s"' % tostr(line))
- r[-1] += line
- else:
- r.append(line)
- return r
-
-first_line_re = re.compile(
- b'([^ ]+) '
- b'((?:[^ :?#]+://[^ ?#/]*(?:[0-9]{1,5})?)?[^ ]+)'
- b'(( HTTP/([0-9.]+))$|$)'
-)
-
-def crack_first_line(line):
- m = first_line_re.match(line)
- if m is not None and m.end() == len(line):
- if m.group(3):
- version = m.group(5)
- else:
- version = None
- method = m.group(1)
-
- # the request methods that are currently defined are all uppercase:
- # https://www.iana.org/assignments/http-methods/http-methods.xhtml and
- # the request method is case sensitive according to
- # https://tools.ietf.org/html/rfc7231#section-4.1
-
- # By disallowing anything but uppercase methods we save poor
- # unsuspecting souls from sending lowercase HTTP methods to waitress
- # and having the request complete, while servers like nginx drop the
- # request onto the floor.
- if method != method.upper():
- raise ParsingError('Malformed HTTP method "%s"' % tostr(method))
- uri = m.group(2)
- return method, uri, version
- else:
- return b'', b'', b''
diff --git a/libs/waitress/receiver.py b/libs/waitress/receiver.py
deleted file mode 100644
index 594ae971d..000000000
--- a/libs/waitress/receiver.py
+++ /dev/null
@@ -1,149 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Data Chunk Receiver
-"""
-
-from waitress.utilities import find_double_newline
-
-from waitress.utilities import BadRequest
-
-class FixedStreamReceiver(object):
-
- # See IStreamConsumer
- completed = False
- error = None
-
- def __init__(self, cl, buf):
- self.remain = cl
- self.buf = buf
-
- def __len__(self):
- return self.buf.__len__()
-
- def received(self, data):
- 'See IStreamConsumer'
- rm = self.remain
- if rm < 1:
- self.completed = True # Avoid any chance of spinning
- return 0
- datalen = len(data)
- if rm <= datalen:
- self.buf.append(data[:rm])
- self.remain = 0
- self.completed = True
- return rm
- else:
- self.buf.append(data)
- self.remain -= datalen
- return datalen
-
- def getfile(self):
- return self.buf.getfile()
-
- def getbuf(self):
- return self.buf
-
-class ChunkedReceiver(object):
-
- chunk_remainder = 0
- control_line = b''
- all_chunks_received = False
- trailer = b''
- completed = False
- error = None
-
- # max_control_line = 1024
- # max_trailer = 65536
-
- def __init__(self, buf):
- self.buf = buf
-
- def __len__(self):
- return self.buf.__len__()
-
- def received(self, s):
- # Returns the number of bytes consumed.
- if self.completed:
- return 0
- orig_size = len(s)
- while s:
- rm = self.chunk_remainder
- if rm > 0:
- # Receive the remainder of a chunk.
- to_write = s[:rm]
- self.buf.append(to_write)
- written = len(to_write)
- s = s[written:]
- self.chunk_remainder -= written
- elif not self.all_chunks_received:
- # Receive a control line.
- s = self.control_line + s
- pos = s.find(b'\n')
- if pos < 0:
- # Control line not finished.
- self.control_line = s
- s = ''
- else:
- # Control line finished.
- line = s[:pos]
- s = s[pos + 1:]
- self.control_line = b''
- line = line.strip()
- if line:
- # Begin a new chunk.
- semi = line.find(b';')
- if semi >= 0:
- # discard extension info.
- line = line[:semi]
- try:
- sz = int(line.strip(), 16) # hexadecimal
- except ValueError: # garbage in input
- self.error = BadRequest(
- 'garbage in chunked encoding input')
- sz = 0
- if sz > 0:
- # Start a new chunk.
- self.chunk_remainder = sz
- else:
- # Finished chunks.
- self.all_chunks_received = True
- # else expect a control line.
- else:
- # Receive the trailer.
- trailer = self.trailer + s
- if trailer.startswith(b'\r\n'):
- # No trailer.
- self.completed = True
- return orig_size - (len(trailer) - 2)
- elif trailer.startswith(b'\n'):
- # No trailer.
- self.completed = True
- return orig_size - (len(trailer) - 1)
- pos = find_double_newline(trailer)
- if pos < 0:
- # Trailer not finished.
- self.trailer = trailer
- s = b''
- else:
- # Finished the trailer.
- self.completed = True
- self.trailer = trailer[:pos]
- return orig_size - (len(trailer) - pos)
- return orig_size
-
- def getfile(self):
- return self.buf.getfile()
-
- def getbuf(self):
- return self.buf
diff --git a/libs/waitress/runner.py b/libs/waitress/runner.py
deleted file mode 100644
index abdb38e87..000000000
--- a/libs/waitress/runner.py
+++ /dev/null
@@ -1,275 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2013 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Command line runner.
-"""
-
-from __future__ import print_function, unicode_literals
-
-import getopt
-import os
-import os.path
-import re
-import sys
-
-from waitress import serve
-from waitress.adjustments import Adjustments
-
-HELP = """\
-Usage:
-
- {0} [OPTS] MODULE:OBJECT
-
-Standard options:
-
- --help
- Show this information.
-
- --call
- Call the given object to get the WSGI application.
-
- --host=ADDR
- Hostname or IP address on which to listen, default is '0.0.0.0',
- which means "all IP addresses on this host".
-
- Note: May not be used together with --listen
-
- --port=PORT
- TCP port on which to listen, default is '8080'
-
- Note: May not be used together with --listen
-
- --listen=ip:port
- Tell waitress to listen on an ip port combination.
-
- Example:
-
- --listen=127.0.0.1:8080
- --listen=[::1]:8080
- --listen=*:8080
-
- This option may be used multiple times to listen on multipe sockets.
- A wildcard for the hostname is also supported and will bind to both
- IPv4/IPv6 depending on whether they are enabled or disabled.
-
- --[no-]ipv4
- Toggle on/off IPv4 support.
-
- Example:
-
- --no-ipv4
-
- This will disable IPv4 socket support. This affects wildcard matching
- when generating the list of sockets.
-
- --[no-]ipv6
- Toggle on/off IPv6 support.
-
- Example:
-
- --no-ipv6
-
- This will turn on IPv6 socket support. This affects wildcard matching
- when generating a list of sockets.
-
- --unix-socket=PATH
- Path of Unix socket. If a socket path is specified, a Unix domain
- socket is made instead of the usual inet domain socket.
-
- Not available on Windows.
-
- --unix-socket-perms=PERMS
- Octal permissions to use for the Unix domain socket, default is
- '600'.
-
- --url-scheme=STR
- Default wsgi.url_scheme value, default is 'http'.
-
- --url-prefix=STR
- The ``SCRIPT_NAME`` WSGI environment value. Setting this to anything
- except the empty string will cause the WSGI ``SCRIPT_NAME`` value to be
- the value passed minus any trailing slashes you add, and it will cause
- the ``PATH_INFO`` of any request which is prefixed with this value to
- be stripped of the prefix. Default is the empty string.
-
- --ident=STR
- Server identity used in the 'Server' header in responses. Default
- is 'waitress'.
-
-Tuning options:
-
- --threads=INT
- Number of threads used to process application logic, default is 4.
-
- --backlog=INT
- Connection backlog for the server. Default is 1024.
-
- --recv-bytes=INT
- Number of bytes to request when calling socket.recv(). Default is
- 8192.
-
- --send-bytes=INT
- Number of bytes to send to socket.send(). Default is 18000.
- Multiples of 9000 should avoid partly-filled TCP packets.
-
- --outbuf-overflow=INT
- A temporary file should be created if the pending output is larger
- than this. Default is 1048576 (1MB).
-
- --inbuf-overflow=INT
- A temporary file should be created if the pending input is larger
- than this. Default is 524288 (512KB).
-
- --connection-limit=INT
- Stop creating new channelse if too many are already active.
- Default is 100.
-
- --cleanup-interval=INT
- Minimum seconds between cleaning up inactive channels. Default
- is 30. See '--channel-timeout'.
-
- --channel-timeout=INT
- Maximum number of seconds to leave inactive connections open.
- Default is 120. 'Inactive' is defined as 'has recieved no data
- from the client and has sent no data to the client'.
-
- --[no-]log-socket-errors
- Toggle whether premature client disconnect tracepacks ought to be
- logged. On by default.
-
- --max-request-header-size=INT
- Maximum size of all request headers combined. Default is 262144
- (256KB).
-
- --max-request-body-size=INT
- Maximum size of request body. Default is 1073741824 (1GB).
-
- --[no-]expose-tracebacks
- Toggle whether to expose tracebacks of unhandled exceptions to the
- client. Off by default.
-
- --asyncore-loop-timeout=INT
- The timeout value in seconds passed to asyncore.loop(). Default is 1.
-
- --asyncore-use-poll
- The use_poll argument passed to ``asyncore.loop()``. Helps overcome
- open file descriptors limit. Default is False.
-
-"""
-
-RUNNER_PATTERN = re.compile(r"""
- ^
- (?P<module>
- [a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*
- )
- :
- (?P<object>
- [a-z_][a-z0-9_]*(?:\.[a-z_][a-z0-9_]*)*
- )
- $
- """, re.I | re.X)
-
-def match(obj_name):
- matches = RUNNER_PATTERN.match(obj_name)
- if not matches:
- raise ValueError("Malformed application '{0}'".format(obj_name))
- return matches.group('module'), matches.group('object')
-
-def resolve(module_name, object_name):
- """Resolve a named object in a module."""
- # We cast each segments due to an issue that has been found to manifest
- # in Python 2.6.6, but not 2.6.8, and may affect other revisions of Python
- # 2.6 and 2.7, whereby ``__import__`` chokes if the list passed in the
- # ``fromlist`` argument are unicode strings rather than 8-bit strings.
- # The error triggered is "TypeError: Item in ``fromlist '' not a string".
- # My guess is that this was fixed by checking against ``basestring``
- # rather than ``str`` sometime between the release of 2.6.6 and 2.6.8,
- # but I've yet to go over the commits. I know, however, that the NEWS
- # file makes no mention of such a change to the behaviour of
- # ``__import__``.
- segments = [str(segment) for segment in object_name.split('.')]
- obj = __import__(module_name, fromlist=segments[:1])
- for segment in segments:
- obj = getattr(obj, segment)
- return obj
-
-def show_help(stream, name, error=None): # pragma: no cover
- if error is not None:
- print('Error: {0}\n'.format(error), file=stream)
- print(HELP.format(name), file=stream)
-
-def show_exception(stream):
- exc_type, exc_value = sys.exc_info()[:2]
- args = getattr(exc_value, 'args', None)
- print(
- (
- 'There was an exception ({0}) importing your module.\n'
- ).format(
- exc_type.__name__,
- ),
- file=stream
- )
- if args:
- print('It had these arguments: ', file=stream)
- for idx, arg in enumerate(args, start=1):
- print('{0}. {1}\n'.format(idx, arg), file=stream)
- else:
- print('It had no arguments.', file=stream)
-
-def run(argv=sys.argv, _serve=serve):
- """Command line runner."""
- name = os.path.basename(argv[0])
-
- try:
- kw, args = Adjustments.parse_args(argv[1:])
- except getopt.GetoptError as exc:
- show_help(sys.stderr, name, str(exc))
- return 1
-
- if kw['help']:
- show_help(sys.stdout, name)
- return 0
-
- if len(args) != 1:
- show_help(sys.stderr, name, 'Specify one application only')
- return 1
-
- try:
- module, obj_name = match(args[0])
- except ValueError as exc:
- show_help(sys.stderr, name, str(exc))
- show_exception(sys.stderr)
- return 1
-
- # Add the current directory onto sys.path
- sys.path.append(os.getcwd())
-
- # Get the WSGI function.
- try:
- app = resolve(module, obj_name)
- except ImportError:
- show_help(sys.stderr, name, "Bad module '{0}'".format(module))
- show_exception(sys.stderr)
- return 1
- except AttributeError:
- show_help(sys.stderr, name, "Bad object name '{0}'".format(obj_name))
- show_exception(sys.stderr)
- return 1
- if kw['call']:
- app = app()
-
- # These arguments are specific to the runner, not waitress itself.
- del kw['call'], kw['help']
-
- _serve(app, **kw)
- return 0
diff --git a/libs/waitress/server.py b/libs/waitress/server.py
deleted file mode 100644
index 79aa9b75d..000000000
--- a/libs/waitress/server.py
+++ /dev/null
@@ -1,357 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-
-import asyncore
-import os
-import os.path
-import socket
-import time
-
-from waitress import trigger
-from waitress.adjustments import Adjustments
-from waitress.channel import HTTPChannel
-from waitress.task import ThreadedTaskDispatcher
-from waitress.utilities import (
- cleanup_unix_socket,
- logging_dispatcher,
- )
-from waitress.compat import (
- IPPROTO_IPV6,
- IPV6_V6ONLY,
- )
-
-def create_server(application,
- map=None,
- _start=True, # test shim
- _sock=None, # test shim
- _dispatcher=None, # test shim
- **kw # adjustments
- ):
- """
- if __name__ == '__main__':
- server = create_server(app)
- server.run()
- """
- if application is None:
- raise ValueError(
- 'The "app" passed to ``create_server`` was ``None``. You forgot '
- 'to return a WSGI app within your application.'
- )
- adj = Adjustments(**kw)
-
- if map is None: # pragma: nocover
- map = {}
-
- dispatcher = _dispatcher
- if dispatcher is None:
- dispatcher = ThreadedTaskDispatcher()
- dispatcher.set_thread_count(adj.threads)
-
- if adj.unix_socket and hasattr(socket, 'AF_UNIX'):
- sockinfo = (socket.AF_UNIX, socket.SOCK_STREAM, None, None)
- return UnixWSGIServer(
- application,
- map,
- _start,
- _sock,
- dispatcher=dispatcher,
- adj=adj,
- sockinfo=sockinfo)
-
- effective_listen = []
- last_serv = None
- for sockinfo in adj.listen:
- # When TcpWSGIServer is called, it registers itself in the map. This
- # side-effect is all we need it for, so we don't store a reference to
- # or return it to the user.
- last_serv = TcpWSGIServer(
- application,
- map,
- _start,
- _sock,
- dispatcher=dispatcher,
- adj=adj,
- sockinfo=sockinfo)
- effective_listen.append((last_serv.effective_host, last_serv.effective_port))
-
- # We are running a single server, so we can just return the last server,
- # saves us from having to create one more object
- if len(adj.listen) == 1:
- # In this case we have no need to use a MultiSocketServer
- return last_serv
-
- # Return a class that has a utility function to print out the sockets it's
- # listening on, and has a .run() function. All of the TcpWSGIServers
- # registered themselves in the map above.
- return MultiSocketServer(map, adj, effective_listen, dispatcher)
-
-
-# This class is only ever used if we have multiple listen sockets. It allows
-# the serve() API to call .run() which starts the asyncore loop, and catches
-# SystemExit/KeyboardInterrupt so that it can atempt to cleanly shut down.
-class MultiSocketServer(object):
- asyncore = asyncore # test shim
-
- def __init__(self,
- map=None,
- adj=None,
- effective_listen=None,
- dispatcher=None,
- ):
- self.adj = adj
- self.map = map
- self.effective_listen = effective_listen
- self.task_dispatcher = dispatcher
-
- def print_listen(self, format_str): # pragma: nocover
- for l in self.effective_listen:
- l = list(l)
-
- if ':' in l[0]:
- l[0] = '[{}]'.format(l[0])
-
- print(format_str.format(*l))
-
- def run(self):
- try:
- self.asyncore.loop(
- timeout=self.adj.asyncore_loop_timeout,
- map=self.map,
- use_poll=self.adj.asyncore_use_poll,
- )
- except (SystemExit, KeyboardInterrupt):
- self.task_dispatcher.shutdown()
-
-
-class BaseWSGIServer(logging_dispatcher, object):
-
- channel_class = HTTPChannel
- next_channel_cleanup = 0
- socketmod = socket # test shim
- asyncore = asyncore # test shim
-
- def __init__(self,
- application,
- map=None,
- _start=True, # test shim
- _sock=None, # test shim
- dispatcher=None, # dispatcher
- adj=None, # adjustments
- sockinfo=None, # opaque object
- **kw
- ):
- if adj is None:
- adj = Adjustments(**kw)
- if map is None:
- # use a nonglobal socket map by default to hopefully prevent
- # conflicts with apps and libs that use the asyncore global socket
- # map ala https://github.com/Pylons/waitress/issues/63
- map = {}
- if sockinfo is None:
- sockinfo = adj.listen[0]
-
- self.sockinfo = sockinfo
- self.family = sockinfo[0]
- self.socktype = sockinfo[1]
- self.application = application
- self.adj = adj
- self.trigger = trigger.trigger(map)
- if dispatcher is None:
- dispatcher = ThreadedTaskDispatcher()
- dispatcher.set_thread_count(self.adj.threads)
-
- self.task_dispatcher = dispatcher
- self.asyncore.dispatcher.__init__(self, _sock, map=map)
- if _sock is None:
- self.create_socket(self.family, self.socktype)
- if self.family == socket.AF_INET6: # pragma: nocover
- self.socket.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, 1)
-
- self.set_reuse_addr()
- self.bind_server_socket()
- self.effective_host, self.effective_port = self.getsockname()
- self.server_name = self.get_server_name(self.effective_host)
- self.active_channels = {}
- if _start:
- self.accept_connections()
-
- def bind_server_socket(self):
- raise NotImplementedError # pragma: no cover
-
- def get_server_name(self, ip):
- """Given an IP or hostname, try to determine the server name."""
- if ip:
- server_name = str(ip)
- else:
- server_name = str(self.socketmod.gethostname())
-
- # Convert to a host name if necessary.
- for c in server_name:
- if c != '.' and not c.isdigit():
- return server_name
- try:
- if server_name == '0.0.0.0' or server_name == '::':
- return 'localhost'
- server_name = self.socketmod.gethostbyaddr(server_name)[0]
- except socket.error: # pragma: no cover
- pass
- return server_name
-
- def getsockname(self):
- raise NotImplementedError # pragma: no cover
-
- def accept_connections(self):
- self.accepting = True
- self.socket.listen(self.adj.backlog) # Get around asyncore NT limit
-
- def add_task(self, task):
- self.task_dispatcher.add_task(task)
-
- def readable(self):
- now = time.time()
- if now >= self.next_channel_cleanup:
- self.next_channel_cleanup = now + self.adj.cleanup_interval
- self.maintenance(now)
- return (self.accepting and len(self._map) < self.adj.connection_limit)
-
- def writable(self):
- return False
-
- def handle_read(self):
- pass
-
- def handle_connect(self):
- pass
-
- def handle_accept(self):
- try:
- v = self.accept()
- if v is None:
- return
- conn, addr = v
- except socket.error:
- # Linux: On rare occasions we get a bogus socket back from
- # accept. socketmodule.c:makesockaddr complains that the
- # address family is unknown. We don't want the whole server
- # to shut down because of this.
- if self.adj.log_socket_errors:
- self.logger.warning('server accept() threw an exception',
- exc_info=True)
- return
- self.set_socket_options(conn)
- addr = self.fix_addr(addr)
- self.channel_class(self, conn, addr, self.adj, map=self._map)
-
- def run(self):
- try:
- self.asyncore.loop(
- timeout=self.adj.asyncore_loop_timeout,
- map=self._map,
- use_poll=self.adj.asyncore_use_poll,
- )
- except (SystemExit, KeyboardInterrupt):
- self.task_dispatcher.shutdown()
-
- def pull_trigger(self):
- self.trigger.pull_trigger()
-
- def set_socket_options(self, conn):
- pass
-
- def fix_addr(self, addr):
- return addr
-
- def maintenance(self, now):
- """
- Closes channels that have not had any activity in a while.
-
- The timeout is configured through adj.channel_timeout (seconds).
- """
- cutoff = now - self.adj.channel_timeout
- for channel in self.active_channels.values():
- if (not channel.requests) and channel.last_activity < cutoff:
- channel.will_close = True
-
- def print_listen(self, format_str): # pragma: nocover
- print(format_str.format(self.effective_host, self.effective_port))
-
-
-class TcpWSGIServer(BaseWSGIServer):
-
- def bind_server_socket(self):
- (_, _, _, sockaddr) = self.sockinfo
- self.bind(sockaddr)
-
- def getsockname(self):
- try:
- return self.socketmod.getnameinfo(
- self.socket.getsockname(),
- self.socketmod.NI_NUMERICSERV
- )
- except: # pragma: no cover
- # This only happens on Linux because a DNS issue is considered a
- # temporary failure that will raise (even when NI_NAMEREQD is not
- # set). Instead we try again, but this time we just ask for the
- # numerichost and the numericserv (port) and return those. It is
- # better than nothing.
- return self.socketmod.getnameinfo(
- self.socket.getsockname(),
- self.socketmod.NI_NUMERICHOST | self.socketmod.NI_NUMERICSERV
- )
-
- def set_socket_options(self, conn):
- for (level, optname, value) in self.adj.socket_options:
- conn.setsockopt(level, optname, value)
-
-
-if hasattr(socket, 'AF_UNIX'):
-
- class UnixWSGIServer(BaseWSGIServer):
-
- def __init__(self,
- application,
- map=None,
- _start=True, # test shim
- _sock=None, # test shim
- dispatcher=None, # dispatcher
- adj=None, # adjustments
- sockinfo=None, # opaque object
- **kw):
- if sockinfo is None:
- sockinfo = (socket.AF_UNIX, socket.SOCK_STREAM, None, None)
-
- super(UnixWSGIServer, self).__init__(
- application,
- map=map,
- _start=_start,
- _sock=_sock,
- dispatcher=dispatcher,
- adj=adj,
- sockinfo=sockinfo,
- **kw)
-
- def bind_server_socket(self):
- cleanup_unix_socket(self.adj.unix_socket)
- self.bind(self.adj.unix_socket)
- if os.path.exists(self.adj.unix_socket):
- os.chmod(self.adj.unix_socket, self.adj.unix_socket_perms)
-
- def getsockname(self):
- return ('unix', self.socket.getsockname())
-
- def fix_addr(self, addr):
- return ('localhost', None)
-
-# Compatibility alias.
-WSGIServer = TcpWSGIServer
diff --git a/libs/waitress/task.py b/libs/waitress/task.py
deleted file mode 100644
index 4ce410cf1..000000000
--- a/libs/waitress/task.py
+++ /dev/null
@@ -1,528 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-
-import socket
-import sys
-import threading
-import time
-
-from waitress.buffers import ReadOnlyFileBasedBuffer
-
-from waitress.compat import (
- tobytes,
- Queue,
- Empty,
- reraise,
-)
-
-from waitress.utilities import (
- build_http_date,
- logger,
-)
-
-rename_headers = { # or keep them without the HTTP_ prefix added
- 'CONTENT_LENGTH': 'CONTENT_LENGTH',
- 'CONTENT_TYPE': 'CONTENT_TYPE',
-}
-
-hop_by_hop = frozenset((
- 'connection',
- 'keep-alive',
- 'proxy-authenticate',
- 'proxy-authorization',
- 'te',
- 'trailers',
- 'transfer-encoding',
- 'upgrade'
-))
-
-class JustTesting(Exception):
- pass
-
-class ThreadedTaskDispatcher(object):
- """A Task Dispatcher that creates a thread for each task.
- """
- stop_count = 0 # Number of threads that will stop soon.
- logger = logger
-
- def __init__(self):
- self.threads = {} # { thread number -> 1 }
- self.queue = Queue()
- self.thread_mgmt_lock = threading.Lock()
-
- def start_new_thread(self, target, args):
- t = threading.Thread(target=target, name='waitress', args=args)
- t.daemon = True
- t.start()
-
- def handler_thread(self, thread_no):
- threads = self.threads
- try:
- while threads.get(thread_no):
- task = self.queue.get()
- if task is None:
- # Special value: kill this thread.
- break
- try:
- task.service()
- except Exception as e:
- self.logger.exception(
- 'Exception when servicing %r' % task)
- if isinstance(e, JustTesting):
- break
- finally:
- with self.thread_mgmt_lock:
- self.stop_count -= 1
- threads.pop(thread_no, None)
-
- def set_thread_count(self, count):
- with self.thread_mgmt_lock:
- threads = self.threads
- thread_no = 0
- running = len(threads) - self.stop_count
- while running < count:
- # Start threads.
- while thread_no in threads:
- thread_no = thread_no + 1
- threads[thread_no] = 1
- running += 1
- self.start_new_thread(self.handler_thread, (thread_no,))
- thread_no = thread_no + 1
- if running > count:
- # Stop threads.
- to_stop = running - count
- self.stop_count += to_stop
- for n in range(to_stop):
- self.queue.put(None)
- running -= 1
-
- def add_task(self, task):
- try:
- task.defer()
- self.queue.put(task)
- except:
- task.cancel()
- raise
-
- def shutdown(self, cancel_pending=True, timeout=5):
- self.set_thread_count(0)
- # Ensure the threads shut down.
- threads = self.threads
- expiration = time.time() + timeout
- while threads:
- if time.time() >= expiration:
- self.logger.warning(
- "%d thread(s) still running" %
- len(threads))
- break
- time.sleep(0.1)
- if cancel_pending:
- # Cancel remaining tasks.
- try:
- queue = self.queue
- while not queue.empty():
- task = queue.get()
- if task is not None:
- task.cancel()
- except Empty: # pragma: no cover
- pass
- return True
- return False
-
-class Task(object):
- close_on_finish = False
- status = '200 OK'
- wrote_header = False
- start_time = 0
- content_length = None
- content_bytes_written = 0
- logged_write_excess = False
- complete = False
- chunked_response = False
- logger = logger
-
- def __init__(self, channel, request):
- self.channel = channel
- self.request = request
- self.response_headers = []
- version = request.version
- if version not in ('1.0', '1.1'):
- # fall back to a version we support.
- version = '1.0'
- self.version = version
-
- def service(self):
- try:
- try:
- self.start()
- self.execute()
- self.finish()
- except socket.error:
- self.close_on_finish = True
- if self.channel.adj.log_socket_errors:
- raise
- finally:
- pass
-
- def cancel(self):
- self.close_on_finish = True
-
- def defer(self):
- pass
-
- def build_response_header(self):
- version = self.version
- # Figure out whether the connection should be closed.
- connection = self.request.headers.get('CONNECTION', '').lower()
- response_headers = self.response_headers
- content_length_header = None
- date_header = None
- server_header = None
- connection_close_header = None
-
- for i, (headername, headerval) in enumerate(response_headers):
- headername = '-'.join(
- [x.capitalize() for x in headername.split('-')]
- )
- if headername == 'Content-Length':
- content_length_header = headerval
- if headername == 'Date':
- date_header = headerval
- if headername == 'Server':
- server_header = headerval
- if headername == 'Connection':
- connection_close_header = headerval.lower()
- # replace with properly capitalized version
- response_headers[i] = (headername, headerval)
-
- if content_length_header is None and self.content_length is not None:
- content_length_header = str(self.content_length)
- self.response_headers.append(
- ('Content-Length', content_length_header)
- )
-
- def close_on_finish():
- if connection_close_header is None:
- response_headers.append(('Connection', 'close'))
- self.close_on_finish = True
-
- if version == '1.0':
- if connection == 'keep-alive':
- if not content_length_header:
- close_on_finish()
- else:
- response_headers.append(('Connection', 'Keep-Alive'))
- else:
- close_on_finish()
-
- elif version == '1.1':
- if connection == 'close':
- close_on_finish()
-
- if not content_length_header:
- response_headers.append(('Transfer-Encoding', 'chunked'))
- self.chunked_response = True
- if not self.close_on_finish:
- close_on_finish()
-
- # under HTTP 1.1 keep-alive is default, no need to set the header
- else:
- raise AssertionError('neither HTTP/1.0 or HTTP/1.1')
-
- # Set the Server and Date field, if not yet specified. This is needed
- # if the server is used as a proxy.
- ident = self.channel.server.adj.ident
- if not server_header:
- response_headers.append(('Server', ident))
- else:
- response_headers.append(('Via', ident))
- if not date_header:
- response_headers.append(('Date', build_http_date(self.start_time)))
-
- first_line = 'HTTP/%s %s' % (self.version, self.status)
- # NB: sorting headers needs to preserve same-named-header order
- # as per RFC 2616 section 4.2; thus the key=lambda x: x[0] here;
- # rely on stable sort to keep relative position of same-named headers
- next_lines = ['%s: %s' % hv for hv in sorted(
- self.response_headers, key=lambda x: x[0])]
- lines = [first_line] + next_lines
- res = '%s\r\n\r\n' % '\r\n'.join(lines)
- return tobytes(res)
-
- def remove_content_length_header(self):
- for i, (header_name, header_value) in enumerate(self.response_headers):
- if header_name.lower() == 'content-length':
- del self.response_headers[i]
-
- def start(self):
- self.start_time = time.time()
-
- def finish(self):
- if not self.wrote_header:
- self.write(b'')
- if self.chunked_response:
- # not self.write, it will chunk it!
- self.channel.write_soon(b'0\r\n\r\n')
-
- def write(self, data):
- if not self.complete:
- raise RuntimeError('start_response was not called before body '
- 'written')
- channel = self.channel
- if not self.wrote_header:
- rh = self.build_response_header()
- channel.write_soon(rh)
- self.wrote_header = True
- if data:
- towrite = data
- cl = self.content_length
- if self.chunked_response:
- # use chunked encoding response
- towrite = tobytes(hex(len(data))[2:].upper()) + b'\r\n'
- towrite += data + b'\r\n'
- elif cl is not None:
- towrite = data[:cl - self.content_bytes_written]
- self.content_bytes_written += len(towrite)
- if towrite != data and not self.logged_write_excess:
- self.logger.warning(
- 'application-written content exceeded the number of '
- 'bytes specified by Content-Length header (%s)' % cl)
- self.logged_write_excess = True
- if towrite:
- channel.write_soon(towrite)
-
-class ErrorTask(Task):
- """ An error task produces an error response
- """
- complete = True
-
- def execute(self):
- e = self.request.error
- body = '%s\r\n\r\n%s' % (e.reason, e.body)
- tag = '\r\n\r\n(generated by waitress)'
- body = body + tag
- self.status = '%s %s' % (e.code, e.reason)
- cl = len(body)
- self.content_length = cl
- self.response_headers.append(('Content-Length', str(cl)))
- self.response_headers.append(('Content-Type', 'text/plain'))
- if self.version == '1.1':
- connection = self.request.headers.get('CONNECTION', '').lower()
- if connection == 'close':
- self.response_headers.append(('Connection', 'close'))
- # under HTTP 1.1 keep-alive is default, no need to set the header
- else:
- # HTTP 1.0
- self.response_headers.append(('Connection', 'close'))
- self.close_on_finish = True
- self.write(tobytes(body))
-
-class WSGITask(Task):
- """A WSGI task produces a response from a WSGI application.
- """
- environ = None
-
- def execute(self):
- env = self.get_environment()
-
- def start_response(status, headers, exc_info=None):
- if self.complete and not exc_info:
- raise AssertionError("start_response called a second time "
- "without providing exc_info.")
- if exc_info:
- try:
- if self.wrote_header:
- # higher levels will catch and handle raised exception:
- # 1. "service" method in task.py
- # 2. "service" method in channel.py
- # 3. "handler_thread" method in task.py
- reraise(exc_info[0], exc_info[1], exc_info[2])
- else:
- # As per WSGI spec existing headers must be cleared
- self.response_headers = []
- finally:
- exc_info = None
-
- self.complete = True
-
- if not status.__class__ is str:
- raise AssertionError('status %s is not a string' % status)
- if '\n' in status or '\r' in status:
- raise ValueError("carriage return/line "
- "feed character present in status")
-
- self.status = status
-
- # Prepare the headers for output
- for k, v in headers:
- if not k.__class__ is str:
- raise AssertionError(
- 'Header name %r is not a string in %r' % (k, (k, v))
- )
- if not v.__class__ is str:
- raise AssertionError(
- 'Header value %r is not a string in %r' % (v, (k, v))
- )
-
- if '\n' in v or '\r' in v:
- raise ValueError("carriage return/line "
- "feed character present in header value")
- if '\n' in k or '\r' in k:
- raise ValueError("carriage return/line "
- "feed character present in header name")
-
- kl = k.lower()
- if kl == 'content-length':
- self.content_length = int(v)
- elif kl in hop_by_hop:
- raise AssertionError(
- '%s is a "hop-by-hop" header; it cannot be used by '
- 'a WSGI application (see PEP 3333)' % k)
-
- self.response_headers.extend(headers)
-
- # Return a method used to write the response data.
- return self.write
-
- # Call the application to handle the request and write a response
- app_iter = self.channel.server.application(env, start_response)
-
- if app_iter.__class__ is ReadOnlyFileBasedBuffer:
- # NB: do not put this inside the below try: finally: which closes
- # the app_iter; we need to defer closing the underlying file. It's
- # intention that we don't want to call ``close`` here if the
- # app_iter is a ROFBB; the buffer (and therefore the file) will
- # eventually be closed within channel.py's _flush_some or
- # handle_close instead.
- cl = self.content_length
- size = app_iter.prepare(cl)
- if size:
- if cl != size:
- if cl is not None:
- self.remove_content_length_header()
- self.content_length = size
- self.write(b'') # generate headers
- self.channel.write_soon(app_iter)
- return
-
- try:
- first_chunk_len = None
- for chunk in app_iter:
- if first_chunk_len is None:
- first_chunk_len = len(chunk)
- # Set a Content-Length header if one is not supplied.
- # start_response may not have been called until first
- # iteration as per PEP, so we must reinterrogate
- # self.content_length here
- if self.content_length is None:
- app_iter_len = None
- if hasattr(app_iter, '__len__'):
- app_iter_len = len(app_iter)
- if app_iter_len == 1:
- self.content_length = first_chunk_len
- # transmit headers only after first iteration of the iterable
- # that returns a non-empty bytestring (PEP 3333)
- if chunk:
- self.write(chunk)
-
- cl = self.content_length
- if cl is not None:
- if self.content_bytes_written != cl:
- # close the connection so the client isn't sitting around
- # waiting for more data when there are too few bytes
- # to service content-length
- self.close_on_finish = True
- if self.request.command != 'HEAD':
- self.logger.warning(
- 'application returned too few bytes (%s) '
- 'for specified Content-Length (%s) via app_iter' % (
- self.content_bytes_written, cl),
- )
- finally:
- if hasattr(app_iter, 'close'):
- app_iter.close()
-
- def get_environment(self):
- """Returns a WSGI environment."""
- environ = self.environ
- if environ is not None:
- # Return the cached copy.
- return environ
-
- request = self.request
- path = request.path
- channel = self.channel
- server = channel.server
- url_prefix = server.adj.url_prefix
-
- if path.startswith('/'):
- # strip extra slashes at the beginning of a path that starts
- # with any number of slashes
- path = '/' + path.lstrip('/')
-
- if url_prefix:
- # NB: url_prefix is guaranteed by the configuration machinery to
- # be either the empty string or a string that starts with a single
- # slash and ends without any slashes
- if path == url_prefix:
- # if the path is the same as the url prefix, the SCRIPT_NAME
- # should be the url_prefix and PATH_INFO should be empty
- path = ''
- else:
- # if the path starts with the url prefix plus a slash,
- # the SCRIPT_NAME should be the url_prefix and PATH_INFO should
- # the value of path from the slash until its end
- url_prefix_with_trailing_slash = url_prefix + '/'
- if path.startswith(url_prefix_with_trailing_slash):
- path = path[len(url_prefix):]
-
- environ = {}
- environ['REQUEST_METHOD'] = request.command.upper()
- environ['SERVER_PORT'] = str(server.effective_port)
- environ['SERVER_NAME'] = server.server_name
- environ['SERVER_SOFTWARE'] = server.adj.ident
- environ['SERVER_PROTOCOL'] = 'HTTP/%s' % self.version
- environ['SCRIPT_NAME'] = url_prefix
- environ['PATH_INFO'] = path
- environ['QUERY_STRING'] = request.query
- host = environ['REMOTE_ADDR'] = channel.addr[0]
-
- headers = dict(request.headers)
- if host == server.adj.trusted_proxy:
- wsgi_url_scheme = headers.pop('X_FORWARDED_PROTO',
- request.url_scheme)
- else:
- wsgi_url_scheme = request.url_scheme
- if wsgi_url_scheme not in ('http', 'https'):
- raise ValueError('Invalid X_FORWARDED_PROTO value')
- for key, value in headers.items():
- value = value.strip()
- mykey = rename_headers.get(key, None)
- if mykey is None:
- mykey = 'HTTP_%s' % key
- if mykey not in environ:
- environ[mykey] = value
-
- # the following environment variables are required by the WSGI spec
- environ['wsgi.version'] = (1, 0)
- environ['wsgi.url_scheme'] = wsgi_url_scheme
- environ['wsgi.errors'] = sys.stderr # apps should use the logging module
- environ['wsgi.multithread'] = True
- environ['wsgi.multiprocess'] = False
- environ['wsgi.run_once'] = False
- environ['wsgi.input'] = request.get_body_stream()
- environ['wsgi.file_wrapper'] = ReadOnlyFileBasedBuffer
-
- self.environ = environ
- return environ
diff --git a/libs/waitress/tests/__init__.py b/libs/waitress/tests/__init__.py
deleted file mode 100644
index b711d3609..000000000
--- a/libs/waitress/tests/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-#
-# This file is necessary to make this directory a package.
diff --git a/libs/waitress/tests/fixtureapps/__init__.py b/libs/waitress/tests/fixtureapps/__init__.py
deleted file mode 100644
index f215a2b90..000000000
--- a/libs/waitress/tests/fixtureapps/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-# package (for -m)
diff --git a/libs/waitress/tests/fixtureapps/badcl.py b/libs/waitress/tests/fixtureapps/badcl.py
deleted file mode 100644
index 2289a1257..000000000
--- a/libs/waitress/tests/fixtureapps/badcl.py
+++ /dev/null
@@ -1,12 +0,0 @@
-def app(environ, start_response): # pragma: no cover
- body = b'abcdefghi'
- cl = len(body)
- if environ['PATH_INFO'] == '/short_body':
- cl = len(body) + 1
- if environ['PATH_INFO'] == '/long_body':
- cl = len(body) - 1
- start_response(
- '200 OK',
- [('Content-Length', str(cl)), ('Content-Type', 'text/plain')]
- )
- return [body]
diff --git a/libs/waitress/tests/fixtureapps/echo.py b/libs/waitress/tests/fixtureapps/echo.py
deleted file mode 100644
index f5fd5d132..000000000
--- a/libs/waitress/tests/fixtureapps/echo.py
+++ /dev/null
@@ -1,11 +0,0 @@
-def app(environ, start_response): # pragma: no cover
- cl = environ.get('CONTENT_LENGTH', None)
- if cl is not None:
- cl = int(cl)
- body = environ['wsgi.input'].read(cl)
- cl = str(len(body))
- start_response(
- '200 OK',
- [('Content-Length', cl), ('Content-Type', 'text/plain')]
- )
- return [body]
diff --git a/libs/waitress/tests/fixtureapps/error.py b/libs/waitress/tests/fixtureapps/error.py
deleted file mode 100644
index cab8ad6eb..000000000
--- a/libs/waitress/tests/fixtureapps/error.py
+++ /dev/null
@@ -1,20 +0,0 @@
-def app(environ, start_response): # pragma: no cover
- cl = environ.get('CONTENT_LENGTH', None)
- if cl is not None:
- cl = int(cl)
- body = environ['wsgi.input'].read(cl)
- cl = str(len(body))
- if environ['PATH_INFO'] == '/before_start_response':
- raise ValueError('wrong')
- write = start_response(
- '200 OK',
- [('Content-Length', cl), ('Content-Type', 'text/plain')]
- )
- if environ['PATH_INFO'] == '/after_write_cb':
- write('abc')
- if environ['PATH_INFO'] == '/in_generator':
- def foo():
- yield 'abc'
- raise ValueError
- return foo()
- raise ValueError('wrong')
diff --git a/libs/waitress/tests/fixtureapps/filewrapper.py b/libs/waitress/tests/fixtureapps/filewrapper.py
deleted file mode 100644
index be35b0251..000000000
--- a/libs/waitress/tests/fixtureapps/filewrapper.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import os
-
-here = os.path.dirname(os.path.abspath(__file__))
-fn = os.path.join(here, 'groundhog1.jpg')
-
-class KindaFilelike(object): # pragma: no cover
-
- def __init__(self, bytes):
- self.bytes = bytes
-
- def read(self, n):
- bytes = self.bytes[:n]
- self.bytes = self.bytes[n:]
- return bytes
-
-def app(environ, start_response): # pragma: no cover
- path_info = environ['PATH_INFO']
- if path_info.startswith('/filelike'):
- f = open(fn, 'rb')
- f.seek(0, 2)
- cl = f.tell()
- f.seek(0)
- if path_info == '/filelike':
- headers = [
- ('Content-Length', str(cl)),
- ('Content-Type', 'image/jpeg'),
- ]
- elif path_info == '/filelike_nocl':
- headers = [('Content-Type', 'image/jpeg')]
- elif path_info == '/filelike_shortcl':
- # short content length
- headers = [
- ('Content-Length', '1'),
- ('Content-Type', 'image/jpeg'),
- ]
- else:
- # long content length (/filelike_longcl)
- headers = [
- ('Content-Length', str(cl + 10)),
- ('Content-Type', 'image/jpeg'),
- ]
- else:
- data = open(fn, 'rb').read()
- cl = len(data)
- f = KindaFilelike(data)
- if path_info == '/notfilelike':
- headers = [
- ('Content-Length', str(len(data))),
- ('Content-Type', 'image/jpeg'),
- ]
- elif path_info == '/notfilelike_nocl':
- headers = [('Content-Type', 'image/jpeg')]
- elif path_info == '/notfilelike_shortcl':
- # short content length
- headers = [
- ('Content-Length', '1'),
- ('Content-Type', 'image/jpeg'),
- ]
- else:
- # long content length (/notfilelike_longcl)
- headers = [
- ('Content-Length', str(cl + 10)),
- ('Content-Type', 'image/jpeg'),
- ]
-
- start_response(
- '200 OK',
- headers
- )
- return environ['wsgi.file_wrapper'](f, 8192)
diff --git a/libs/waitress/tests/fixtureapps/getline.py b/libs/waitress/tests/fixtureapps/getline.py
deleted file mode 100644
index 7d8ae5d23..000000000
--- a/libs/waitress/tests/fixtureapps/getline.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import sys
-
-if __name__ == '__main__':
- try:
- from urllib.request import urlopen, URLError
- except ImportError:
- from urllib2 import urlopen, URLError
-
- url = sys.argv[1]
- headers = {'Content-Type': 'text/plain; charset=utf-8'}
- try:
- resp = urlopen(url)
- line = resp.readline().decode('ascii') # py3
- except URLError:
- line = 'failed to read %s' % url
- sys.stdout.write(line)
- sys.stdout.flush()
diff --git a/libs/waitress/tests/fixtureapps/groundhog1.jpg b/libs/waitress/tests/fixtureapps/groundhog1.jpg
deleted file mode 100644
index 90f610ea0..000000000
--- a/libs/waitress/tests/fixtureapps/groundhog1.jpg
+++ /dev/null
Binary files differ
diff --git a/libs/waitress/tests/fixtureapps/nocl.py b/libs/waitress/tests/fixtureapps/nocl.py
deleted file mode 100644
index 05e1d18fb..000000000
--- a/libs/waitress/tests/fixtureapps/nocl.py
+++ /dev/null
@@ -1,24 +0,0 @@
-def chunks(l, n): # pragma: no cover
- """ Yield successive n-sized chunks from l.
- """
- for i in range(0, len(l), n):
- yield l[i:i + n]
-
-def gen(body): # pragma: no cover
- for chunk in chunks(body, 10):
- yield chunk
-
-def app(environ, start_response): # pragma: no cover
- cl = environ.get('CONTENT_LENGTH', None)
- if cl is not None:
- cl = int(cl)
- body = environ['wsgi.input'].read(cl)
- start_response(
- '200 OK',
- [('Content-Type', 'text/plain')]
- )
- if environ['PATH_INFO'] == '/list':
- return [body]
- if environ['PATH_INFO'] == '/list_lentwo':
- return [body[0:1], body[1:]]
- return gen(body)
diff --git a/libs/waitress/tests/fixtureapps/runner.py b/libs/waitress/tests/fixtureapps/runner.py
deleted file mode 100644
index eee0e45f9..000000000
--- a/libs/waitress/tests/fixtureapps/runner.py
+++ /dev/null
@@ -1,5 +0,0 @@
-def app(): # pragma: no cover
- return None
-
-def returns_app(): # pragma: no cover
- return app
diff --git a/libs/waitress/tests/fixtureapps/sleepy.py b/libs/waitress/tests/fixtureapps/sleepy.py
deleted file mode 100644
index 03bd0ab06..000000000
--- a/libs/waitress/tests/fixtureapps/sleepy.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import time
-
-def app(environ, start_response): # pragma: no cover
- if environ['PATH_INFO'] == '/sleepy':
- time.sleep(2)
- body = b'sleepy returned'
- else:
- body = b'notsleepy returned'
- cl = str(len(body))
- start_response(
- '200 OK',
- [('Content-Length', cl), ('Content-Type', 'text/plain')]
- )
- return [body]
diff --git a/libs/waitress/tests/fixtureapps/toolarge.py b/libs/waitress/tests/fixtureapps/toolarge.py
deleted file mode 100644
index 150e90875..000000000
--- a/libs/waitress/tests/fixtureapps/toolarge.py
+++ /dev/null
@@ -1,8 +0,0 @@
-def app(environ, start_response): # pragma: no cover
- body = b'abcdef'
- cl = len(body)
- start_response(
- '200 OK',
- [('Content-Length', str(cl)), ('Content-Type', 'text/plain')]
- )
- return [body]
diff --git a/libs/waitress/tests/fixtureapps/writecb.py b/libs/waitress/tests/fixtureapps/writecb.py
deleted file mode 100644
index ac59eb96f..000000000
--- a/libs/waitress/tests/fixtureapps/writecb.py
+++ /dev/null
@@ -1,14 +0,0 @@
-def app(environ, start_response): # pragma: no cover
- path_info = environ['PATH_INFO']
- if path_info == '/no_content_length':
- headers = []
- else:
- headers = [('Content-Length', '9')]
- write = start_response('200 OK', headers)
- if path_info == '/long_body':
- write(b'abcdefghij')
- elif path_info == '/short_body':
- write(b'abcdefgh')
- else:
- write(b'abcdefghi')
- return []
diff --git a/libs/waitress/tests/test_adjustments.py b/libs/waitress/tests/test_adjustments.py
deleted file mode 100644
index 9446705d2..000000000
--- a/libs/waitress/tests/test_adjustments.py
+++ /dev/null
@@ -1,294 +0,0 @@
-import sys
-import socket
-
-from waitress.compat import (
- PY2,
- WIN,
- )
-
-if sys.version_info[:2] == (2, 6): # pragma: no cover
- import unittest2 as unittest
-else: # pragma: no cover
- import unittest
-
-class Test_asbool(unittest.TestCase):
-
- def _callFUT(self, s):
- from waitress.adjustments import asbool
- return asbool(s)
-
- def test_s_is_None(self):
- result = self._callFUT(None)
- self.assertEqual(result, False)
-
- def test_s_is_True(self):
- result = self._callFUT(True)
- self.assertEqual(result, True)
-
- def test_s_is_False(self):
- result = self._callFUT(False)
- self.assertEqual(result, False)
-
- def test_s_is_true(self):
- result = self._callFUT('True')
- self.assertEqual(result, True)
-
- def test_s_is_false(self):
- result = self._callFUT('False')
- self.assertEqual(result, False)
-
- def test_s_is_yes(self):
- result = self._callFUT('yes')
- self.assertEqual(result, True)
-
- def test_s_is_on(self):
- result = self._callFUT('on')
- self.assertEqual(result, True)
-
- def test_s_is_1(self):
- result = self._callFUT(1)
- self.assertEqual(result, True)
-
-class TestAdjustments(unittest.TestCase):
-
- def _hasIPv6(self): # pragma: nocover
- if not socket.has_ipv6:
- return False
-
- try:
- socket.getaddrinfo(
- '::1',
- 0,
- socket.AF_UNSPEC,
- socket.SOCK_STREAM,
- socket.IPPROTO_TCP,
- socket.AI_PASSIVE | socket.AI_ADDRCONFIG
- )
-
- return True
- except socket.gaierror as e:
- # Check to see what the error is
- if e.errno == socket.EAI_ADDRFAMILY:
- return False
- else:
- raise e
-
- def _makeOne(self, **kw):
- from waitress.adjustments import Adjustments
- return Adjustments(**kw)
-
- def test_goodvars(self):
- inst = self._makeOne(
- host='localhost',
- port='8080',
- threads='5',
- trusted_proxy='192.168.1.1',
- url_scheme='https',
- backlog='20',
- recv_bytes='200',
- send_bytes='300',
- outbuf_overflow='400',
- inbuf_overflow='500',
- connection_limit='1000',
- cleanup_interval='1100',
- channel_timeout='1200',
- log_socket_errors='true',
- max_request_header_size='1300',
- max_request_body_size='1400',
- expose_tracebacks='true',
- ident='abc',
- asyncore_loop_timeout='5',
- asyncore_use_poll=True,
- unix_socket='/tmp/waitress.sock',
- unix_socket_perms='777',
- url_prefix='///foo/',
- ipv4=True,
- ipv6=False,
- )
-
- self.assertEqual(inst.host, 'localhost')
- self.assertEqual(inst.port, 8080)
- self.assertEqual(inst.threads, 5)
- self.assertEqual(inst.trusted_proxy, '192.168.1.1')
- self.assertEqual(inst.url_scheme, 'https')
- self.assertEqual(inst.backlog, 20)
- self.assertEqual(inst.recv_bytes, 200)
- self.assertEqual(inst.send_bytes, 300)
- self.assertEqual(inst.outbuf_overflow, 400)
- self.assertEqual(inst.inbuf_overflow, 500)
- self.assertEqual(inst.connection_limit, 1000)
- self.assertEqual(inst.cleanup_interval, 1100)
- self.assertEqual(inst.channel_timeout, 1200)
- self.assertEqual(inst.log_socket_errors, True)
- self.assertEqual(inst.max_request_header_size, 1300)
- self.assertEqual(inst.max_request_body_size, 1400)
- self.assertEqual(inst.expose_tracebacks, True)
- self.assertEqual(inst.asyncore_loop_timeout, 5)
- self.assertEqual(inst.asyncore_use_poll, True)
- self.assertEqual(inst.ident, 'abc')
- self.assertEqual(inst.unix_socket, '/tmp/waitress.sock')
- self.assertEqual(inst.unix_socket_perms, 0o777)
- self.assertEqual(inst.url_prefix, '/foo')
- self.assertEqual(inst.ipv4, True)
- self.assertEqual(inst.ipv6, False)
-
- bind_pairs = [
- sockaddr[:2]
- for (family, _, _, sockaddr) in inst.listen
- if family == socket.AF_INET
- ]
-
- # On Travis, somehow we start listening to two sockets when resolving
- # localhost...
- self.assertEqual(('127.0.0.1', 8080), bind_pairs[0])
-
- def test_goodvar_listen(self):
- inst = self._makeOne(listen='127.0.0.1')
-
- bind_pairs = [(host, port) for (_, _, _, (host, port)) in inst.listen]
-
- self.assertEqual(bind_pairs, [('127.0.0.1', 8080)])
-
- def test_default_listen(self):
- inst = self._makeOne()
-
- bind_pairs = [(host, port) for (_, _, _, (host, port)) in inst.listen]
-
- self.assertEqual(bind_pairs, [('0.0.0.0', 8080)])
-
- def test_multiple_listen(self):
- inst = self._makeOne(listen='127.0.0.1:9090 127.0.0.1:8080')
-
- bind_pairs = [sockaddr[:2] for (_, _, _, sockaddr) in inst.listen]
-
- self.assertEqual(bind_pairs,
- [('127.0.0.1', 9090),
- ('127.0.0.1', 8080)])
-
- def test_wildcard_listen(self):
- inst = self._makeOne(listen='*:8080')
-
- bind_pairs = [sockaddr[:2] for (_, _, _, sockaddr) in inst.listen]
-
- self.assertTrue(len(bind_pairs) >= 1)
-
- def test_ipv6_no_port(self): # pragma: nocover
- if not self._hasIPv6():
- return
-
- inst = self._makeOne(listen='[::1]')
-
- bind_pairs = [sockaddr[:2] for (_, _, _, sockaddr) in inst.listen]
-
- self.assertEqual(bind_pairs, [('::1', 8080)])
-
- def test_bad_port(self):
- self.assertRaises(ValueError, self._makeOne, listen='127.0.0.1:test')
-
- def test_service_port(self):
- if WIN and PY2: # pragma: no cover
- # On Windows and Python 2 this is broken, so we raise a ValueError
- self.assertRaises(
- ValueError,
- self._makeOne,
- listen='127.0.0.1:http',
- )
- return
-
- inst = self._makeOne(listen='127.0.0.1:http 0.0.0.0:https')
-
- bind_pairs = [sockaddr[:2] for (_, _, _, sockaddr) in inst.listen]
-
- self.assertEqual(bind_pairs, [('127.0.0.1', 80), ('0.0.0.0', 443)])
-
- def test_dont_mix_host_port_listen(self):
- self.assertRaises(
- ValueError,
- self._makeOne,
- host='localhost',
- port='8080',
- listen='127.0.0.1:8080',
- )
-
- def test_badvar(self):
- self.assertRaises(ValueError, self._makeOne, nope=True)
-
- def test_ipv4_disabled(self):
- self.assertRaises(ValueError, self._makeOne, ipv4=False, listen="127.0.0.1:8080")
-
- def test_ipv6_disabled(self):
- self.assertRaises(ValueError, self._makeOne, ipv6=False, listen="[::]:8080")
-
-class TestCLI(unittest.TestCase):
-
- def parse(self, argv):
- from waitress.adjustments import Adjustments
- return Adjustments.parse_args(argv)
-
- def test_noargs(self):
- opts, args = self.parse([])
- self.assertDictEqual(opts, {'call': False, 'help': False})
- self.assertSequenceEqual(args, [])
-
- def test_help(self):
- opts, args = self.parse(['--help'])
- self.assertDictEqual(opts, {'call': False, 'help': True})
- self.assertSequenceEqual(args, [])
-
- def test_call(self):
- opts, args = self.parse(['--call'])
- self.assertDictEqual(opts, {'call': True, 'help': False})
- self.assertSequenceEqual(args, [])
-
- def test_both(self):
- opts, args = self.parse(['--call', '--help'])
- self.assertDictEqual(opts, {'call': True, 'help': True})
- self.assertSequenceEqual(args, [])
-
- def test_positive_boolean(self):
- opts, args = self.parse(['--expose-tracebacks'])
- self.assertDictContainsSubset({'expose_tracebacks': 'true'}, opts)
- self.assertSequenceEqual(args, [])
-
- def test_negative_boolean(self):
- opts, args = self.parse(['--no-expose-tracebacks'])
- self.assertDictContainsSubset({'expose_tracebacks': 'false'}, opts)
- self.assertSequenceEqual(args, [])
-
- def test_cast_params(self):
- opts, args = self.parse([
- '--host=localhost',
- '--port=80',
- '--unix-socket-perms=777'
- ])
- self.assertDictContainsSubset({
- 'host': 'localhost',
- 'port': '80',
- 'unix_socket_perms': '777',
- }, opts)
- self.assertSequenceEqual(args, [])
-
- def test_listen_params(self):
- opts, args = self.parse([
- '--listen=test:80',
- ])
-
- self.assertDictContainsSubset({
- 'listen': ' test:80'
- }, opts)
- self.assertSequenceEqual(args, [])
-
- def test_multiple_listen_params(self):
- opts, args = self.parse([
- '--listen=test:80',
- '--listen=test:8080',
- ])
-
- self.assertDictContainsSubset({
- 'listen': ' test:80 test:8080'
- }, opts)
- self.assertSequenceEqual(args, [])
-
- def test_bad_param(self):
- import getopt
- self.assertRaises(getopt.GetoptError, self.parse, ['--no-host'])
diff --git a/libs/waitress/tests/test_buffers.py b/libs/waitress/tests/test_buffers.py
deleted file mode 100644
index 46a215eb0..000000000
--- a/libs/waitress/tests/test_buffers.py
+++ /dev/null
@@ -1,453 +0,0 @@
-import unittest
-import io
-
-class TestFileBasedBuffer(unittest.TestCase):
-
- def _makeOne(self, file=None, from_buffer=None):
- from waitress.buffers import FileBasedBuffer
- return FileBasedBuffer(file, from_buffer=from_buffer)
-
- def test_ctor_from_buffer_None(self):
- inst = self._makeOne('file')
- self.assertEqual(inst.file, 'file')
-
- def test_ctor_from_buffer(self):
- from_buffer = io.BytesIO(b'data')
- from_buffer.getfile = lambda *x: from_buffer
- f = io.BytesIO()
- inst = self._makeOne(f, from_buffer)
- self.assertEqual(inst.file, f)
- del from_buffer.getfile
- self.assertEqual(inst.remain, 4)
- from_buffer.close()
-
- def test___len__(self):
- inst = self._makeOne()
- inst.remain = 10
- self.assertEqual(len(inst), 10)
-
- def test___nonzero__(self):
- inst = self._makeOne()
- inst.remain = 10
- self.assertEqual(bool(inst), True)
- inst.remain = 0
- self.assertEqual(bool(inst), True)
-
- def test_append(self):
- f = io.BytesIO(b'data')
- inst = self._makeOne(f)
- inst.append(b'data2')
- self.assertEqual(f.getvalue(), b'datadata2')
- self.assertEqual(inst.remain, 5)
-
- def test_get_skip_true(self):
- f = io.BytesIO(b'data')
- inst = self._makeOne(f)
- result = inst.get(100, skip=True)
- self.assertEqual(result, b'data')
- self.assertEqual(inst.remain, -4)
-
- def test_get_skip_false(self):
- f = io.BytesIO(b'data')
- inst = self._makeOne(f)
- result = inst.get(100, skip=False)
- self.assertEqual(result, b'data')
- self.assertEqual(inst.remain, 0)
-
- def test_get_skip_bytes_less_than_zero(self):
- f = io.BytesIO(b'data')
- inst = self._makeOne(f)
- result = inst.get(-1, skip=False)
- self.assertEqual(result, b'data')
- self.assertEqual(inst.remain, 0)
-
- def test_skip_remain_gt_bytes(self):
- f = io.BytesIO(b'd')
- inst = self._makeOne(f)
- inst.remain = 1
- inst.skip(1)
- self.assertEqual(inst.remain, 0)
-
- def test_skip_remain_lt_bytes(self):
- f = io.BytesIO(b'd')
- inst = self._makeOne(f)
- inst.remain = 1
- self.assertRaises(ValueError, inst.skip, 2)
-
- def test_newfile(self):
- inst = self._makeOne()
- self.assertRaises(NotImplementedError, inst.newfile)
-
- def test_prune_remain_notzero(self):
- f = io.BytesIO(b'd')
- inst = self._makeOne(f)
- inst.remain = 1
- nf = io.BytesIO()
- inst.newfile = lambda *x: nf
- inst.prune()
- self.assertTrue(inst.file is not f)
- self.assertEqual(nf.getvalue(), b'd')
-
- def test_prune_remain_zero_tell_notzero(self):
- f = io.BytesIO(b'd')
- inst = self._makeOne(f)
- nf = io.BytesIO(b'd')
- inst.newfile = lambda *x: nf
- inst.remain = 0
- inst.prune()
- self.assertTrue(inst.file is not f)
- self.assertEqual(nf.getvalue(), b'd')
-
- def test_prune_remain_zero_tell_zero(self):
- f = io.BytesIO()
- inst = self._makeOne(f)
- inst.remain = 0
- inst.prune()
- self.assertTrue(inst.file is f)
-
- def test_close(self):
- f = io.BytesIO()
- inst = self._makeOne(f)
- inst.close()
- self.assertTrue(f.closed)
-
-class TestTempfileBasedBuffer(unittest.TestCase):
-
- def _makeOne(self, from_buffer=None):
- from waitress.buffers import TempfileBasedBuffer
- return TempfileBasedBuffer(from_buffer=from_buffer)
-
- def test_newfile(self):
- inst = self._makeOne()
- r = inst.newfile()
- self.assertTrue(hasattr(r, 'fileno')) # file
-
-class TestBytesIOBasedBuffer(unittest.TestCase):
-
- def _makeOne(self, from_buffer=None):
- from waitress.buffers import BytesIOBasedBuffer
- return BytesIOBasedBuffer(from_buffer=from_buffer)
-
- def test_ctor_from_buffer_not_None(self):
- f = io.BytesIO()
- f.getfile = lambda *x: f
- inst = self._makeOne(f)
- self.assertTrue(hasattr(inst.file, 'read'))
-
- def test_ctor_from_buffer_None(self):
- inst = self._makeOne()
- self.assertTrue(hasattr(inst.file, 'read'))
-
- def test_newfile(self):
- inst = self._makeOne()
- r = inst.newfile()
- self.assertTrue(hasattr(r, 'read'))
-
-class TestReadOnlyFileBasedBuffer(unittest.TestCase):
-
- def _makeOne(self, file, block_size=8192):
- from waitress.buffers import ReadOnlyFileBasedBuffer
- return ReadOnlyFileBasedBuffer(file, block_size)
-
- def test_prepare_not_seekable(self):
- f = KindaFilelike(b'abc')
- inst = self._makeOne(f)
- result = inst.prepare()
- self.assertEqual(result, False)
- self.assertEqual(inst.remain, 0)
-
- def test_prepare_not_seekable_closeable(self):
- f = KindaFilelike(b'abc', close=1)
- inst = self._makeOne(f)
- result = inst.prepare()
- self.assertEqual(result, False)
- self.assertEqual(inst.remain, 0)
- self.assertTrue(hasattr(inst, 'close'))
-
- def test_prepare_seekable_closeable(self):
- f = Filelike(b'abc', close=1, tellresults=[0, 10])
- inst = self._makeOne(f)
- result = inst.prepare()
- self.assertEqual(result, 10)
- self.assertEqual(inst.remain, 10)
- self.assertEqual(inst.file.seeked, 0)
- self.assertTrue(hasattr(inst, 'close'))
-
- def test_get_numbytes_neg_one(self):
- f = io.BytesIO(b'abcdef')
- inst = self._makeOne(f)
- inst.remain = 2
- result = inst.get(-1)
- self.assertEqual(result, b'ab')
- self.assertEqual(inst.remain, 2)
- self.assertEqual(f.tell(), 0)
-
- def test_get_numbytes_gt_remain(self):
- f = io.BytesIO(b'abcdef')
- inst = self._makeOne(f)
- inst.remain = 2
- result = inst.get(3)
- self.assertEqual(result, b'ab')
- self.assertEqual(inst.remain, 2)
- self.assertEqual(f.tell(), 0)
-
- def test_get_numbytes_lt_remain(self):
- f = io.BytesIO(b'abcdef')
- inst = self._makeOne(f)
- inst.remain = 2
- result = inst.get(1)
- self.assertEqual(result, b'a')
- self.assertEqual(inst.remain, 2)
- self.assertEqual(f.tell(), 0)
-
- def test_get_numbytes_gt_remain_withskip(self):
- f = io.BytesIO(b'abcdef')
- inst = self._makeOne(f)
- inst.remain = 2
- result = inst.get(3, skip=True)
- self.assertEqual(result, b'ab')
- self.assertEqual(inst.remain, 0)
- self.assertEqual(f.tell(), 2)
-
- def test_get_numbytes_lt_remain_withskip(self):
- f = io.BytesIO(b'abcdef')
- inst = self._makeOne(f)
- inst.remain = 2
- result = inst.get(1, skip=True)
- self.assertEqual(result, b'a')
- self.assertEqual(inst.remain, 1)
- self.assertEqual(f.tell(), 1)
-
- def test___iter__(self):
- data = b'a' * 10000
- f = io.BytesIO(data)
- inst = self._makeOne(f)
- r = b''
- for val in inst:
- r += val
- self.assertEqual(r, data)
-
- def test_append(self):
- inst = self._makeOne(None)
- self.assertRaises(NotImplementedError, inst.append, 'a')
-
-class TestOverflowableBuffer(unittest.TestCase):
-
- def _makeOne(self, overflow=10):
- from waitress.buffers import OverflowableBuffer
- return OverflowableBuffer(overflow)
-
- def test___len__buf_is_None(self):
- inst = self._makeOne()
- self.assertEqual(len(inst), 0)
-
- def test___len__buf_is_not_None(self):
- inst = self._makeOne()
- inst.buf = b'abc'
- self.assertEqual(len(inst), 3)
-
- def test___nonzero__(self):
- inst = self._makeOne()
- inst.buf = b'abc'
- self.assertEqual(bool(inst), True)
- inst.buf = b''
- self.assertEqual(bool(inst), False)
-
- def test___nonzero___on_int_overflow_buffer(self):
- inst = self._makeOne()
-
- class int_overflow_buf(bytes):
- def __len__(self):
- # maxint + 1
- return 0x7fffffffffffffff + 1
- inst.buf = int_overflow_buf()
- self.assertEqual(bool(inst), True)
- inst.buf = b''
- self.assertEqual(bool(inst), False)
-
- def test__create_buffer_large(self):
- from waitress.buffers import TempfileBasedBuffer
- inst = self._makeOne()
- inst.strbuf = b'x' * 11
- inst._create_buffer()
- self.assertEqual(inst.buf.__class__, TempfileBasedBuffer)
- self.assertEqual(inst.buf.get(100), b'x' * 11)
- self.assertEqual(inst.strbuf, b'')
-
- def test__create_buffer_small(self):
- from waitress.buffers import BytesIOBasedBuffer
- inst = self._makeOne()
- inst.strbuf = b'x' * 5
- inst._create_buffer()
- self.assertEqual(inst.buf.__class__, BytesIOBasedBuffer)
- self.assertEqual(inst.buf.get(100), b'x' * 5)
- self.assertEqual(inst.strbuf, b'')
-
- def test_append_with_len_more_than_max_int(self):
- from waitress.compat import MAXINT
- inst = self._makeOne()
- inst.overflowed = True
- buf = DummyBuffer(length=MAXINT)
- inst.buf = buf
- result = inst.append(b'x')
- # we don't want this to throw an OverflowError on Python 2 (see
- # https://github.com/Pylons/waitress/issues/47)
- self.assertEqual(result, None)
-
- def test_append_buf_None_not_longer_than_srtbuf_limit(self):
- inst = self._makeOne()
- inst.strbuf = b'x' * 5
- inst.append(b'hello')
- self.assertEqual(inst.strbuf, b'xxxxxhello')
-
- def test_append_buf_None_longer_than_strbuf_limit(self):
- inst = self._makeOne(10000)
- inst.strbuf = b'x' * 8192
- inst.append(b'hello')
- self.assertEqual(inst.strbuf, b'')
- self.assertEqual(len(inst.buf), 8197)
-
- def test_append_overflow(self):
- inst = self._makeOne(10)
- inst.strbuf = b'x' * 8192
- inst.append(b'hello')
- self.assertEqual(inst.strbuf, b'')
- self.assertEqual(len(inst.buf), 8197)
-
- def test_append_sz_gt_overflow(self):
- from waitress.buffers import BytesIOBasedBuffer
- f = io.BytesIO(b'data')
- inst = self._makeOne(f)
- buf = BytesIOBasedBuffer()
- inst.buf = buf
- inst.overflow = 2
- inst.append(b'data2')
- self.assertEqual(f.getvalue(), b'data')
- self.assertTrue(inst.overflowed)
- self.assertNotEqual(inst.buf, buf)
-
- def test_get_buf_None_skip_False(self):
- inst = self._makeOne()
- inst.strbuf = b'x' * 5
- r = inst.get(5)
- self.assertEqual(r, b'xxxxx')
-
- def test_get_buf_None_skip_True(self):
- inst = self._makeOne()
- inst.strbuf = b'x' * 5
- r = inst.get(5, skip=True)
- self.assertFalse(inst.buf is None)
- self.assertEqual(r, b'xxxxx')
-
- def test_skip_buf_None(self):
- inst = self._makeOne()
- inst.strbuf = b'data'
- inst.skip(4)
- self.assertEqual(inst.strbuf, b'')
- self.assertNotEqual(inst.buf, None)
-
- def test_skip_buf_None_allow_prune_True(self):
- inst = self._makeOne()
- inst.strbuf = b'data'
- inst.skip(4, True)
- self.assertEqual(inst.strbuf, b'')
- self.assertEqual(inst.buf, None)
-
- def test_prune_buf_None(self):
- inst = self._makeOne()
- inst.prune()
- self.assertEqual(inst.strbuf, b'')
-
- def test_prune_with_buf(self):
- inst = self._makeOne()
- class Buf(object):
- def prune(self):
- self.pruned = True
- inst.buf = Buf()
- inst.prune()
- self.assertEqual(inst.buf.pruned, True)
-
- def test_prune_with_buf_overflow(self):
- inst = self._makeOne()
- class DummyBuffer(io.BytesIO):
- def getfile(self):
- return self
- def prune(self):
- return True
- def __len__(self):
- return 5
- buf = DummyBuffer(b'data')
- inst.buf = buf
- inst.overflowed = True
- inst.overflow = 10
- inst.prune()
- self.assertNotEqual(inst.buf, buf)
-
- def test_prune_with_buflen_more_than_max_int(self):
- from waitress.compat import MAXINT
- inst = self._makeOne()
- inst.overflowed = True
- buf = DummyBuffer(length=MAXINT+1)
- inst.buf = buf
- result = inst.prune()
- # we don't want this to throw an OverflowError on Python 2 (see
- # https://github.com/Pylons/waitress/issues/47)
- self.assertEqual(result, None)
-
- def test_getfile_buf_None(self):
- inst = self._makeOne()
- f = inst.getfile()
- self.assertTrue(hasattr(f, 'read'))
-
- def test_getfile_buf_not_None(self):
- inst = self._makeOne()
- buf = io.BytesIO()
- buf.getfile = lambda *x: buf
- inst.buf = buf
- f = inst.getfile()
- self.assertEqual(f, buf)
-
- def test_close_nobuf(self):
- inst = self._makeOne()
- inst.buf = None
- self.assertEqual(inst.close(), None) # doesnt raise
-
- def test_close_withbuf(self):
- class Buffer(object):
- def close(self):
- self.closed = True
- buf = Buffer()
- inst = self._makeOne()
- inst.buf = buf
- inst.close()
- self.assertTrue(buf.closed)
-
-class KindaFilelike(object):
-
- def __init__(self, bytes, close=None, tellresults=None):
- self.bytes = bytes
- self.tellresults = tellresults
- if close is not None:
- self.close = close
-
-class Filelike(KindaFilelike):
-
- def seek(self, v, whence=0):
- self.seeked = v
-
- def tell(self):
- v = self.tellresults.pop(0)
- return v
-
-class DummyBuffer(object):
- def __init__(self, length=0):
- self.length = length
-
- def __len__(self):
- return self.length
-
- def append(self, s):
- self.length = self.length + len(s)
-
- def prune(self):
- pass
diff --git a/libs/waitress/tests/test_channel.py b/libs/waitress/tests/test_channel.py
deleted file mode 100644
index afe6e510d..000000000
--- a/libs/waitress/tests/test_channel.py
+++ /dev/null
@@ -1,727 +0,0 @@
-import unittest
-import io
-
-class TestHTTPChannel(unittest.TestCase):
-
- def _makeOne(self, sock, addr, adj, map=None):
- from waitress.channel import HTTPChannel
- server = DummyServer()
- return HTTPChannel(server, sock, addr, adj=adj, map=map)
-
- def _makeOneWithMap(self, adj=None):
- if adj is None:
- adj = DummyAdjustments()
- sock = DummySock()
- map = {}
- inst = self._makeOne(sock, '127.0.0.1', adj, map=map)
- inst.outbuf_lock = DummyLock()
- return inst, sock, map
-
- def test_ctor(self):
- inst, _, map = self._makeOneWithMap()
- self.assertEqual(inst.addr, '127.0.0.1')
- self.assertEqual(map[100], inst)
-
- def test_total_outbufs_len_an_outbuf_size_gt_sys_maxint(self):
- from waitress.compat import MAXINT
- inst, _, map = self._makeOneWithMap()
- class DummyHugeBuffer(object):
- def __len__(self):
- return MAXINT + 1
- inst.outbufs = [DummyHugeBuffer()]
- result = inst.total_outbufs_len()
- # we are testing that this method does not raise an OverflowError
- # (see https://github.com/Pylons/waitress/issues/47)
- self.assertEqual(result, MAXINT+1)
-
- def test_writable_something_in_outbuf(self):
- inst, sock, map = self._makeOneWithMap()
- inst.outbufs[0].append(b'abc')
- self.assertTrue(inst.writable())
-
- def test_writable_nothing_in_outbuf(self):
- inst, sock, map = self._makeOneWithMap()
- self.assertFalse(inst.writable())
-
- def test_writable_nothing_in_outbuf_will_close(self):
- inst, sock, map = self._makeOneWithMap()
- inst.will_close = True
- self.assertTrue(inst.writable())
-
- def test_handle_write_not_connected(self):
- inst, sock, map = self._makeOneWithMap()
- inst.connected = False
- self.assertFalse(inst.handle_write())
-
- def test_handle_write_with_requests(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = True
- inst.last_activity = 0
- result = inst.handle_write()
- self.assertEqual(result, None)
- self.assertEqual(inst.last_activity, 0)
-
- def test_handle_write_no_request_with_outbuf(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = []
- inst.outbufs = [DummyBuffer(b'abc')]
- inst.last_activity = 0
- result = inst.handle_write()
- self.assertEqual(result, None)
- self.assertNotEqual(inst.last_activity, 0)
- self.assertEqual(sock.sent, b'abc')
-
- def test_handle_write_outbuf_raises_socketerror(self):
- import socket
- inst, sock, map = self._makeOneWithMap()
- inst.requests = []
- outbuf = DummyBuffer(b'abc', socket.error)
- inst.outbufs = [outbuf]
- inst.last_activity = 0
- inst.logger = DummyLogger()
- result = inst.handle_write()
- self.assertEqual(result, None)
- self.assertEqual(inst.last_activity, 0)
- self.assertEqual(sock.sent, b'')
- self.assertEqual(len(inst.logger.exceptions), 1)
- self.assertTrue(outbuf.closed)
-
- def test_handle_write_outbuf_raises_othererror(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = []
- outbuf = DummyBuffer(b'abc', IOError)
- inst.outbufs = [outbuf]
- inst.last_activity = 0
- inst.logger = DummyLogger()
- result = inst.handle_write()
- self.assertEqual(result, None)
- self.assertEqual(inst.last_activity, 0)
- self.assertEqual(sock.sent, b'')
- self.assertEqual(len(inst.logger.exceptions), 1)
- self.assertTrue(outbuf.closed)
-
- def test_handle_write_no_requests_no_outbuf_will_close(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = []
- outbuf = DummyBuffer(b'')
- inst.outbufs = [outbuf]
- inst.will_close = True
- inst.last_activity = 0
- result = inst.handle_write()
- self.assertEqual(result, None)
- self.assertEqual(inst.connected, False)
- self.assertEqual(sock.closed, True)
- self.assertEqual(inst.last_activity, 0)
- self.assertTrue(outbuf.closed)
-
- def test_handle_write_no_requests_force_flush(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = [True]
- inst.outbufs = [DummyBuffer(b'abc')]
- inst.will_close = False
- inst.force_flush = True
- inst.last_activity = 0
- result = inst.handle_write()
- self.assertEqual(result, None)
- self.assertEqual(inst.will_close, False)
- self.assertTrue(inst.outbuf_lock.acquired)
- self.assertEqual(inst.force_flush, False)
- self.assertEqual(sock.sent, b'abc')
-
- def test_handle_write_no_requests_outbuf_gt_send_bytes(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = [True]
- inst.outbufs = [DummyBuffer(b'abc')]
- inst.adj.send_bytes = 2
- inst.will_close = False
- inst.last_activity = 0
- result = inst.handle_write()
- self.assertEqual(result, None)
- self.assertEqual(inst.will_close, False)
- self.assertTrue(inst.outbuf_lock.acquired)
- self.assertEqual(sock.sent, b'abc')
-
- def test_handle_write_close_when_flushed(self):
- inst, sock, map = self._makeOneWithMap()
- outbuf = DummyBuffer(b'abc')
- inst.outbufs = [outbuf]
- inst.will_close = False
- inst.close_when_flushed = True
- inst.last_activity = 0
- result = inst.handle_write()
- self.assertEqual(result, None)
- self.assertEqual(inst.will_close, True)
- self.assertEqual(inst.close_when_flushed, False)
- self.assertEqual(sock.sent, b'abc')
- self.assertTrue(outbuf.closed)
-
- def test_readable_no_requests_not_will_close(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = []
- inst.will_close = False
- self.assertEqual(inst.readable(), True)
-
- def test_readable_no_requests_will_close(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = []
- inst.will_close = True
- self.assertEqual(inst.readable(), False)
-
- def test_readable_with_requests(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = True
- self.assertEqual(inst.readable(), False)
-
- def test_handle_read_no_error(self):
- inst, sock, map = self._makeOneWithMap()
- inst.will_close = False
- inst.recv = lambda *arg: b'abc'
- inst.last_activity = 0
- L = []
- inst.received = lambda x: L.append(x)
- result = inst.handle_read()
- self.assertEqual(result, None)
- self.assertNotEqual(inst.last_activity, 0)
- self.assertEqual(L, [b'abc'])
-
- def test_handle_read_error(self):
- import socket
- inst, sock, map = self._makeOneWithMap()
- inst.will_close = False
- def recv(b):
- raise socket.error
- inst.recv = recv
- inst.last_activity = 0
- inst.logger = DummyLogger()
- result = inst.handle_read()
- self.assertEqual(result, None)
- self.assertEqual(inst.last_activity, 0)
- self.assertEqual(len(inst.logger.exceptions), 1)
-
- def test_write_soon_empty_byte(self):
- inst, sock, map = self._makeOneWithMap()
- wrote = inst.write_soon(b'')
- self.assertEqual(wrote, 0)
- self.assertEqual(len(inst.outbufs[0]), 0)
-
- def test_write_soon_nonempty_byte(self):
- inst, sock, map = self._makeOneWithMap()
- wrote = inst.write_soon(b'a')
- self.assertEqual(wrote, 1)
- self.assertEqual(len(inst.outbufs[0]), 1)
-
- def test_write_soon_filewrapper(self):
- from waitress.buffers import ReadOnlyFileBasedBuffer
- f = io.BytesIO(b'abc')
- wrapper = ReadOnlyFileBasedBuffer(f, 8192)
- wrapper.prepare()
- inst, sock, map = self._makeOneWithMap()
- outbufs = inst.outbufs
- orig_outbuf = outbufs[0]
- wrote = inst.write_soon(wrapper)
- self.assertEqual(wrote, 3)
- self.assertEqual(len(outbufs), 3)
- self.assertEqual(outbufs[0], orig_outbuf)
- self.assertEqual(outbufs[1], wrapper)
- self.assertEqual(outbufs[2].__class__.__name__, 'OverflowableBuffer')
-
- def test__flush_some_empty_outbuf(self):
- inst, sock, map = self._makeOneWithMap()
- result = inst._flush_some()
- self.assertEqual(result, False)
-
- def test__flush_some_full_outbuf_socket_returns_nonzero(self):
- inst, sock, map = self._makeOneWithMap()
- inst.outbufs[0].append(b'abc')
- result = inst._flush_some()
- self.assertEqual(result, True)
-
- def test__flush_some_full_outbuf_socket_returns_zero(self):
- inst, sock, map = self._makeOneWithMap()
- sock.send = lambda x: False
- inst.outbufs[0].append(b'abc')
- result = inst._flush_some()
- self.assertEqual(result, False)
-
- def test_flush_some_multiple_buffers_first_empty(self):
- inst, sock, map = self._makeOneWithMap()
- sock.send = lambda x: len(x)
- buffer = DummyBuffer(b'abc')
- inst.outbufs.append(buffer)
- result = inst._flush_some()
- self.assertEqual(result, True)
- self.assertEqual(buffer.skipped, 3)
- self.assertEqual(inst.outbufs, [buffer])
-
- def test_flush_some_multiple_buffers_close_raises(self):
- inst, sock, map = self._makeOneWithMap()
- sock.send = lambda x: len(x)
- buffer = DummyBuffer(b'abc')
- inst.outbufs.append(buffer)
- inst.logger = DummyLogger()
- def doraise():
- raise NotImplementedError
- inst.outbufs[0].close = doraise
- result = inst._flush_some()
- self.assertEqual(result, True)
- self.assertEqual(buffer.skipped, 3)
- self.assertEqual(inst.outbufs, [buffer])
- self.assertEqual(len(inst.logger.exceptions), 1)
-
- def test__flush_some_outbuf_len_gt_sys_maxint(self):
- from waitress.compat import MAXINT
- inst, sock, map = self._makeOneWithMap()
- class DummyHugeOutbuffer(object):
- def __init__(self):
- self.length = MAXINT + 1
- def __len__(self):
- return self.length
- def get(self, numbytes):
- self.length = 0
- return b'123'
- def skip(self, *args): pass
- buf = DummyHugeOutbuffer()
- inst.outbufs = [buf]
- inst.send = lambda *arg: 0
- result = inst._flush_some()
- # we are testing that _flush_some doesn't raise an OverflowError
- # when one of its outbufs has a __len__ that returns gt sys.maxint
- self.assertEqual(result, False)
-
- def test_handle_close(self):
- inst, sock, map = self._makeOneWithMap()
- inst.handle_close()
- self.assertEqual(inst.connected, False)
- self.assertEqual(sock.closed, True)
-
- def test_handle_close_outbuf_raises_on_close(self):
- inst, sock, map = self._makeOneWithMap()
- def doraise():
- raise NotImplementedError
- inst.outbufs[0].close = doraise
- inst.logger = DummyLogger()
- inst.handle_close()
- self.assertEqual(inst.connected, False)
- self.assertEqual(sock.closed, True)
- self.assertEqual(len(inst.logger.exceptions), 1)
-
- def test_add_channel(self):
- inst, sock, map = self._makeOneWithMap()
- fileno = inst._fileno
- inst.add_channel(map)
- self.assertEqual(map[fileno], inst)
- self.assertEqual(inst.server.active_channels[fileno], inst)
-
- def test_del_channel(self):
- inst, sock, map = self._makeOneWithMap()
- fileno = inst._fileno
- inst.server.active_channels[fileno] = True
- inst.del_channel(map)
- self.assertEqual(map.get(fileno), None)
- self.assertEqual(inst.server.active_channels.get(fileno), None)
-
- def test_received(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- inst.received(b'GET / HTTP/1.1\n\n')
- self.assertEqual(inst.server.tasks, [inst])
- self.assertTrue(inst.requests)
-
- def test_received_no_chunk(self):
- inst, sock, map = self._makeOneWithMap()
- self.assertEqual(inst.received(b''), False)
-
- def test_received_preq_not_completed(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- preq = DummyParser()
- inst.request = preq
- preq.completed = False
- preq.empty = True
- inst.received(b'GET / HTTP/1.1\n\n')
- self.assertEqual(inst.requests, ())
- self.assertEqual(inst.server.tasks, [])
-
- def test_received_preq_completed_empty(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- preq = DummyParser()
- inst.request = preq
- preq.completed = True
- preq.empty = True
- inst.received(b'GET / HTTP/1.1\n\n')
- self.assertEqual(inst.request, None)
- self.assertEqual(inst.server.tasks, [])
-
- def test_received_preq_error(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- preq = DummyParser()
- inst.request = preq
- preq.completed = True
- preq.error = True
- inst.received(b'GET / HTTP/1.1\n\n')
- self.assertEqual(inst.request, None)
- self.assertEqual(len(inst.server.tasks), 1)
- self.assertTrue(inst.requests)
-
- def test_received_preq_completed_connection_close(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- preq = DummyParser()
- inst.request = preq
- preq.completed = True
- preq.empty = True
- preq.connection_close = True
- inst.received(b'GET / HTTP/1.1\n\n' + b'a' * 50000)
- self.assertEqual(inst.request, None)
- self.assertEqual(inst.server.tasks, [])
-
- def test_received_preq_completed_n_lt_data(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- preq = DummyParser()
- inst.request = preq
- preq.completed = True
- preq.empty = False
- line = b'GET / HTTP/1.1\n\n'
- preq.retval = len(line)
- inst.received(line + line)
- self.assertEqual(inst.request, None)
- self.assertEqual(len(inst.requests), 2)
- self.assertEqual(len(inst.server.tasks), 1)
-
- def test_received_headers_finished_expect_continue_false(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- preq = DummyParser()
- inst.request = preq
- preq.expect_continue = False
- preq.headers_finished = True
- preq.completed = False
- preq.empty = False
- preq.retval = 1
- inst.received(b'GET / HTTP/1.1\n\n')
- self.assertEqual(inst.request, preq)
- self.assertEqual(inst.server.tasks, [])
- self.assertEqual(inst.outbufs[0].get(100), b'')
-
- def test_received_headers_finished_expect_continue_true(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- preq = DummyParser()
- inst.request = preq
- preq.expect_continue = True
- preq.headers_finished = True
- preq.completed = False
- preq.empty = False
- inst.received(b'GET / HTTP/1.1\n\n')
- self.assertEqual(inst.request, preq)
- self.assertEqual(inst.server.tasks, [])
- self.assertEqual(sock.sent, b'HTTP/1.1 100 Continue\r\n\r\n')
- self.assertEqual(inst.sent_continue, True)
- self.assertEqual(preq.completed, False)
-
- def test_received_headers_finished_expect_continue_true_sent_true(self):
- inst, sock, map = self._makeOneWithMap()
- inst.server = DummyServer()
- preq = DummyParser()
- inst.request = preq
- preq.expect_continue = True
- preq.headers_finished = True
- preq.completed = False
- preq.empty = False
- inst.sent_continue = True
- inst.received(b'GET / HTTP/1.1\n\n')
- self.assertEqual(inst.request, preq)
- self.assertEqual(inst.server.tasks, [])
- self.assertEqual(sock.sent, b'')
- self.assertEqual(inst.sent_continue, True)
- self.assertEqual(preq.completed, False)
-
- def test_service_no_requests(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = []
- inst.service()
- self.assertEqual(inst.requests, [])
- self.assertTrue(inst.force_flush)
- self.assertTrue(inst.last_activity)
-
- def test_service_with_one_request(self):
- inst, sock, map = self._makeOneWithMap()
- request = DummyRequest()
- inst.task_class = DummyTaskClass()
- inst.requests = [request]
- inst.service()
- self.assertEqual(inst.requests, [])
- self.assertTrue(request.serviced)
- self.assertTrue(request.closed)
-
- def test_service_with_one_error_request(self):
- inst, sock, map = self._makeOneWithMap()
- request = DummyRequest()
- request.error = DummyError()
- inst.error_task_class = DummyTaskClass()
- inst.requests = [request]
- inst.service()
- self.assertEqual(inst.requests, [])
- self.assertTrue(request.serviced)
- self.assertTrue(request.closed)
-
- def test_service_with_multiple_requests(self):
- inst, sock, map = self._makeOneWithMap()
- request1 = DummyRequest()
- request2 = DummyRequest()
- inst.task_class = DummyTaskClass()
- inst.requests = [request1, request2]
- inst.service()
- self.assertEqual(inst.requests, [])
- self.assertTrue(request1.serviced)
- self.assertTrue(request2.serviced)
- self.assertTrue(request1.closed)
- self.assertTrue(request2.closed)
-
- def test_service_with_request_raises(self):
- inst, sock, map = self._makeOneWithMap()
- inst.adj.expose_tracebacks = False
- inst.server = DummyServer()
- request = DummyRequest()
- inst.requests = [request]
- inst.task_class = DummyTaskClass(ValueError)
- inst.task_class.wrote_header = False
- inst.error_task_class = DummyTaskClass()
- inst.logger = DummyLogger()
- inst.service()
- self.assertTrue(request.serviced)
- self.assertEqual(inst.requests, [])
- self.assertEqual(len(inst.logger.exceptions), 1)
- self.assertTrue(inst.force_flush)
- self.assertTrue(inst.last_activity)
- self.assertFalse(inst.will_close)
- self.assertEqual(inst.error_task_class.serviced, True)
- self.assertTrue(request.closed)
-
- def test_service_with_requests_raises_already_wrote_header(self):
- inst, sock, map = self._makeOneWithMap()
- inst.adj.expose_tracebacks = False
- inst.server = DummyServer()
- request = DummyRequest()
- inst.requests = [request]
- inst.task_class = DummyTaskClass(ValueError)
- inst.error_task_class = DummyTaskClass()
- inst.logger = DummyLogger()
- inst.service()
- self.assertTrue(request.serviced)
- self.assertEqual(inst.requests, [])
- self.assertEqual(len(inst.logger.exceptions), 1)
- self.assertTrue(inst.force_flush)
- self.assertTrue(inst.last_activity)
- self.assertTrue(inst.close_when_flushed)
- self.assertEqual(inst.error_task_class.serviced, False)
- self.assertTrue(request.closed)
-
- def test_service_with_requests_raises_didnt_write_header_expose_tbs(self):
- inst, sock, map = self._makeOneWithMap()
- inst.adj.expose_tracebacks = True
- inst.server = DummyServer()
- request = DummyRequest()
- inst.requests = [request]
- inst.task_class = DummyTaskClass(ValueError)
- inst.task_class.wrote_header = False
- inst.error_task_class = DummyTaskClass()
- inst.logger = DummyLogger()
- inst.service()
- self.assertTrue(request.serviced)
- self.assertFalse(inst.will_close)
- self.assertEqual(inst.requests, [])
- self.assertEqual(len(inst.logger.exceptions), 1)
- self.assertTrue(inst.force_flush)
- self.assertTrue(inst.last_activity)
- self.assertEqual(inst.error_task_class.serviced, True)
- self.assertTrue(request.closed)
-
- def test_service_with_requests_raises_didnt_write_header(self):
- inst, sock, map = self._makeOneWithMap()
- inst.adj.expose_tracebacks = False
- inst.server = DummyServer()
- request = DummyRequest()
- inst.requests = [request]
- inst.task_class = DummyTaskClass(ValueError)
- inst.task_class.wrote_header = False
- inst.logger = DummyLogger()
- inst.service()
- self.assertTrue(request.serviced)
- self.assertEqual(inst.requests, [])
- self.assertEqual(len(inst.logger.exceptions), 1)
- self.assertTrue(inst.force_flush)
- self.assertTrue(inst.last_activity)
- self.assertTrue(inst.close_when_flushed)
- self.assertTrue(request.closed)
-
- def test_cancel_no_requests(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = ()
- inst.cancel()
- self.assertEqual(inst.requests, [])
-
- def test_cancel_with_requests(self):
- inst, sock, map = self._makeOneWithMap()
- inst.requests = [None]
- inst.cancel()
- self.assertEqual(inst.requests, [])
-
- def test_defer(self):
- inst, sock, map = self._makeOneWithMap()
- self.assertEqual(inst.defer(), None)
-
-class DummySock(object):
- blocking = False
- closed = False
-
- def __init__(self):
- self.sent = b''
-
- def setblocking(self, *arg):
- self.blocking = True
-
- def fileno(self):
- return 100
-
- def getpeername(self):
- return '127.0.0.1'
-
- def close(self):
- self.closed = True
-
- def send(self, data):
- self.sent += data
- return len(data)
-
-class DummyLock(object):
-
- def __init__(self, acquirable=True):
- self.acquirable = acquirable
-
- def acquire(self, val):
- self.val = val
- self.acquired = True
- return self.acquirable
-
- def release(self):
- self.released = True
-
- def __exit__(self, type, val, traceback):
- self.acquire(True)
-
- def __enter__(self):
- pass
-
-class DummyBuffer(object):
- closed = False
-
- def __init__(self, data, toraise=None):
- self.data = data
- self.toraise = toraise
-
- def get(self, *arg):
- if self.toraise:
- raise self.toraise
- data = self.data
- self.data = b''
- return data
-
- def skip(self, num, x):
- self.skipped = num
-
- def __len__(self):
- return len(self.data)
-
- def close(self):
- self.closed = True
-
-class DummyAdjustments(object):
- outbuf_overflow = 1048576
- inbuf_overflow = 512000
- cleanup_interval = 900
- send_bytes = 9000
- url_scheme = 'http'
- channel_timeout = 300
- log_socket_errors = True
- recv_bytes = 8192
- expose_tracebacks = True
- ident = 'waitress'
- max_request_header_size = 10000
-
-class DummyServer(object):
- trigger_pulled = False
- adj = DummyAdjustments()
-
- def __init__(self):
- self.tasks = []
- self.active_channels = {}
-
- def add_task(self, task):
- self.tasks.append(task)
-
- def pull_trigger(self):
- self.trigger_pulled = True
-
-class DummyParser(object):
- version = 1
- data = None
- completed = True
- empty = False
- headers_finished = False
- expect_continue = False
- retval = None
- error = None
- connection_close = False
-
- def received(self, data):
- self.data = data
- if self.retval is not None:
- return self.retval
- return len(data)
-
-class DummyRequest(object):
- error = None
- path = '/'
- version = '1.0'
- closed = False
-
- def __init__(self):
- self.headers = {}
-
- def close(self):
- self.closed = True
-
-class DummyLogger(object):
-
- def __init__(self):
- self.exceptions = []
-
- def exception(self, msg):
- self.exceptions.append(msg)
-
-class DummyError(object):
- code = '431'
- reason = 'Bleh'
- body = 'My body'
-
-class DummyTaskClass(object):
- wrote_header = True
- close_on_finish = False
- serviced = False
-
- def __init__(self, toraise=None):
- self.toraise = toraise
-
- def __call__(self, channel, request):
- self.request = request
- return self
-
- def service(self):
- self.serviced = True
- self.request.serviced = True
- if self.toraise:
- raise self.toraise
diff --git a/libs/waitress/tests/test_compat.py b/libs/waitress/tests/test_compat.py
deleted file mode 100644
index b5f662572..000000000
--- a/libs/waitress/tests/test_compat.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-
-import unittest
-
-class Test_unquote_bytes_to_wsgi(unittest.TestCase):
-
- def _callFUT(self, v):
- from waitress.compat import unquote_bytes_to_wsgi
- return unquote_bytes_to_wsgi(v)
-
- def test_highorder(self):
- from waitress.compat import PY3
- val = b'/a%C5%9B'
- result = self._callFUT(val)
- if PY3: # pragma: no cover
- # PEP 3333 urlunquoted-latin1-decoded-bytes
- self.assertEqual(result, '/aÃ…\x9b')
- else: # pragma: no cover
- # sanity
- self.assertEqual(result, b'/a\xc5\x9b')
diff --git a/libs/waitress/tests/test_functional.py b/libs/waitress/tests/test_functional.py
deleted file mode 100644
index 59ef4e4d4..000000000
--- a/libs/waitress/tests/test_functional.py
+++ /dev/null
@@ -1,1551 +0,0 @@
-import errno
-import logging
-import multiprocessing
-import os
-import socket
-import string
-import subprocess
-import sys
-import time
-import unittest
-from waitress import server
-from waitress.compat import (
- httplib,
- tobytes
-)
-from waitress.utilities import cleanup_unix_socket
-
-dn = os.path.dirname
-here = dn(__file__)
-
-class NullHandler(logging.Handler): # pragma: no cover
- """A logging handler that swallows all emitted messages.
- """
- def emit(self, record):
- pass
-
-def start_server(app, svr, queue, **kwargs): # pragma: no cover
- """Run a fixture application.
- """
- logging.getLogger('waitress').addHandler(NullHandler())
- svr(app, queue, **kwargs).run()
-
-class FixtureTcpWSGIServer(server.TcpWSGIServer):
- """A version of TcpWSGIServer that relays back what it's bound to.
- """
-
- family = socket.AF_INET # Testing
-
- def __init__(self, application, queue, **kw): # pragma: no cover
- # Coverage doesn't see this as it's ran in a separate process.
- kw['port'] = 0 # Bind to any available port.
- super(FixtureTcpWSGIServer, self).__init__(application, **kw)
- host, port = self.socket.getsockname()
- if os.name == 'nt':
- host = '127.0.0.1'
- queue.put((host, port))
-
-class SubprocessTests(object):
-
- # For nose: all tests may be ran in separate processes.
- _multiprocess_can_split_ = True
-
- exe = sys.executable
-
- server = None
-
- def start_subprocess(self, target, **kw):
- # Spawn a server process.
- self.queue = multiprocessing.Queue()
- self.proc = multiprocessing.Process(
- target=start_server,
- args=(target, self.server, self.queue),
- kwargs=kw,
- )
- self.proc.start()
- if self.proc.exitcode is not None: # pragma: no cover
- raise RuntimeError("%s didn't start" % str(target))
- # Get the socket the server is listening on.
- self.bound_to = self.queue.get(timeout=5)
- self.sock = self.create_socket()
-
- def stop_subprocess(self):
- if self.proc.exitcode is None:
- self.proc.terminate()
- self.sock.close()
- # This give us one FD back ...
- self.queue.close()
-
- def assertline(self, line, status, reason, version):
- v, s, r = (x.strip() for x in line.split(None, 2))
- self.assertEqual(s, tobytes(status))
- self.assertEqual(r, tobytes(reason))
- self.assertEqual(v, tobytes(version))
-
- def create_socket(self):
- return socket.socket(self.server.family, socket.SOCK_STREAM)
-
- def connect(self):
- self.sock.connect(self.bound_to)
-
- def make_http_connection(self):
- raise NotImplementedError # pragma: no cover
-
- def send_check_error(self, to_send):
- self.sock.send(to_send)
-
-class TcpTests(SubprocessTests):
-
- server = FixtureTcpWSGIServer
-
- def make_http_connection(self):
- return httplib.HTTPConnection(*self.bound_to)
-
-class SleepyThreadTests(TcpTests, unittest.TestCase):
- # test that sleepy thread doesnt block other requests
-
- def setUp(self):
- from waitress.tests.fixtureapps import sleepy
- self.start_subprocess(sleepy.app)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_it(self):
- getline = os.path.join(here, 'fixtureapps', 'getline.py')
- cmds = (
- [self.exe, getline, 'http://%s:%d/sleepy' % self.bound_to],
- [self.exe, getline, 'http://%s:%d/' % self.bound_to]
- )
- r, w = os.pipe()
- procs = []
- for cmd in cmds:
- procs.append(subprocess.Popen(cmd, stdout=w))
- time.sleep(3)
- for proc in procs:
- if proc.returncode is not None: # pragma: no cover
- proc.terminate()
- # the notsleepy response should always be first returned (it sleeps
- # for 2 seconds, then returns; the notsleepy response should be
- # processed in the meantime)
- result = os.read(r, 10000)
- os.close(r)
- os.close(w)
- self.assertEqual(result, b'notsleepy returnedsleepy returned')
-
-class EchoTests(object):
-
- def setUp(self):
- from waitress.tests.fixtureapps import echo
- self.start_subprocess(echo.app)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_date_and_server(self):
- to_send = ("GET / HTTP/1.0\n"
- "Content-Length: 0\n\n")
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(headers.get('server'), 'waitress')
- self.assertTrue(headers.get('date'))
-
- def test_bad_host_header(self):
- # http://corte.si/posts/code/pathod/pythonservers/index.html
- to_send = ("GET / HTTP/1.0\n"
- " Host: 0\n\n")
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '400', 'Bad Request', 'HTTP/1.0')
- self.assertEqual(headers.get('server'), 'waitress')
- self.assertTrue(headers.get('date'))
-
- def test_send_with_body(self):
- to_send = ("GET / HTTP/1.0\n"
- "Content-Length: 5\n\n")
- to_send += 'hello'
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(headers.get('content-length'), '5')
- self.assertEqual(response_body, b'hello')
-
- def test_send_empty_body(self):
- to_send = ("GET / HTTP/1.0\n"
- "Content-Length: 0\n\n")
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(headers.get('content-length'), '0')
- self.assertEqual(response_body, b'')
-
- def test_multiple_requests_with_body(self):
- for x in range(3):
- self.sock = self.create_socket()
- self.test_send_with_body()
- self.sock.close()
-
- def test_multiple_requests_without_body(self):
- for x in range(3):
- self.sock = self.create_socket()
- self.test_send_empty_body()
- self.sock.close()
-
- def test_without_crlf(self):
- data = "Echo\nthis\r\nplease"
- s = tobytes(
- "GET / HTTP/1.0\n"
- "Connection: close\n"
- "Content-Length: %d\n"
- "\n"
- "%s" % (len(data), data)
- )
- self.connect()
- self.sock.send(s)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(int(headers['content-length']), len(data))
- self.assertEqual(len(response_body), len(data))
- self.assertEqual(response_body, tobytes(data))
-
- def test_large_body(self):
- # 1024 characters.
- body = 'This string has 32 characters.\r\n' * 32
- s = tobytes(
- "GET / HTTP/1.0\n"
- "Content-Length: %d\n"
- "\n"
- "%s" % (len(body), body)
- )
- self.connect()
- self.sock.send(s)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(headers.get('content-length'), '1024')
- self.assertEqual(response_body, tobytes(body))
-
- def test_many_clients(self):
- conns = []
- for n in range(50):
- h = self.make_http_connection()
- h.request("GET", "/", headers={"Accept": "text/plain"})
- conns.append(h)
- responses = []
- for h in conns:
- response = h.getresponse()
- self.assertEqual(response.status, 200)
- responses.append(response)
- for response in responses:
- response.read()
-
- def test_chunking_request_without_content(self):
- header = tobytes(
- "GET / HTTP/1.1\n"
- "Transfer-Encoding: chunked\n\n"
- )
- self.connect()
- self.sock.send(header)
- self.sock.send(b"0\r\n\r\n")
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- self.assertEqual(response_body, b'')
- self.assertEqual(headers['content-length'], '0')
- self.assertFalse('transfer-encoding' in headers)
-
- def test_chunking_request_with_content(self):
- control_line = b"20;\r\n" # 20 hex = 32 dec
- s = b'This string has 32 characters.\r\n'
- expected = s * 12
- header = tobytes(
- "GET / HTTP/1.1\n"
- "Transfer-Encoding: chunked\n\n"
- )
- self.connect()
- self.sock.send(header)
- fp = self.sock.makefile('rb', 0)
- for n in range(12):
- self.sock.send(control_line)
- self.sock.send(s)
- self.sock.send(b"0\r\n\r\n")
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- self.assertEqual(response_body, expected)
- self.assertEqual(headers['content-length'], str(len(expected)))
- self.assertFalse('transfer-encoding' in headers)
-
- def test_broken_chunked_encoding(self):
- control_line = "20;\r\n" # 20 hex = 32 dec
- s = 'This string has 32 characters.\r\n'
- to_send = "GET / HTTP/1.1\nTransfer-Encoding: chunked\n\n"
- to_send += (control_line + s)
- # garbage in input
- to_send += "GET / HTTP/1.1\nTransfer-Encoding: chunked\n\n"
- to_send += (control_line + s)
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- # receiver caught garbage and turned it into a 400
- self.assertline(line, '400', 'Bad Request', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- self.assertEqual(sorted(headers.keys()),
- ['content-length', 'content-type', 'date', 'server'])
- self.assertEqual(headers['content-type'], 'text/plain')
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_keepalive_http_10(self):
- # Handling of Keep-Alive within HTTP 1.0
- data = "Default: Don't keep me alive"
- s = tobytes(
- "GET / HTTP/1.0\n"
- "Content-Length: %d\n"
- "\n"
- "%s" % (len(data), data)
- )
- self.connect()
- self.sock.send(s)
- response = httplib.HTTPResponse(self.sock)
- response.begin()
- self.assertEqual(int(response.status), 200)
- connection = response.getheader('Connection', '')
- # We sent no Connection: Keep-Alive header
- # Connection: close (or no header) is default.
- self.assertTrue(connection != 'Keep-Alive')
-
- def test_keepalive_http10_explicit(self):
- # If header Connection: Keep-Alive is explicitly sent,
- # we want to keept the connection open, we also need to return
- # the corresponding header
- data = "Keep me alive"
- s = tobytes(
- "GET / HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: %d\n"
- "\n"
- "%s" % (len(data), data)
- )
- self.connect()
- self.sock.send(s)
- response = httplib.HTTPResponse(self.sock)
- response.begin()
- self.assertEqual(int(response.status), 200)
- connection = response.getheader('Connection', '')
- self.assertEqual(connection, 'Keep-Alive')
-
- def test_keepalive_http_11(self):
- # Handling of Keep-Alive within HTTP 1.1
-
- # All connections are kept alive, unless stated otherwise
- data = "Default: Keep me alive"
- s = tobytes(
- "GET / HTTP/1.1\n"
- "Content-Length: %d\n"
- "\n"
- "%s" % (len(data), data))
- self.connect()
- self.sock.send(s)
- response = httplib.HTTPResponse(self.sock)
- response.begin()
- self.assertEqual(int(response.status), 200)
- self.assertTrue(response.getheader('connection') != 'close')
-
- def test_keepalive_http11_explicit(self):
- # Explicitly set keep-alive
- data = "Default: Keep me alive"
- s = tobytes(
- "GET / HTTP/1.1\n"
- "Connection: keep-alive\n"
- "Content-Length: %d\n"
- "\n"
- "%s" % (len(data), data)
- )
- self.connect()
- self.sock.send(s)
- response = httplib.HTTPResponse(self.sock)
- response.begin()
- self.assertEqual(int(response.status), 200)
- self.assertTrue(response.getheader('connection') != 'close')
-
- def test_keepalive_http11_connclose(self):
- # specifying Connection: close explicitly
- data = "Don't keep me alive"
- s = tobytes(
- "GET / HTTP/1.1\n"
- "Connection: close\n"
- "Content-Length: %d\n"
- "\n"
- "%s" % (len(data), data)
- )
- self.connect()
- self.sock.send(s)
- response = httplib.HTTPResponse(self.sock)
- response.begin()
- self.assertEqual(int(response.status), 200)
- self.assertEqual(response.getheader('connection'), 'close')
-
-class PipeliningTests(object):
-
- def setUp(self):
- from waitress.tests.fixtureapps import echo
- self.start_subprocess(echo.app)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_pipelining(self):
- s = ("GET / HTTP/1.0\r\n"
- "Connection: %s\r\n"
- "Content-Length: %d\r\n"
- "\r\n"
- "%s")
- to_send = b''
- count = 25
- for n in range(count):
- body = "Response #%d\r\n" % (n + 1)
- if n + 1 < count:
- conn = 'keep-alive'
- else:
- conn = 'close'
- to_send += tobytes(s % (conn, len(body), body))
-
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- for n in range(count):
- expect_body = tobytes("Response #%d\r\n" % (n + 1))
- line = fp.readline() # status line
- version, status, reason = (x.strip() for x in line.split(None, 2))
- headers = parse_headers(fp)
- length = int(headers.get('content-length')) or None
- response_body = fp.read(length)
- self.assertEqual(int(status), 200)
- self.assertEqual(length, len(response_body))
- self.assertEqual(response_body, expect_body)
-
-class ExpectContinueTests(object):
-
- def setUp(self):
- from waitress.tests.fixtureapps import echo
- self.start_subprocess(echo.app)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_expect_continue(self):
- # specifying Connection: close explicitly
- data = "I have expectations"
- to_send = tobytes(
- "GET / HTTP/1.1\n"
- "Connection: close\n"
- "Content-Length: %d\n"
- "Expect: 100-continue\n"
- "\n"
- "%s" % (len(data), data)
- )
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line = fp.readline() # continue status line
- version, status, reason = (x.strip() for x in line.split(None, 2))
- self.assertEqual(int(status), 100)
- self.assertEqual(reason, b'Continue')
- self.assertEqual(version, b'HTTP/1.1')
- fp.readline() # blank line
- line = fp.readline() # next status line
- version, status, reason = (x.strip() for x in line.split(None, 2))
- headers = parse_headers(fp)
- length = int(headers.get('content-length')) or None
- response_body = fp.read(length)
- self.assertEqual(int(status), 200)
- self.assertEqual(length, len(response_body))
- self.assertEqual(response_body, tobytes(data))
-
-class BadContentLengthTests(object):
-
- def setUp(self):
- from waitress.tests.fixtureapps import badcl
- self.start_subprocess(badcl.app)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_short_body(self):
- # check to see if server closes connection when body is too short
- # for cl header
- to_send = tobytes(
- "GET /short_body HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: 0\n"
- "\n"
- )
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line = fp.readline() # status line
- version, status, reason = (x.strip() for x in line.split(None, 2))
- headers = parse_headers(fp)
- content_length = int(headers.get('content-length'))
- response_body = fp.read(content_length)
- self.assertEqual(int(status), 200)
- self.assertNotEqual(content_length, len(response_body))
- self.assertEqual(len(response_body), content_length - 1)
- self.assertEqual(response_body, tobytes('abcdefghi'))
- # remote closed connection (despite keepalive header); not sure why
- # first send succeeds
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_long_body(self):
- # check server doesnt close connection when body is too short
- # for cl header
- to_send = tobytes(
- "GET /long_body HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: 0\n"
- "\n"
- )
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line = fp.readline() # status line
- version, status, reason = (x.strip() for x in line.split(None, 2))
- headers = parse_headers(fp)
- content_length = int(headers.get('content-length')) or None
- response_body = fp.read(content_length)
- self.assertEqual(int(status), 200)
- self.assertEqual(content_length, len(response_body))
- self.assertEqual(response_body, tobytes('abcdefgh'))
- # remote does not close connection (keepalive header)
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line = fp.readline() # status line
- version, status, reason = (x.strip() for x in line.split(None, 2))
- headers = parse_headers(fp)
- content_length = int(headers.get('content-length')) or None
- response_body = fp.read(content_length)
- self.assertEqual(int(status), 200)
-
-class NoContentLengthTests(object):
-
- def setUp(self):
- from waitress.tests.fixtureapps import nocl
- self.start_subprocess(nocl.app)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_http10_generator(self):
- body = string.ascii_letters
- to_send = ("GET / HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: %d\n\n" % len(body))
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(headers.get('content-length'), None)
- self.assertEqual(headers.get('connection'), 'close')
- self.assertEqual(response_body, tobytes(body))
- # remote closed connection (despite keepalive header), because
- # generators cannot have a content-length divined
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_http10_list(self):
- body = string.ascii_letters
- to_send = ("GET /list HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: %d\n\n" % len(body))
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(headers['content-length'], str(len(body)))
- self.assertEqual(headers.get('connection'), 'Keep-Alive')
- self.assertEqual(response_body, tobytes(body))
- # remote keeps connection open because it divined the content length
- # from a length-1 list
- self.sock.send(to_send)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
-
- def test_http10_listlentwo(self):
- body = string.ascii_letters
- to_send = ("GET /list_lentwo HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: %d\n\n" % len(body))
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(headers.get('content-length'), None)
- self.assertEqual(headers.get('connection'), 'close')
- self.assertEqual(response_body, tobytes(body))
- # remote closed connection (despite keepalive header), because
- # lists of length > 1 cannot have their content length divined
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_http11_generator(self):
- body = string.ascii_letters
- to_send = ("GET / HTTP/1.1\n"
- "Content-Length: %s\n\n" % len(body))
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb')
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- expected = b''
- for chunk in chunks(body, 10):
- expected += tobytes(
- '%s\r\n%s\r\n' % (str(hex(len(chunk))[2:].upper()), chunk)
- )
- expected += b'0\r\n\r\n'
- self.assertEqual(response_body, expected)
- # connection is always closed at the end of a chunked response
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_http11_list(self):
- body = string.ascii_letters
- to_send = ("GET /list HTTP/1.1\n"
- "Content-Length: %d\n\n" % len(body))
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- self.assertEqual(headers['content-length'], str(len(body)))
- self.assertEqual(response_body, tobytes(body))
- # remote keeps connection open because it divined the content length
- # from a length-1 list
- self.sock.send(to_send)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
-
- def test_http11_listlentwo(self):
- body = string.ascii_letters
- to_send = ("GET /list_lentwo HTTP/1.1\n"
- "Content-Length: %s\n\n" % len(body))
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb')
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- expected = b''
- for chunk in (body[0], body[1:]):
- expected += tobytes(
- '%s\r\n%s\r\n' % (str(hex(len(chunk))[2:].upper()), chunk)
- )
- expected += b'0\r\n\r\n'
- self.assertEqual(response_body, expected)
- # connection is always closed at the end of a chunked response
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
-class WriteCallbackTests(object):
-
- def setUp(self):
- from waitress.tests.fixtureapps import writecb
- self.start_subprocess(writecb.app)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_short_body(self):
- # check to see if server closes connection when body is too short
- # for cl header
- to_send = tobytes(
- "GET /short_body HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: 0\n"
- "\n"
- )
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- # server trusts the content-length header (5)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, 9)
- self.assertNotEqual(cl, len(response_body))
- self.assertEqual(len(response_body), cl - 1)
- self.assertEqual(response_body, tobytes('abcdefgh'))
- # remote closed connection (despite keepalive header)
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_long_body(self):
- # check server doesnt close connection when body is too long
- # for cl header
- to_send = tobytes(
- "GET /long_body HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: 0\n"
- "\n"
- )
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- content_length = int(headers.get('content-length')) or None
- self.assertEqual(content_length, 9)
- self.assertEqual(content_length, len(response_body))
- self.assertEqual(response_body, tobytes('abcdefghi'))
- # remote does not close connection (keepalive header)
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
-
- def test_equal_body(self):
- # check server doesnt close connection when body is equal to
- # cl header
- to_send = tobytes(
- "GET /equal_body HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: 0\n"
- "\n"
- )
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- content_length = int(headers.get('content-length')) or None
- self.assertEqual(content_length, 9)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- self.assertEqual(content_length, len(response_body))
- self.assertEqual(response_body, tobytes('abcdefghi'))
- # remote does not close connection (keepalive header)
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
-
- def test_no_content_length(self):
- # wtf happens when there's no content-length
- to_send = tobytes(
- "GET /no_content_length HTTP/1.0\n"
- "Connection: Keep-Alive\n"
- "Content-Length: 0\n"
- "\n"
- )
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line = fp.readline() # status line
- line, headers, response_body = read_http(fp)
- content_length = headers.get('content-length')
- self.assertEqual(content_length, None)
- self.assertEqual(response_body, tobytes('abcdefghi'))
- # remote closed connection (despite keepalive header)
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
-class TooLargeTests(object):
-
- toobig = 1050
-
- def setUp(self):
- from waitress.tests.fixtureapps import toolarge
- self.start_subprocess(toolarge.app,
- max_request_header_size=1000,
- max_request_body_size=1000)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_request_body_too_large_with_wrong_cl_http10(self):
- body = 'a' * self.toobig
- to_send = ("GET / HTTP/1.0\n"
- "Content-Length: 5\n\n")
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb')
- # first request succeeds (content-length 5)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # server trusts the content-length header; no pipelining,
- # so request fulfilled, extra bytes are thrown away
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_request_body_too_large_with_wrong_cl_http10_keepalive(self):
- body = 'a' * self.toobig
- to_send = ("GET / HTTP/1.0\n"
- "Content-Length: 5\n"
- "Connection: Keep-Alive\n\n")
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb')
- # first request succeeds (content-length 5)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- line, headers, response_body = read_http(fp)
- self.assertline(line, '431', 'Request Header Fields Too Large',
- 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_request_body_too_large_with_no_cl_http10(self):
- body = 'a' * self.toobig
- to_send = "GET / HTTP/1.0\n\n"
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # extra bytes are thrown away (no pipelining), connection closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_request_body_too_large_with_no_cl_http10_keepalive(self):
- body = 'a' * self.toobig
- to_send = "GET / HTTP/1.0\nConnection: Keep-Alive\n\n"
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- # server trusts the content-length header (assumed zero)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- line, headers, response_body = read_http(fp)
- # next response overruns because the extra data appears to be
- # header data
- self.assertline(line, '431', 'Request Header Fields Too Large',
- 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_request_body_too_large_with_wrong_cl_http11(self):
- body = 'a' * self.toobig
- to_send = ("GET / HTTP/1.1\n"
- "Content-Length: 5\n\n")
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb')
- # first request succeeds (content-length 5)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # second response is an error response
- line, headers, response_body = read_http(fp)
- self.assertline(line, '431', 'Request Header Fields Too Large',
- 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_request_body_too_large_with_wrong_cl_http11_connclose(self):
- body = 'a' * self.toobig
- to_send = "GET / HTTP/1.1\nContent-Length: 5\nConnection: close\n\n"
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- # server trusts the content-length header (5)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_request_body_too_large_with_no_cl_http11(self):
- body = 'a' * self.toobig
- to_send = "GET / HTTP/1.1\n\n"
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb')
- # server trusts the content-length header (assumed 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # server assumes pipelined requests due to http/1.1, and the first
- # request was assumed c-l 0 because it had no content-length header,
- # so entire body looks like the header of the subsequent request
- # second response is an error response
- line, headers, response_body = read_http(fp)
- self.assertline(line, '431', 'Request Header Fields Too Large',
- 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_request_body_too_large_with_no_cl_http11_connclose(self):
- body = 'a' * self.toobig
- to_send = "GET / HTTP/1.1\nConnection: close\n\n"
- to_send += body
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- # server trusts the content-length header (assumed 0)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_request_body_too_large_chunked_encoding(self):
- control_line = "20;\r\n" # 20 hex = 32 dec
- s = 'This string has 32 characters.\r\n'
- to_send = "GET / HTTP/1.1\nTransfer-Encoding: chunked\n\n"
- repeat = control_line + s
- to_send += repeat * ((self.toobig // len(repeat)) + 1)
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- # body bytes counter caught a max_request_body_size overrun
- self.assertline(line, '413', 'Request Entity Too Large', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- self.assertEqual(headers['content-type'], 'text/plain')
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
-class InternalServerErrorTests(object):
-
- def setUp(self):
- from waitress.tests.fixtureapps import error
- self.start_subprocess(error.app, expose_tracebacks=True)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_before_start_response_http_10(self):
- to_send = "GET /before_start_response HTTP/1.0\n\n"
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '500', 'Internal Server Error', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- self.assertTrue(response_body.startswith(b'Internal Server Error'))
- self.assertEqual(headers['connection'], 'close')
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_before_start_response_http_11(self):
- to_send = "GET /before_start_response HTTP/1.1\n\n"
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '500', 'Internal Server Error', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- self.assertTrue(response_body.startswith(b'Internal Server Error'))
- self.assertEqual(sorted(headers.keys()),
- ['content-length', 'content-type', 'date', 'server'])
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_before_start_response_http_11_close(self):
- to_send = tobytes(
- "GET /before_start_response HTTP/1.1\n"
- "Connection: close\n\n")
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '500', 'Internal Server Error', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- self.assertTrue(response_body.startswith(b'Internal Server Error'))
- self.assertEqual(sorted(headers.keys()),
- ['connection', 'content-length', 'content-type', 'date',
- 'server'])
- self.assertEqual(headers['connection'], 'close')
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_after_start_response_http10(self):
- to_send = "GET /after_start_response HTTP/1.0\n\n"
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '500', 'Internal Server Error', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- self.assertTrue(response_body.startswith(b'Internal Server Error'))
- self.assertEqual(sorted(headers.keys()),
- ['connection', 'content-length', 'content-type', 'date',
- 'server'])
- self.assertEqual(headers['connection'], 'close')
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_after_start_response_http11(self):
- to_send = "GET /after_start_response HTTP/1.1\n\n"
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '500', 'Internal Server Error', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- self.assertTrue(response_body.startswith(b'Internal Server Error'))
- self.assertEqual(sorted(headers.keys()),
- ['content-length', 'content-type', 'date', 'server'])
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_after_start_response_http11_close(self):
- to_send = tobytes(
- "GET /after_start_response HTTP/1.1\n"
- "Connection: close\n\n")
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '500', 'Internal Server Error', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- self.assertTrue(response_body.startswith(b'Internal Server Error'))
- self.assertEqual(sorted(headers.keys()),
- ['connection', 'content-length', 'content-type', 'date',
- 'server'])
- self.assertEqual(headers['connection'], 'close')
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_after_write_cb(self):
- to_send = "GET /after_write_cb HTTP/1.1\n\n"
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- self.assertEqual(response_body, b'')
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_in_generator(self):
- to_send = "GET /in_generator HTTP/1.1\n\n"
- to_send = tobytes(to_send)
- self.connect()
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- self.assertEqual(response_body, b'')
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
-class FileWrapperTests(object):
-
- def setUp(self):
- from waitress.tests.fixtureapps import filewrapper
- self.start_subprocess(filewrapper.app)
-
- def tearDown(self):
- self.stop_subprocess()
-
- def test_filelike_http11(self):
- to_send = "GET /filelike HTTP/1.1\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- for t in range(0, 2):
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has not been closed
-
- def test_filelike_nocl_http11(self):
- to_send = "GET /filelike_nocl HTTP/1.1\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- for t in range(0, 2):
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has not been closed
-
- def test_filelike_shortcl_http11(self):
- to_send = "GET /filelike_shortcl HTTP/1.1\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- for t in range(0, 2):
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, 1)
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377' in response_body)
- # connection has not been closed
-
- def test_filelike_longcl_http11(self):
- to_send = "GET /filelike_longcl HTTP/1.1\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- for t in range(0, 2):
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has not been closed
-
- def test_notfilelike_http11(self):
- to_send = "GET /notfilelike HTTP/1.1\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- for t in range(0, 2):
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has not been closed
-
- def test_notfilelike_nocl_http11(self):
- to_send = "GET /notfilelike_nocl HTTP/1.1\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has been closed (no content-length)
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_notfilelike_shortcl_http11(self):
- to_send = "GET /notfilelike_shortcl HTTP/1.1\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- for t in range(0, 2):
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, 1)
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377' in response_body)
- # connection has not been closed
-
- def test_notfilelike_longcl_http11(self):
- to_send = "GET /notfilelike_longcl HTTP/1.1\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.1')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body) + 10)
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_filelike_http10(self):
- to_send = "GET /filelike HTTP/1.0\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_filelike_nocl_http10(self):
- to_send = "GET /filelike_nocl HTTP/1.0\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_notfilelike_http10(self):
- to_send = "GET /notfilelike HTTP/1.0\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- cl = int(headers['content-length'])
- self.assertEqual(cl, len(response_body))
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has been closed
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
- def test_notfilelike_nocl_http10(self):
- to_send = "GET /notfilelike_nocl HTTP/1.0\n\n"
- to_send = tobytes(to_send)
-
- self.connect()
-
- self.sock.send(to_send)
- fp = self.sock.makefile('rb', 0)
- line, headers, response_body = read_http(fp)
- self.assertline(line, '200', 'OK', 'HTTP/1.0')
- ct = headers['content-type']
- self.assertEqual(ct, 'image/jpeg')
- self.assertTrue(b'\377\330\377' in response_body)
- # connection has been closed (no content-length)
- self.send_check_error(to_send)
- self.assertRaises(ConnectionClosed, read_http, fp)
-
-class TcpEchoTests(EchoTests, TcpTests, unittest.TestCase):
- pass
-
-class TcpPipeliningTests(PipeliningTests, TcpTests, unittest.TestCase):
- pass
-
-class TcpExpectContinueTests(ExpectContinueTests, TcpTests, unittest.TestCase):
- pass
-
-class TcpBadContentLengthTests(
- BadContentLengthTests, TcpTests, unittest.TestCase):
- pass
-
-class TcpNoContentLengthTests(
- NoContentLengthTests, TcpTests, unittest.TestCase):
- pass
-
-class TcpWriteCallbackTests(WriteCallbackTests, TcpTests, unittest.TestCase):
- pass
-
-class TcpTooLargeTests(TooLargeTests, TcpTests, unittest.TestCase):
- pass
-
-class TcpInternalServerErrorTests(
- InternalServerErrorTests, TcpTests, unittest.TestCase):
- pass
-
-class TcpFileWrapperTests(FileWrapperTests, TcpTests, unittest.TestCase):
- pass
-
-if hasattr(socket, 'AF_UNIX'):
-
- class FixtureUnixWSGIServer(server.UnixWSGIServer):
- """A version of UnixWSGIServer that relays back what it's bound to.
- """
-
- family = socket.AF_UNIX # Testing
-
- def __init__(self, application, queue, **kw): # pragma: no cover
- # Coverage doesn't see this as it's ran in a separate process.
- # To permit parallel testing, use a PID-dependent socket.
- kw['unix_socket'] = '/tmp/waitress.test-%d.sock' % os.getpid()
- super(FixtureUnixWSGIServer, self).__init__(application, **kw)
- queue.put(self.socket.getsockname())
-
- class UnixTests(SubprocessTests):
-
- server = FixtureUnixWSGIServer
-
- def make_http_connection(self):
- return UnixHTTPConnection(self.bound_to)
-
- def stop_subprocess(self):
- super(UnixTests, self).stop_subprocess()
- cleanup_unix_socket(self.bound_to)
-
- def send_check_error(self, to_send):
- # Unlike inet domain sockets, Unix domain sockets can trigger a
- # 'Broken pipe' error when the socket it closed.
- try:
- self.sock.send(to_send)
- except socket.error as exc:
- self.assertEqual(get_errno(exc), errno.EPIPE)
-
- class UnixEchoTests(EchoTests, UnixTests, unittest.TestCase):
- pass
-
- class UnixPipeliningTests(PipeliningTests, UnixTests, unittest.TestCase):
- pass
-
- class UnixExpectContinueTests(
- ExpectContinueTests, UnixTests, unittest.TestCase):
- pass
-
- class UnixBadContentLengthTests(
- BadContentLengthTests, UnixTests, unittest.TestCase):
- pass
-
- class UnixNoContentLengthTests(
- NoContentLengthTests, UnixTests, unittest.TestCase):
- pass
-
- class UnixWriteCallbackTests(
- WriteCallbackTests, UnixTests, unittest.TestCase):
- pass
-
- class UnixTooLargeTests(TooLargeTests, UnixTests, unittest.TestCase):
- pass
-
- class UnixInternalServerErrorTests(
- InternalServerErrorTests, UnixTests, unittest.TestCase):
- pass
-
- class UnixFileWrapperTests(FileWrapperTests, UnixTests, unittest.TestCase):
- pass
-
-def parse_headers(fp):
- """Parses only RFC2822 headers from a file pointer.
- """
- headers = {}
- while True:
- line = fp.readline()
- if line in (b'\r\n', b'\n', b''):
- break
- line = line.decode('iso-8859-1')
- name, value = line.strip().split(':', 1)
- headers[name.lower().strip()] = value.lower().strip()
- return headers
-
-class UnixHTTPConnection(httplib.HTTPConnection):
- """Patched version of HTTPConnection that uses Unix domain sockets.
- """
-
- def __init__(self, path):
- httplib.HTTPConnection.__init__(self, 'localhost')
- self.path = path
-
- def connect(self):
- sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- sock.connect(self.path)
- self.sock = sock
-
-class ConnectionClosed(Exception):
- pass
-
-# stolen from gevent
-def read_http(fp): # pragma: no cover
- try:
- response_line = fp.readline()
- except socket.error as exc:
- fp.close()
- # errno 104 is ENOTRECOVERABLE, In WinSock 10054 is ECONNRESET
- if get_errno(exc) in (errno.ECONNABORTED, errno.ECONNRESET, 104, 10054):
- raise ConnectionClosed
- raise
- if not response_line:
- raise ConnectionClosed
-
- header_lines = []
- while True:
- line = fp.readline()
- if line in (b'\r\n', b'\n', b''):
- break
- else:
- header_lines.append(line)
- headers = dict()
- for x in header_lines:
- x = x.strip()
- if not x:
- continue
- key, value = x.split(b': ', 1)
- key = key.decode('iso-8859-1').lower()
- value = value.decode('iso-8859-1')
- assert key not in headers, "%s header duplicated" % key
- headers[key] = value
-
- if 'content-length' in headers:
- num = int(headers['content-length'])
- body = b''
- left = num
- while left > 0:
- data = fp.read(left)
- if not data:
- break
- body += data
- left -= len(data)
- else:
- # read until EOF
- body = fp.read()
-
- return response_line, headers, body
-
-# stolen from gevent
-def get_errno(exc): # pragma: no cover
- """ Get the error code out of socket.error objects.
- socket.error in <2.5 does not have errno attribute
- socket.error in 3.x does not allow indexing access
- e.args[0] works for all.
- There are cases when args[0] is not errno.
- i.e. http://bugs.python.org/issue6471
- Maybe there are cases when errno is set, but it is not the first argument?
- """
- try:
- if exc.errno is not None:
- return exc.errno
- except AttributeError:
- pass
- try:
- return exc.args[0]
- except IndexError:
- return None
-
-def chunks(l, n):
- """ Yield successive n-sized chunks from l.
- """
- for i in range(0, len(l), n):
- yield l[i:i + n]
diff --git a/libs/waitress/tests/test_init.py b/libs/waitress/tests/test_init.py
deleted file mode 100644
index 66c34ce8e..000000000
--- a/libs/waitress/tests/test_init.py
+++ /dev/null
@@ -1,47 +0,0 @@
-import unittest
-
-class Test_serve(unittest.TestCase):
-
- def _callFUT(self, app, **kw):
- from waitress import serve
- return serve(app, **kw)
-
- def test_it(self):
- server = DummyServerFactory()
- app = object()
- result = self._callFUT(app, _server=server, _quiet=True)
- self.assertEqual(server.app, app)
- self.assertEqual(result, None)
- self.assertEqual(server.ran, True)
-
-class Test_serve_paste(unittest.TestCase):
-
- def _callFUT(self, app, **kw):
- from waitress import serve_paste
- return serve_paste(app, None, **kw)
-
- def test_it(self):
- server = DummyServerFactory()
- app = object()
- result = self._callFUT(app, _server=server, _quiet=True)
- self.assertEqual(server.app, app)
- self.assertEqual(result, 0)
- self.assertEqual(server.ran, True)
-
-class DummyServerFactory(object):
- ran = False
-
- def __call__(self, app, **kw):
- self.adj = DummyAdj(kw)
- self.app = app
- self.kw = kw
- return self
-
- def run(self):
- self.ran = True
-
-class DummyAdj(object):
- verbose = False
-
- def __init__(self, kw):
- self.__dict__.update(kw)
diff --git a/libs/waitress/tests/test_parser.py b/libs/waitress/tests/test_parser.py
deleted file mode 100644
index ecb66060a..000000000
--- a/libs/waitress/tests/test_parser.py
+++ /dev/null
@@ -1,452 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""HTTP Request Parser tests
-"""
-import unittest
-
-from waitress.compat import (
- text_,
- tobytes,
-)
-
-class TestHTTPRequestParser(unittest.TestCase):
-
- def setUp(self):
- from waitress.parser import HTTPRequestParser
- from waitress.adjustments import Adjustments
- my_adj = Adjustments()
- self.parser = HTTPRequestParser(my_adj)
-
- def test_get_body_stream_None(self):
- self.parser.body_recv = None
- result = self.parser.get_body_stream()
- self.assertEqual(result.getvalue(), b'')
-
- def test_get_body_stream_nonNone(self):
- body_rcv = DummyBodyStream()
- self.parser.body_rcv = body_rcv
- result = self.parser.get_body_stream()
- self.assertEqual(result, body_rcv)
-
- def test_received_nonsense_with_double_cr(self):
- data = b"""\
-HTTP/1.0 GET /foobar
-
-
-"""
- result = self.parser.received(data)
- self.assertEqual(result, 22)
- self.assertTrue(self.parser.completed)
- self.assertEqual(self.parser.headers, {})
-
- def test_received_bad_host_header(self):
- from waitress.utilities import BadRequest
- data = b"""\
-HTTP/1.0 GET /foobar
- Host: foo
-
-
-"""
- result = self.parser.received(data)
- self.assertEqual(result, 33)
- self.assertTrue(self.parser.completed)
- self.assertEqual(self.parser.error.__class__, BadRequest)
-
- def test_received_nonsense_nothing(self):
- data = b"""\
-
-
-"""
- result = self.parser.received(data)
- self.assertEqual(result, 2)
- self.assertTrue(self.parser.completed)
- self.assertEqual(self.parser.headers, {})
-
- def test_received_no_doublecr(self):
- data = b"""\
-GET /foobar HTTP/8.4
-"""
- result = self.parser.received(data)
- self.assertEqual(result, 21)
- self.assertFalse(self.parser.completed)
- self.assertEqual(self.parser.headers, {})
-
- def test_received_already_completed(self):
- self.parser.completed = True
- result = self.parser.received(b'a')
- self.assertEqual(result, 0)
-
- def test_received_cl_too_large(self):
- from waitress.utilities import RequestEntityTooLarge
- self.parser.adj.max_request_body_size = 2
- data = b"""\
-GET /foobar HTTP/8.4
-Content-Length: 10
-
-"""
- result = self.parser.received(data)
- self.assertEqual(result, 41)
- self.assertTrue(self.parser.completed)
- self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge))
-
- def test_received_headers_too_large(self):
- from waitress.utilities import RequestHeaderFieldsTooLarge
- self.parser.adj.max_request_header_size = 2
- data = b"""\
-GET /foobar HTTP/8.4
-X-Foo: 1
-"""
- result = self.parser.received(data)
- self.assertEqual(result, 30)
- self.assertTrue(self.parser.completed)
- self.assertTrue(isinstance(self.parser.error,
- RequestHeaderFieldsTooLarge))
-
- def test_received_body_too_large(self):
- from waitress.utilities import RequestEntityTooLarge
- self.parser.adj.max_request_body_size = 2
- data = b"""\
-GET /foobar HTTP/1.1
-Transfer-Encoding: chunked
-X-Foo: 1
-
-20;\r\n
-This string has 32 characters\r\n
-0\r\n\r\n"""
- result = self.parser.received(data)
- self.assertEqual(result, 58)
- self.parser.received(data[result:])
- self.assertTrue(self.parser.completed)
- self.assertTrue(isinstance(self.parser.error,
- RequestEntityTooLarge))
-
- def test_received_error_from_parser(self):
- from waitress.utilities import BadRequest
- data = b"""\
-GET /foobar HTTP/1.1
-Transfer-Encoding: chunked
-X-Foo: 1
-
-garbage
-"""
- # header
- result = self.parser.received(data)
- # body
- result = self.parser.received(data[result:])
- self.assertEqual(result, 8)
- self.assertTrue(self.parser.completed)
- self.assertTrue(isinstance(self.parser.error,
- BadRequest))
-
- def test_received_chunked_completed_sets_content_length(self):
- data = b"""\
-GET /foobar HTTP/1.1
-Transfer-Encoding: chunked
-X-Foo: 1
-
-20;\r\n
-This string has 32 characters\r\n
-0\r\n\r\n"""
- result = self.parser.received(data)
- self.assertEqual(result, 58)
- data = data[result:]
- result = self.parser.received(data)
- self.assertTrue(self.parser.completed)
- self.assertTrue(self.parser.error is None)
- self.assertEqual(self.parser.headers['CONTENT_LENGTH'], '32')
-
- def test_parse_header_gardenpath(self):
- data = b"""\
-GET /foobar HTTP/8.4
-foo: bar"""
- self.parser.parse_header(data)
- self.assertEqual(self.parser.first_line, b'GET /foobar HTTP/8.4')
- self.assertEqual(self.parser.headers['FOO'], 'bar')
-
- def test_parse_header_no_cr_in_headerplus(self):
- data = b"GET /foobar HTTP/8.4"
- self.parser.parse_header(data)
- self.assertEqual(self.parser.first_line, data)
-
- def test_parse_header_bad_content_length(self):
- data = b"GET /foobar HTTP/8.4\ncontent-length: abc"
- self.parser.parse_header(data)
- self.assertEqual(self.parser.body_rcv, None)
-
- def test_parse_header_11_te_chunked(self):
- # NB: test that capitalization of header value is unimportant
- data = b"GET /foobar HTTP/1.1\ntransfer-encoding: ChUnKed"
- self.parser.parse_header(data)
- self.assertEqual(self.parser.body_rcv.__class__.__name__,
- 'ChunkedReceiver')
-
- def test_parse_header_11_expect_continue(self):
- data = b"GET /foobar HTTP/1.1\nexpect: 100-continue"
- self.parser.parse_header(data)
- self.assertEqual(self.parser.expect_continue, True)
-
- def test_parse_header_connection_close(self):
- data = b"GET /foobar HTTP/1.1\nConnection: close\n\n"
- self.parser.parse_header(data)
- self.assertEqual(self.parser.connection_close, True)
-
- def test_close_with_body_rcv(self):
- body_rcv = DummyBodyStream()
- self.parser.body_rcv = body_rcv
- self.parser.close()
- self.assertTrue(body_rcv.closed)
-
- def test_close_with_no_body_rcv(self):
- self.parser.body_rcv = None
- self.parser.close() # doesn't raise
-
-class Test_split_uri(unittest.TestCase):
-
- def _callFUT(self, uri):
- from waitress.parser import split_uri
- (self.proxy_scheme,
- self.proxy_netloc,
- self.path,
- self.query, self.fragment) = split_uri(uri)
-
- def test_split_uri_unquoting_unneeded(self):
- self._callFUT(b'http://localhost:8080/abc def')
- self.assertEqual(self.path, '/abc def')
-
- def test_split_uri_unquoting_needed(self):
- self._callFUT(b'http://localhost:8080/abc%20def')
- self.assertEqual(self.path, '/abc def')
-
- def test_split_url_with_query(self):
- self._callFUT(b'http://localhost:8080/abc?a=1&b=2')
- self.assertEqual(self.path, '/abc')
- self.assertEqual(self.query, 'a=1&b=2')
-
- def test_split_url_with_query_empty(self):
- self._callFUT(b'http://localhost:8080/abc?')
- self.assertEqual(self.path, '/abc')
- self.assertEqual(self.query, '')
-
- def test_split_url_with_fragment(self):
- self._callFUT(b'http://localhost:8080/#foo')
- self.assertEqual(self.path, '/')
- self.assertEqual(self.fragment, 'foo')
-
- def test_split_url_https(self):
- self._callFUT(b'https://localhost:8080/')
- self.assertEqual(self.path, '/')
- self.assertEqual(self.proxy_scheme, 'https')
- self.assertEqual(self.proxy_netloc, 'localhost:8080')
-
- def test_split_uri_unicode_error_raises_parsing_error(self):
- # See https://github.com/Pylons/waitress/issues/64
- from waitress.parser import ParsingError
- # Either pass or throw a ParsingError, just don't throw another type of
- # exception as that will cause the connection to close badly:
- try:
- self._callFUT(b'/\xd0')
- except ParsingError:
- pass
-
-class Test_get_header_lines(unittest.TestCase):
-
- def _callFUT(self, data):
- from waitress.parser import get_header_lines
- return get_header_lines(data)
-
- def test_get_header_lines(self):
- result = self._callFUT(b'slam\nslim')
- self.assertEqual(result, [b'slam', b'slim'])
-
- def test_get_header_lines_folded(self):
- # From RFC2616:
- # HTTP/1.1 header field values can be folded onto multiple lines if the
- # continuation line begins with a space or horizontal tab. All linear
- # white space, including folding, has the same semantics as SP. A
- # recipient MAY replace any linear white space with a single SP before
- # interpreting the field value or forwarding the message downstream.
-
- # We are just preserving the whitespace that indicates folding.
- result = self._callFUT(b'slim\n slam')
- self.assertEqual(result, [b'slim slam'])
-
- def test_get_header_lines_tabbed(self):
- result = self._callFUT(b'slam\n\tslim')
- self.assertEqual(result, [b'slam\tslim'])
-
- def test_get_header_lines_malformed(self):
- # http://corte.si/posts/code/pathod/pythonservers/index.html
- from waitress.parser import ParsingError
- self.assertRaises(ParsingError,
- self._callFUT, b' Host: localhost\r\n\r\n')
-
-class Test_crack_first_line(unittest.TestCase):
-
- def _callFUT(self, line):
- from waitress.parser import crack_first_line
- return crack_first_line(line)
-
- def test_crack_first_line_matchok(self):
- result = self._callFUT(b'GET / HTTP/1.0')
- self.assertEqual(result, (b'GET', b'/', b'1.0'))
-
- def test_crack_first_line_lowercase_method(self):
- from waitress.parser import ParsingError
- self.assertRaises(ParsingError, self._callFUT, b'get / HTTP/1.0')
-
- def test_crack_first_line_nomatch(self):
- result = self._callFUT(b'GET / bleh')
- self.assertEqual(result, (b'', b'', b''))
-
- def test_crack_first_line_missing_version(self):
- result = self._callFUT(b'GET /')
- self.assertEqual(result, (b'GET', b'/', None))
-
-class TestHTTPRequestParserIntegration(unittest.TestCase):
-
- def setUp(self):
- from waitress.parser import HTTPRequestParser
- from waitress.adjustments import Adjustments
- my_adj = Adjustments()
- self.parser = HTTPRequestParser(my_adj)
-
- def feed(self, data):
- parser = self.parser
- for n in range(100): # make sure we never loop forever
- consumed = parser.received(data)
- data = data[consumed:]
- if parser.completed:
- return
- raise ValueError('Looping') # pragma: no cover
-
- def testSimpleGET(self):
- data = b"""\
-GET /foobar HTTP/8.4
-FirstName: mickey
-lastname: Mouse
-content-length: 7
-
-Hello.
-"""
- parser = self.parser
- self.feed(data)
- self.assertTrue(parser.completed)
- self.assertEqual(parser.version, '8.4')
- self.assertFalse(parser.empty)
- self.assertEqual(parser.headers,
- {'FIRSTNAME': 'mickey',
- 'LASTNAME': 'Mouse',
- 'CONTENT_LENGTH': '7',
- })
- self.assertEqual(parser.path, '/foobar')
- self.assertEqual(parser.command, 'GET')
- self.assertEqual(parser.query, '')
- self.assertEqual(parser.proxy_scheme, '')
- self.assertEqual(parser.proxy_netloc, '')
- self.assertEqual(parser.get_body_stream().getvalue(), b'Hello.\n')
-
- def testComplexGET(self):
- data = b"""\
-GET /foo/a+%2B%2F%C3%A4%3D%26a%3Aint?d=b+%2B%2F%3D%26b%3Aint&c+%2B%2F%3D%26c%3Aint=6 HTTP/8.4
-FirstName: mickey
-lastname: Mouse
-content-length: 10
-
-Hello mickey.
-"""
- parser = self.parser
- self.feed(data)
- self.assertEqual(parser.command, 'GET')
- self.assertEqual(parser.version, '8.4')
- self.assertFalse(parser.empty)
- self.assertEqual(parser.headers,
- {'FIRSTNAME': 'mickey',
- 'LASTNAME': 'Mouse',
- 'CONTENT_LENGTH': '10',
- })
- # path should be utf-8 encoded
- self.assertEqual(tobytes(parser.path).decode('utf-8'),
- text_(b'/foo/a++/\xc3\xa4=&a:int', 'utf-8'))
- self.assertEqual(parser.query,
- 'd=b+%2B%2F%3D%26b%3Aint&c+%2B%2F%3D%26c%3Aint=6')
- self.assertEqual(parser.get_body_stream().getvalue(), b'Hello mick')
-
- def testProxyGET(self):
- data = b"""\
-GET https://example.com:8080/foobar HTTP/8.4
-content-length: 7
-
-Hello.
-"""
- parser = self.parser
- self.feed(data)
- self.assertTrue(parser.completed)
- self.assertEqual(parser.version, '8.4')
- self.assertFalse(parser.empty)
- self.assertEqual(parser.headers,
- {'CONTENT_LENGTH': '7',
- })
- self.assertEqual(parser.path, '/foobar')
- self.assertEqual(parser.command, 'GET')
- self.assertEqual(parser.proxy_scheme, 'https')
- self.assertEqual(parser.proxy_netloc, 'example.com:8080')
- self.assertEqual(parser.command, 'GET')
- self.assertEqual(parser.query, '')
- self.assertEqual(parser.get_body_stream().getvalue(), b'Hello.\n')
-
- def testDuplicateHeaders(self):
- # Ensure that headers with the same key get concatenated as per
- # RFC2616.
- data = b"""\
-GET /foobar HTTP/8.4
-x-forwarded-for: 10.11.12.13
-x-forwarded-for: unknown,127.0.0.1
-X-Forwarded_for: 255.255.255.255
-content-length: 7
-
-Hello.
-"""
- self.feed(data)
- self.assertTrue(self.parser.completed)
- self.assertEqual(self.parser.headers, {
- 'CONTENT_LENGTH': '7',
- 'X_FORWARDED_FOR':
- '10.11.12.13, unknown,127.0.0.1',
- })
-
- def testSpoofedHeadersDropped(self):
- data = b"""\
-GET /foobar HTTP/8.4
-x-auth_user: bob
-content-length: 7
-
-Hello.
-"""
- self.feed(data)
- self.assertTrue(self.parser.completed)
- self.assertEqual(self.parser.headers, {
- 'CONTENT_LENGTH': '7',
- })
-
-
-class DummyBodyStream(object):
-
- def getfile(self):
- return self
-
- def getbuf(self):
- return self
-
- def close(self):
- self.closed = True
diff --git a/libs/waitress/tests/test_receiver.py b/libs/waitress/tests/test_receiver.py
deleted file mode 100644
index 707f3284f..000000000
--- a/libs/waitress/tests/test_receiver.py
+++ /dev/null
@@ -1,169 +0,0 @@
-import unittest
-
-class TestFixedStreamReceiver(unittest.TestCase):
-
- def _makeOne(self, cl, buf):
- from waitress.receiver import FixedStreamReceiver
- return FixedStreamReceiver(cl, buf)
-
- def test_received_remain_lt_1(self):
- buf = DummyBuffer()
- inst = self._makeOne(0, buf)
- result = inst.received('a')
- self.assertEqual(result, 0)
- self.assertEqual(inst.completed, True)
-
- def test_received_remain_lte_datalen(self):
- buf = DummyBuffer()
- inst = self._makeOne(1, buf)
- result = inst.received('aa')
- self.assertEqual(result, 1)
- self.assertEqual(inst.completed, True)
- self.assertEqual(inst.completed, 1)
- self.assertEqual(inst.remain, 0)
- self.assertEqual(buf.data, ['a'])
-
- def test_received_remain_gt_datalen(self):
- buf = DummyBuffer()
- inst = self._makeOne(10, buf)
- result = inst.received('aa')
- self.assertEqual(result, 2)
- self.assertEqual(inst.completed, False)
- self.assertEqual(inst.remain, 8)
- self.assertEqual(buf.data, ['aa'])
-
- def test_getfile(self):
- buf = DummyBuffer()
- inst = self._makeOne(10, buf)
- self.assertEqual(inst.getfile(), buf)
-
- def test_getbuf(self):
- buf = DummyBuffer()
- inst = self._makeOne(10, buf)
- self.assertEqual(inst.getbuf(), buf)
-
- def test___len__(self):
- buf = DummyBuffer(['1', '2'])
- inst = self._makeOne(10, buf)
- self.assertEqual(inst.__len__(), 2)
-
-class TestChunkedReceiver(unittest.TestCase):
-
- def _makeOne(self, buf):
- from waitress.receiver import ChunkedReceiver
- return ChunkedReceiver(buf)
-
- def test_alreadycompleted(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- inst.completed = True
- result = inst.received(b'a')
- self.assertEqual(result, 0)
- self.assertEqual(inst.completed, True)
-
- def test_received_remain_gt_zero(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- inst.chunk_remainder = 100
- result = inst.received(b'a')
- self.assertEqual(inst.chunk_remainder, 99)
- self.assertEqual(result, 1)
- self.assertEqual(inst.completed, False)
-
- def test_received_control_line_notfinished(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- result = inst.received(b'a')
- self.assertEqual(inst.control_line, b'a')
- self.assertEqual(result, 1)
- self.assertEqual(inst.completed, False)
-
- def test_received_control_line_finished_garbage_in_input(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- result = inst.received(b'garbage\n')
- self.assertEqual(result, 8)
- self.assertTrue(inst.error)
-
- def test_received_control_line_finished_all_chunks_not_received(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- result = inst.received(b'a;discard\n')
- self.assertEqual(inst.control_line, b'')
- self.assertEqual(inst.chunk_remainder, 10)
- self.assertEqual(inst.all_chunks_received, False)
- self.assertEqual(result, 10)
- self.assertEqual(inst.completed, False)
-
- def test_received_control_line_finished_all_chunks_received(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- result = inst.received(b'0;discard\n')
- self.assertEqual(inst.control_line, b'')
- self.assertEqual(inst.all_chunks_received, True)
- self.assertEqual(result, 10)
- self.assertEqual(inst.completed, False)
-
- def test_received_trailer_startswith_crlf(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- inst.all_chunks_received = True
- result = inst.received(b'\r\n')
- self.assertEqual(result, 2)
- self.assertEqual(inst.completed, True)
-
- def test_received_trailer_startswith_lf(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- inst.all_chunks_received = True
- result = inst.received(b'\n')
- self.assertEqual(result, 1)
- self.assertEqual(inst.completed, True)
-
- def test_received_trailer_not_finished(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- inst.all_chunks_received = True
- result = inst.received(b'a')
- self.assertEqual(result, 1)
- self.assertEqual(inst.completed, False)
-
- def test_received_trailer_finished(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- inst.all_chunks_received = True
- result = inst.received(b'abc\r\n\r\n')
- self.assertEqual(inst.trailer, b'abc\r\n\r\n')
- self.assertEqual(result, 7)
- self.assertEqual(inst.completed, True)
-
- def test_getfile(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- self.assertEqual(inst.getfile(), buf)
-
- def test_getbuf(self):
- buf = DummyBuffer()
- inst = self._makeOne(buf)
- self.assertEqual(inst.getbuf(), buf)
-
- def test___len__(self):
- buf = DummyBuffer(['1', '2'])
- inst = self._makeOne(buf)
- self.assertEqual(inst.__len__(), 2)
-
-class DummyBuffer(object):
-
- def __init__(self, data=None):
- if data is None:
- data = []
- self.data = data
-
- def append(self, s):
- self.data.append(s)
-
- def getfile(self):
- return self
-
- def __len__(self):
- return len(self.data)
diff --git a/libs/waitress/tests/test_regression.py b/libs/waitress/tests/test_regression.py
deleted file mode 100644
index f43895e16..000000000
--- a/libs/waitress/tests/test_regression.py
+++ /dev/null
@@ -1,144 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2005 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Tests for waitress.channel maintenance logic
-"""
-import doctest
-
-class FakeSocket: # pragma: no cover
- data = ''
- setblocking = lambda *_: None
- close = lambda *_: None
-
- def __init__(self, no):
- self.no = no
-
- def fileno(self):
- return self.no
-
- def getpeername(self):
- return ('localhost', self.no)
-
- def send(self, data):
- self.data += data
- return len(data)
-
- def recv(self, data):
- return 'data'
-
-def zombies_test():
- """Regression test for HTTPChannel.maintenance method
-
- Bug: This method checks for channels that have been "inactive" for a
- configured time. The bug was that last_activity is set at creation time
- but never updated during async channel activity (reads and writes), so
- any channel older than the configured timeout will be closed when a new
- channel is created, regardless of activity.
-
- >>> import time
- >>> import waitress.adjustments
- >>> config = waitress.adjustments.Adjustments()
-
- >>> from waitress.server import HTTPServer
- >>> class TestServer(HTTPServer):
- ... def bind(self, (ip, port)):
- ... print "Listening on %s:%d" % (ip or '*', port)
- >>> sb = TestServer('127.0.0.1', 80, start=False, verbose=True)
- Listening on 127.0.0.1:80
-
- First we confirm the correct behavior, where a channel with no activity
- for the timeout duration gets closed.
-
- >>> from waitress.channel import HTTPChannel
- >>> socket = FakeSocket(42)
- >>> channel = HTTPChannel(sb, socket, ('localhost', 42))
-
- >>> channel.connected
- True
-
- >>> channel.last_activity -= int(config.channel_timeout) + 1
-
- >>> channel.next_channel_cleanup[0] = channel.creation_time - int(
- ... config.cleanup_interval) - 1
-
- >>> socket2 = FakeSocket(7)
- >>> channel2 = HTTPChannel(sb, socket2, ('localhost', 7))
-
- >>> channel.connected
- False
-
- Write Activity
- --------------
-
- Now we make sure that if there is activity the channel doesn't get closed
- incorrectly.
-
- >>> channel2.connected
- True
-
- >>> channel2.last_activity -= int(config.channel_timeout) + 1
-
- >>> channel2.handle_write()
-
- >>> channel2.next_channel_cleanup[0] = channel2.creation_time - int(
- ... config.cleanup_interval) - 1
-
- >>> socket3 = FakeSocket(3)
- >>> channel3 = HTTPChannel(sb, socket3, ('localhost', 3))
-
- >>> channel2.connected
- True
-
- Read Activity
- --------------
-
- We should test to see that read activity will update a channel as well.
-
- >>> channel3.connected
- True
-
- >>> channel3.last_activity -= int(config.channel_timeout) + 1
-
- >>> import waitress.parser
- >>> channel3.parser_class = (
- ... waitress.parser.HTTPRequestParser)
- >>> channel3.handle_read()
-
- >>> channel3.next_channel_cleanup[0] = channel3.creation_time - int(
- ... config.cleanup_interval) - 1
-
- >>> socket4 = FakeSocket(4)
- >>> channel4 = HTTPChannel(sb, socket4, ('localhost', 4))
-
- >>> channel3.connected
- True
-
- Main loop window
- ----------------
-
- There is also a corner case we'll do a shallow test for where a
- channel can be closed waiting for the main loop.
-
- >>> channel4.last_activity -= 1
-
- >>> last_active = channel4.last_activity
-
- >>> channel4.set_async()
-
- >>> channel4.last_activity != last_active
- True
-
-"""
-
-def test_suite():
- return doctest.DocTestSuite()
diff --git a/libs/waitress/tests/test_runner.py b/libs/waitress/tests/test_runner.py
deleted file mode 100644
index fa927f0a2..000000000
--- a/libs/waitress/tests/test_runner.py
+++ /dev/null
@@ -1,205 +0,0 @@
-import contextlib
-import os
-import sys
-
-if sys.version_info[:2] == (2, 6): # pragma: no cover
- import unittest2 as unittest
-else: # pragma: no cover
- import unittest
-
-from waitress import runner
-
-class Test_match(unittest.TestCase):
-
- def test_empty(self):
- self.assertRaisesRegexp(
- ValueError, "^Malformed application ''$",
- runner.match, '')
-
- def test_module_only(self):
- self.assertRaisesRegexp(
- ValueError, r"^Malformed application 'foo\.bar'$",
- runner.match, 'foo.bar')
-
- def test_bad_module(self):
- self.assertRaisesRegexp(
- ValueError,
- r"^Malformed application 'foo#bar:barney'$",
- runner.match, 'foo#bar:barney')
-
- def test_module_obj(self):
- self.assertTupleEqual(
- runner.match('foo.bar:fred.barney'),
- ('foo.bar', 'fred.barney'))
-
-class Test_resolve(unittest.TestCase):
-
- def test_bad_module(self):
- self.assertRaises(
- ImportError,
- runner.resolve, 'nonexistent', 'nonexistent_function')
-
- def test_nonexistent_function(self):
- self.assertRaisesRegexp(
- AttributeError,
- r"has no attribute 'nonexistent_function'",
- runner.resolve, 'os.path', 'nonexistent_function')
-
- def test_simple_happy_path(self):
- from os.path import exists
- self.assertIs(runner.resolve('os.path', 'exists'), exists)
-
- def test_complex_happy_path(self):
- # Ensure we can recursively resolve object attributes if necessary.
- self.assertEquals(
- runner.resolve('os.path', 'exists.__name__'),
- 'exists')
-
-class Test_run(unittest.TestCase):
-
- def match_output(self, argv, code, regex):
- argv = ['waitress-serve'] + argv
- with capture() as captured:
- self.assertEqual(runner.run(argv=argv), code)
- self.assertRegexpMatches(captured.getvalue(), regex)
- captured.close()
-
- def test_bad(self):
- self.match_output(
- ['--bad-opt'],
- 1,
- '^Error: option --bad-opt not recognized')
-
- def test_help(self):
- self.match_output(
- ['--help'],
- 0,
- "^Usage:\n\n waitress-serve")
-
- def test_no_app(self):
- self.match_output(
- [],
- 1,
- "^Error: Specify one application only")
-
- def test_multiple_apps_app(self):
- self.match_output(
- ['a:a', 'b:b'],
- 1,
- "^Error: Specify one application only")
-
- def test_bad_apps_app(self):
- self.match_output(
- ['a'],
- 1,
- "^Error: Malformed application 'a'")
-
- def test_bad_app_module(self):
- self.match_output(
- ['nonexistent:a'],
- 1,
- "^Error: Bad module 'nonexistent'")
-
- self.match_output(
- ['nonexistent:a'],
- 1,
- (
- r"There was an exception \((ImportError|ModuleNotFoundError)\) "
- "importing your module.\n\nIt had these arguments: \n"
- "1. No module named '?nonexistent'?"
- )
- )
-
- def test_cwd_added_to_path(self):
- def null_serve(app, **kw):
- pass
- sys_path = sys.path
- current_dir = os.getcwd()
- try:
- os.chdir(os.path.dirname(__file__))
- argv = [
- 'waitress-serve',
- 'fixtureapps.runner:app',
- ]
- self.assertEqual(runner.run(argv=argv, _serve=null_serve), 0)
- finally:
- sys.path = sys_path
- os.chdir(current_dir)
-
- def test_bad_app_object(self):
- self.match_output(
- ['waitress.tests.fixtureapps.runner:a'],
- 1,
- "^Error: Bad object name 'a'")
-
- def test_simple_call(self):
- import waitress.tests.fixtureapps.runner as _apps
- def check_server(app, **kw):
- self.assertIs(app, _apps.app)
- self.assertDictEqual(kw, {'port': '80'})
- argv = [
- 'waitress-serve',
- '--port=80',
- 'waitress.tests.fixtureapps.runner:app',
- ]
- self.assertEqual(runner.run(argv=argv, _serve=check_server), 0)
-
- def test_returned_app(self):
- import waitress.tests.fixtureapps.runner as _apps
- def check_server(app, **kw):
- self.assertIs(app, _apps.app)
- self.assertDictEqual(kw, {'port': '80'})
- argv = [
- 'waitress-serve',
- '--port=80',
- '--call',
- 'waitress.tests.fixtureapps.runner:returns_app',
- ]
- self.assertEqual(runner.run(argv=argv, _serve=check_server), 0)
-
-class Test_helper(unittest.TestCase):
-
- def test_exception_logging(self):
- from waitress.runner import show_exception
-
- regex = (
- r"There was an exception \(ImportError\) importing your module."
- r"\n\nIt had these arguments: \n1. My reason"
- )
-
- with capture() as captured:
- try:
- raise ImportError("My reason")
- except ImportError:
- self.assertEqual(show_exception(sys.stderr), None)
- self.assertRegexpMatches(
- captured.getvalue(),
- regex
- )
- captured.close()
-
- regex = (
- r"There was an exception \(ImportError\) importing your module."
- r"\n\nIt had no arguments."
- )
-
- with capture() as captured:
- try:
- raise ImportError
- except ImportError:
- self.assertEqual(show_exception(sys.stderr), None)
- self.assertRegexpMatches(
- captured.getvalue(),
- regex
- )
- captured.close()
-
-def capture():
- from waitress.compat import NativeIO
- fd = NativeIO()
- sys.stdout = fd
- sys.stderr = fd
- yield fd
- sys.stdout = sys.__stdout__
- sys.stderr = sys.__stderr__
diff --git a/libs/waitress/tests/test_server.py b/libs/waitress/tests/test_server.py
deleted file mode 100644
index 39b90b3d6..000000000
--- a/libs/waitress/tests/test_server.py
+++ /dev/null
@@ -1,367 +0,0 @@
-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
- return create_server(
- application,
- host=host,
- port=port,
- map=map,
- _dispatcher=_dispatcher,
- _start=_start,
- _sock=_sock)
-
- 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
- return create_server(
- app,
- listen=listen,
- map=map,
- _dispatcher=task_dispatcher,
- _start=_start,
- _sock=sock)
-
- def test_ctor_app_is_None(self):
- 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)
- result = inst.get_server_name('')
- self.assertTrue(result)
-
- 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.assertEqual(result, 'localhost')
-
- 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 = 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)
- inst = WSGIServer(None, _start=False, port=1234)
- # Ensure the adjustment was actually applied.
- self.assertNotEqual(Adjustments.port, 1234)
- self.assertEqual(inst.adj.port, 1234)
-
-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
- return create_server(
- dummy_app,
- map={},
- _start=_start,
- _sock=_sock,
- _dispatcher=DummyTaskDispatcher(),
- unix_socket=self.unix_socket,
- unix_socket_perms='600'
- )
-
- 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
- inst = UnixWSGIServer(
- dummy_app,
- unix_socket=self.unix_socket,
- unix_socket_perms='600'
- )
-
- self.assertEqual(inst.sockinfo[0], socket.AF_UNIX)
-
-class DummySock(object):
- accepted = False
- blocking = False
- family = socket.AF_INET
-
- def __init__(self, toraise=None, acceptresult=(None, None)):
- self.toraise = toraise
- self.acceptresult = acceptresult
- self.bound = None
- self.opts = []
-
- def bind(self, addr):
- 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
-
-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
-
-class DummyLogger(object):
-
- def __init__(self):
- self.logged = []
-
- def warning(self, msg, **kw):
- self.logged.append(msg)
diff --git a/libs/waitress/tests/test_task.py b/libs/waitress/tests/test_task.py
deleted file mode 100644
index 2a2759a23..000000000
--- a/libs/waitress/tests/test_task.py
+++ /dev/null
@@ -1,931 +0,0 @@
-import unittest
-import io
-
-class TestThreadedTaskDispatcher(unittest.TestCase):
-
- def _makeOne(self):
- from waitress.task import ThreadedTaskDispatcher
- return ThreadedTaskDispatcher()
-
- def test_handler_thread_task_is_None(self):
- inst = self._makeOne()
- inst.threads[0] = True
- inst.queue.put(None)
- inst.handler_thread(0)
- self.assertEqual(inst.stop_count, -1)
- self.assertEqual(inst.threads, {})
-
- def test_handler_thread_task_raises(self):
- from waitress.task import JustTesting
- inst = self._makeOne()
- inst.threads[0] = True
- inst.logger = DummyLogger()
- task = DummyTask(JustTesting)
- inst.logger = DummyLogger()
- inst.queue.put(task)
- inst.handler_thread(0)
- self.assertEqual(inst.stop_count, -1)
- self.assertEqual(inst.threads, {})
- self.assertEqual(len(inst.logger.logged), 1)
-
- def test_set_thread_count_increase(self):
- inst = self._makeOne()
- L = []
- inst.start_new_thread = lambda *x: L.append(x)
- inst.set_thread_count(1)
- self.assertEqual(L, [(inst.handler_thread, (0,))])
-
- def test_set_thread_count_increase_with_existing(self):
- inst = self._makeOne()
- L = []
- inst.threads = {0: 1}
- inst.start_new_thread = lambda *x: L.append(x)
- inst.set_thread_count(2)
- self.assertEqual(L, [(inst.handler_thread, (1,))])
-
- def test_set_thread_count_decrease(self):
- inst = self._makeOne()
- inst.threads = {'a': 1, 'b': 2}
- inst.set_thread_count(1)
- self.assertEqual(inst.queue.qsize(), 1)
- self.assertEqual(inst.queue.get(), None)
-
- def test_set_thread_count_same(self):
- inst = self._makeOne()
- L = []
- inst.start_new_thread = lambda *x: L.append(x)
- inst.threads = {0: 1}
- inst.set_thread_count(1)
- self.assertEqual(L, [])
-
- def test_add_task(self):
- task = DummyTask()
- inst = self._makeOne()
- inst.add_task(task)
- self.assertEqual(inst.queue.qsize(), 1)
- self.assertTrue(task.deferred)
-
- def test_add_task_defer_raises(self):
- task = DummyTask(ValueError)
- inst = self._makeOne()
- self.assertRaises(ValueError, inst.add_task, task)
- self.assertEqual(inst.queue.qsize(), 0)
- self.assertTrue(task.deferred)
- self.assertTrue(task.cancelled)
-
- def test_shutdown_one_thread(self):
- inst = self._makeOne()
- inst.threads[0] = 1
- inst.logger = DummyLogger()
- task = DummyTask()
- inst.queue.put(task)
- self.assertEqual(inst.shutdown(timeout=.01), True)
- self.assertEqual(inst.logger.logged, ['1 thread(s) still running'])
- self.assertEqual(task.cancelled, True)
-
- def test_shutdown_no_threads(self):
- inst = self._makeOne()
- self.assertEqual(inst.shutdown(timeout=.01), True)
-
- def test_shutdown_no_cancel_pending(self):
- inst = self._makeOne()
- self.assertEqual(inst.shutdown(cancel_pending=False, timeout=.01),
- False)
-
-class TestTask(unittest.TestCase):
-
- def _makeOne(self, channel=None, request=None):
- if channel is None:
- channel = DummyChannel()
- if request is None:
- request = DummyParser()
- from waitress.task import Task
- return Task(channel, request)
-
- def test_ctor_version_not_in_known(self):
- request = DummyParser()
- request.version = '8.4'
- inst = self._makeOne(request=request)
- self.assertEqual(inst.version, '1.0')
-
- def test_cancel(self):
- inst = self._makeOne()
- inst.cancel()
- self.assertTrue(inst.close_on_finish)
-
- def test_defer(self):
- inst = self._makeOne()
- self.assertEqual(inst.defer(), None)
-
- def test_build_response_header_bad_http_version(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.version = '8.4'
- self.assertRaises(AssertionError, inst.build_response_header)
-
- def test_build_response_header_v10_keepalive_no_content_length(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.request.headers['CONNECTION'] = 'keep-alive'
- inst.version = '1.0'
- result = inst.build_response_header()
- lines = filter_lines(result)
- self.assertEqual(len(lines), 4)
- self.assertEqual(lines[0], b'HTTP/1.0 200 OK')
- self.assertEqual(lines[1], b'Connection: close')
- self.assertTrue(lines[2].startswith(b'Date:'))
- self.assertEqual(lines[3], b'Server: waitress')
- self.assertEqual(inst.close_on_finish, True)
- self.assertTrue(('Connection', 'close') in inst.response_headers)
-
- def test_build_response_header_v10_keepalive_with_content_length(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.request.headers['CONNECTION'] = 'keep-alive'
- inst.response_headers = [('Content-Length', '10')]
- inst.version = '1.0'
- inst.content_length = 0
- result = inst.build_response_header()
- lines = filter_lines(result)
- self.assertEqual(len(lines), 5)
- self.assertEqual(lines[0], b'HTTP/1.0 200 OK')
- self.assertEqual(lines[1], b'Connection: Keep-Alive')
- self.assertEqual(lines[2], b'Content-Length: 10')
- self.assertTrue(lines[3].startswith(b'Date:'))
- self.assertEqual(lines[4], b'Server: waitress')
- self.assertEqual(inst.close_on_finish, False)
-
- def test_build_response_header_v11_connection_closed_by_client(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.version = '1.1'
- inst.request.headers['CONNECTION'] = 'close'
- result = inst.build_response_header()
- lines = filter_lines(result)
- self.assertEqual(len(lines), 5)
- self.assertEqual(lines[0], b'HTTP/1.1 200 OK')
- self.assertEqual(lines[1], b'Connection: close')
- self.assertTrue(lines[2].startswith(b'Date:'))
- self.assertEqual(lines[3], b'Server: waitress')
- self.assertEqual(lines[4], b'Transfer-Encoding: chunked')
- self.assertTrue(('Connection', 'close') in inst.response_headers)
- self.assertEqual(inst.close_on_finish, True)
-
- def test_build_response_header_v11_connection_keepalive_by_client(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.request.headers['CONNECTION'] = 'keep-alive'
- inst.version = '1.1'
- result = inst.build_response_header()
- lines = filter_lines(result)
- self.assertEqual(len(lines), 5)
- self.assertEqual(lines[0], b'HTTP/1.1 200 OK')
- self.assertEqual(lines[1], b'Connection: close')
- self.assertTrue(lines[2].startswith(b'Date:'))
- self.assertEqual(lines[3], b'Server: waitress')
- self.assertEqual(lines[4], b'Transfer-Encoding: chunked')
- self.assertTrue(('Connection', 'close') in inst.response_headers)
- self.assertEqual(inst.close_on_finish, True)
-
- def test_build_response_header_v11_200_no_content_length(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.version = '1.1'
- result = inst.build_response_header()
- lines = filter_lines(result)
- self.assertEqual(len(lines), 5)
- self.assertEqual(lines[0], b'HTTP/1.1 200 OK')
- self.assertEqual(lines[1], b'Connection: close')
- self.assertTrue(lines[2].startswith(b'Date:'))
- self.assertEqual(lines[3], b'Server: waitress')
- self.assertEqual(lines[4], b'Transfer-Encoding: chunked')
- self.assertEqual(inst.close_on_finish, True)
- self.assertTrue(('Connection', 'close') in inst.response_headers)
-
- def test_build_response_header_via_added(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.version = '1.0'
- inst.response_headers = [('Server', 'abc')]
- result = inst.build_response_header()
- lines = filter_lines(result)
- self.assertEqual(len(lines), 5)
- self.assertEqual(lines[0], b'HTTP/1.0 200 OK')
- self.assertEqual(lines[1], b'Connection: close')
- self.assertTrue(lines[2].startswith(b'Date:'))
- self.assertEqual(lines[3], b'Server: abc')
- self.assertEqual(lines[4], b'Via: waitress')
-
- def test_build_response_header_date_exists(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.version = '1.0'
- inst.response_headers = [('Date', 'date')]
- result = inst.build_response_header()
- lines = filter_lines(result)
- self.assertEqual(len(lines), 4)
- self.assertEqual(lines[0], b'HTTP/1.0 200 OK')
- self.assertEqual(lines[1], b'Connection: close')
- self.assertTrue(lines[2].startswith(b'Date:'))
- self.assertEqual(lines[3], b'Server: waitress')
-
- def test_build_response_header_preexisting_content_length(self):
- inst = self._makeOne()
- inst.request = DummyParser()
- inst.version = '1.1'
- inst.content_length = 100
- result = inst.build_response_header()
- lines = filter_lines(result)
- self.assertEqual(len(lines), 4)
- self.assertEqual(lines[0], b'HTTP/1.1 200 OK')
- self.assertEqual(lines[1], b'Content-Length: 100')
- self.assertTrue(lines[2].startswith(b'Date:'))
- self.assertEqual(lines[3], b'Server: waitress')
-
- def test_remove_content_length_header(self):
- inst = self._makeOne()
- inst.response_headers = [('Content-Length', '70')]
- inst.remove_content_length_header()
- self.assertEqual(inst.response_headers, [])
-
- def test_start(self):
- inst = self._makeOne()
- inst.start()
- self.assertTrue(inst.start_time)
-
- def test_finish_didnt_write_header(self):
- inst = self._makeOne()
- inst.wrote_header = False
- inst.complete = True
- inst.finish()
- self.assertTrue(inst.channel.written)
-
- def test_finish_wrote_header(self):
- inst = self._makeOne()
- inst.wrote_header = True
- inst.finish()
- self.assertFalse(inst.channel.written)
-
- def test_finish_chunked_response(self):
- inst = self._makeOne()
- inst.wrote_header = True
- inst.chunked_response = True
- inst.finish()
- self.assertEqual(inst.channel.written, b'0\r\n\r\n')
-
- def test_write_wrote_header(self):
- inst = self._makeOne()
- inst.wrote_header = True
- inst.complete = True
- inst.content_length = 3
- inst.write(b'abc')
- self.assertEqual(inst.channel.written, b'abc')
-
- def test_write_header_not_written(self):
- inst = self._makeOne()
- inst.wrote_header = False
- inst.complete = True
- inst.write(b'abc')
- self.assertTrue(inst.channel.written)
- self.assertEqual(inst.wrote_header, True)
-
- def test_write_start_response_uncalled(self):
- inst = self._makeOne()
- self.assertRaises(RuntimeError, inst.write, b'')
-
- def test_write_chunked_response(self):
- inst = self._makeOne()
- inst.wrote_header = True
- inst.chunked_response = True
- inst.complete = True
- inst.write(b'abc')
- self.assertEqual(inst.channel.written, b'3\r\nabc\r\n')
-
- def test_write_preexisting_content_length(self):
- inst = self._makeOne()
- inst.wrote_header = True
- inst.complete = True
- inst.content_length = 1
- inst.logger = DummyLogger()
- inst.write(b'abc')
- self.assertTrue(inst.channel.written)
- self.assertEqual(inst.logged_write_excess, True)
- self.assertEqual(len(inst.logger.logged), 1)
-
-class TestWSGITask(unittest.TestCase):
-
- def _makeOne(self, channel=None, request=None):
- if channel is None:
- channel = DummyChannel()
- if request is None:
- request = DummyParser()
- from waitress.task import WSGITask
- return WSGITask(channel, request)
-
- def test_service(self):
- inst = self._makeOne()
- def execute():
- inst.executed = True
- inst.execute = execute
- inst.complete = True
- inst.service()
- self.assertTrue(inst.start_time)
- self.assertTrue(inst.close_on_finish)
- self.assertTrue(inst.channel.written)
- self.assertEqual(inst.executed, True)
-
- def test_service_server_raises_socket_error(self):
- import socket
- inst = self._makeOne()
- def execute():
- raise socket.error
- inst.execute = execute
- self.assertRaises(socket.error, inst.service)
- self.assertTrue(inst.start_time)
- self.assertTrue(inst.close_on_finish)
- self.assertFalse(inst.channel.written)
-
- def test_execute_app_calls_start_response_twice_wo_exc_info(self):
- def app(environ, start_response):
- start_response('200 OK', [])
- start_response('200 OK', [])
- inst = self._makeOne()
- inst.channel.server.application = app
- self.assertRaises(AssertionError, inst.execute)
-
- def test_execute_app_calls_start_response_w_exc_info_complete(self):
- def app(environ, start_response):
- start_response('200 OK', [], [ValueError, ValueError(), None])
- return [b'a']
- inst = self._makeOne()
- inst.complete = True
- inst.channel.server.application = app
- inst.execute()
- self.assertTrue(inst.complete)
- self.assertEqual(inst.status, '200 OK')
- self.assertTrue(inst.channel.written)
-
- def test_execute_app_calls_start_response_w_excinf_headers_unwritten(self):
- def app(environ, start_response):
- start_response('200 OK', [], [ValueError, None, None])
- return [b'a']
- inst = self._makeOne()
- inst.wrote_header = False
- inst.channel.server.application = app
- inst.response_headers = [('a', 'b')]
- inst.execute()
- self.assertTrue(inst.complete)
- self.assertEqual(inst.status, '200 OK')
- self.assertTrue(inst.channel.written)
- self.assertFalse(('a','b') in inst.response_headers)
-
- def test_execute_app_calls_start_response_w_excinf_headers_written(self):
- def app(environ, start_response):
- start_response('200 OK', [], [ValueError, ValueError(), None])
- inst = self._makeOne()
- inst.complete = True
- inst.wrote_header = True
- inst.channel.server.application = app
- self.assertRaises(ValueError, inst.execute)
-
- def test_execute_bad_header_key(self):
- def app(environ, start_response):
- start_response('200 OK', [(None, 'a')])
- inst = self._makeOne()
- inst.channel.server.application = app
- self.assertRaises(AssertionError, inst.execute)
-
- def test_execute_bad_header_value(self):
- def app(environ, start_response):
- start_response('200 OK', [('a', None)])
- inst = self._makeOne()
- inst.channel.server.application = app
- self.assertRaises(AssertionError, inst.execute)
-
- def test_execute_hopbyhop_header(self):
- def app(environ, start_response):
- start_response('200 OK', [('Connection', 'close')])
- inst = self._makeOne()
- inst.channel.server.application = app
- self.assertRaises(AssertionError, inst.execute)
-
- def test_execute_bad_header_value_control_characters(self):
- def app(environ, start_response):
- start_response('200 OK', [('a', '\n')])
- inst = self._makeOne()
- inst.channel.server.application = app
- self.assertRaises(ValueError, inst.execute)
-
- def test_execute_bad_header_name_control_characters(self):
- def app(environ, start_response):
- start_response('200 OK', [('a\r', 'value')])
- inst = self._makeOne()
- inst.channel.server.application = app
- self.assertRaises(ValueError, inst.execute)
-
- def test_execute_bad_status_control_characters(self):
- def app(environ, start_response):
- start_response('200 OK\r', [])
- inst = self._makeOne()
- inst.channel.server.application = app
- self.assertRaises(ValueError, inst.execute)
-
- def test_preserve_header_value_order(self):
- def app(environ, start_response):
- write = start_response('200 OK', [('C', 'b'), ('A', 'b'), ('A', 'a')])
- write(b'abc')
- return []
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.execute()
- self.assertTrue(b'A: b\r\nA: a\r\nC: b\r\n' in inst.channel.written)
-
- def test_execute_bad_status_value(self):
- def app(environ, start_response):
- start_response(None, [])
- inst = self._makeOne()
- inst.channel.server.application = app
- self.assertRaises(AssertionError, inst.execute)
-
- def test_execute_with_content_length_header(self):
- def app(environ, start_response):
- start_response('200 OK', [('Content-Length', '1')])
- return [b'a']
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.execute()
- self.assertEqual(inst.content_length, 1)
-
- def test_execute_app_calls_write(self):
- def app(environ, start_response):
- write = start_response('200 OK', [('Content-Length', '3')])
- write(b'abc')
- return []
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.execute()
- self.assertEqual(inst.channel.written[-3:], b'abc')
-
- def test_execute_app_returns_len1_chunk_without_cl(self):
- def app(environ, start_response):
- start_response('200 OK', [])
- return [b'abc']
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.execute()
- self.assertEqual(inst.content_length, 3)
-
- def test_execute_app_returns_empty_chunk_as_first(self):
- def app(environ, start_response):
- start_response('200 OK', [])
- return ['', b'abc']
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.execute()
- self.assertEqual(inst.content_length, None)
-
- def test_execute_app_returns_too_many_bytes(self):
- def app(environ, start_response):
- start_response('200 OK', [('Content-Length', '1')])
- return [b'abc']
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.logger = DummyLogger()
- inst.execute()
- self.assertEqual(inst.close_on_finish, True)
- self.assertEqual(len(inst.logger.logged), 1)
-
- def test_execute_app_returns_too_few_bytes(self):
- def app(environ, start_response):
- start_response('200 OK', [('Content-Length', '3')])
- return [b'a']
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.logger = DummyLogger()
- inst.execute()
- self.assertEqual(inst.close_on_finish, True)
- self.assertEqual(len(inst.logger.logged), 1)
-
- def test_execute_app_do_not_warn_on_head(self):
- def app(environ, start_response):
- start_response('200 OK', [('Content-Length', '3')])
- return [b'']
- inst = self._makeOne()
- inst.request.command = 'HEAD'
- inst.channel.server.application = app
- inst.logger = DummyLogger()
- inst.execute()
- self.assertEqual(inst.close_on_finish, True)
- self.assertEqual(len(inst.logger.logged), 0)
-
- def test_execute_app_returns_closeable(self):
- class closeable(list):
- def close(self):
- self.closed = True
- foo = closeable([b'abc'])
- def app(environ, start_response):
- start_response('200 OK', [('Content-Length', '3')])
- return foo
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.execute()
- self.assertEqual(foo.closed, True)
-
- def test_execute_app_returns_filewrapper_prepare_returns_True(self):
- from waitress.buffers import ReadOnlyFileBasedBuffer
- f = io.BytesIO(b'abc')
- app_iter = ReadOnlyFileBasedBuffer(f, 8192)
- def app(environ, start_response):
- start_response('200 OK', [('Content-Length', '3')])
- return app_iter
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.execute()
- self.assertTrue(inst.channel.written) # header
- self.assertEqual(inst.channel.otherdata, [app_iter])
-
- def test_execute_app_returns_filewrapper_prepare_returns_True_nocl(self):
- from waitress.buffers import ReadOnlyFileBasedBuffer
- f = io.BytesIO(b'abc')
- app_iter = ReadOnlyFileBasedBuffer(f, 8192)
- def app(environ, start_response):
- start_response('200 OK', [])
- return app_iter
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.execute()
- self.assertTrue(inst.channel.written) # header
- self.assertEqual(inst.channel.otherdata, [app_iter])
- self.assertEqual(inst.content_length, 3)
-
- def test_execute_app_returns_filewrapper_prepare_returns_True_badcl(self):
- from waitress.buffers import ReadOnlyFileBasedBuffer
- f = io.BytesIO(b'abc')
- app_iter = ReadOnlyFileBasedBuffer(f, 8192)
- def app(environ, start_response):
- start_response('200 OK', [])
- return app_iter
- inst = self._makeOne()
- inst.channel.server.application = app
- inst.content_length = 10
- inst.response_headers = [('Content-Length', '10')]
- inst.execute()
- self.assertTrue(inst.channel.written) # header
- self.assertEqual(inst.channel.otherdata, [app_iter])
- self.assertEqual(inst.content_length, 3)
- self.assertEqual(dict(inst.response_headers)['Content-Length'], '3')
-
- def test_get_environment_already_cached(self):
- inst = self._makeOne()
- inst.environ = object()
- self.assertEqual(inst.get_environment(), inst.environ)
-
- def test_get_environment_path_startswith_more_than_one_slash(self):
- inst = self._makeOne()
- request = DummyParser()
- request.path = '///abc'
- inst.request = request
- environ = inst.get_environment()
- self.assertEqual(environ['PATH_INFO'], '/abc')
-
- def test_get_environment_path_empty(self):
- inst = self._makeOne()
- request = DummyParser()
- request.path = ''
- inst.request = request
- environ = inst.get_environment()
- self.assertEqual(environ['PATH_INFO'], '')
-
- def test_get_environment_no_query(self):
- inst = self._makeOne()
- request = DummyParser()
- inst.request = request
- environ = inst.get_environment()
- self.assertEqual(environ['QUERY_STRING'], '')
-
- def test_get_environment_with_query(self):
- inst = self._makeOne()
- request = DummyParser()
- request.query = 'abc'
- inst.request = request
- environ = inst.get_environment()
- self.assertEqual(environ['QUERY_STRING'], 'abc')
-
- def test_get_environ_with_url_prefix_miss(self):
- inst = self._makeOne()
- inst.channel.server.adj.url_prefix = '/foo'
- request = DummyParser()
- request.path = '/bar'
- inst.request = request
- environ = inst.get_environment()
- self.assertEqual(environ['PATH_INFO'], '/bar')
- self.assertEqual(environ['SCRIPT_NAME'], '/foo')
-
- def test_get_environ_with_url_prefix_hit(self):
- inst = self._makeOne()
- inst.channel.server.adj.url_prefix = '/foo'
- request = DummyParser()
- request.path = '/foo/fuz'
- inst.request = request
- environ = inst.get_environment()
- self.assertEqual(environ['PATH_INFO'], '/fuz')
- self.assertEqual(environ['SCRIPT_NAME'], '/foo')
-
- def test_get_environ_with_url_prefix_empty_path(self):
- inst = self._makeOne()
- inst.channel.server.adj.url_prefix = '/foo'
- request = DummyParser()
- request.path = '/foo'
- inst.request = request
- environ = inst.get_environment()
- self.assertEqual(environ['PATH_INFO'], '')
- self.assertEqual(environ['SCRIPT_NAME'], '/foo')
-
- def test_get_environment_values(self):
- import sys
- inst = self._makeOne()
- request = DummyParser()
- request.headers = {
- 'CONTENT_TYPE': 'abc',
- 'CONTENT_LENGTH': '10',
- 'X_FOO': 'BAR',
- 'CONNECTION': 'close',
- }
- request.query = 'abc'
- inst.request = request
- environ = inst.get_environment()
-
- # nail the keys of environ
- self.assertEqual(sorted(environ.keys()), [
- 'CONTENT_LENGTH', 'CONTENT_TYPE', 'HTTP_CONNECTION', 'HTTP_X_FOO',
- 'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'REQUEST_METHOD',
- 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_PORT', 'SERVER_PROTOCOL',
- 'SERVER_SOFTWARE', 'wsgi.errors', 'wsgi.file_wrapper', 'wsgi.input',
- 'wsgi.multiprocess', 'wsgi.multithread', 'wsgi.run_once',
- 'wsgi.url_scheme', 'wsgi.version'])
-
- self.assertEqual(environ['REQUEST_METHOD'], 'GET')
- self.assertEqual(environ['SERVER_PORT'], '80')
- self.assertEqual(environ['SERVER_NAME'], 'localhost')
- self.assertEqual(environ['SERVER_SOFTWARE'], 'waitress')
- self.assertEqual(environ['SERVER_PROTOCOL'], 'HTTP/1.0')
- self.assertEqual(environ['SCRIPT_NAME'], '')
- self.assertEqual(environ['HTTP_CONNECTION'], 'close')
- self.assertEqual(environ['PATH_INFO'], '/')
- self.assertEqual(environ['QUERY_STRING'], 'abc')
- self.assertEqual(environ['REMOTE_ADDR'], '127.0.0.1')
- self.assertEqual(environ['CONTENT_TYPE'], 'abc')
- self.assertEqual(environ['CONTENT_LENGTH'], '10')
- self.assertEqual(environ['HTTP_X_FOO'], 'BAR')
- self.assertEqual(environ['wsgi.version'], (1, 0))
- self.assertEqual(environ['wsgi.url_scheme'], 'http')
- self.assertEqual(environ['wsgi.errors'], sys.stderr)
- self.assertEqual(environ['wsgi.multithread'], True)
- self.assertEqual(environ['wsgi.multiprocess'], False)
- self.assertEqual(environ['wsgi.run_once'], False)
- self.assertEqual(environ['wsgi.input'], 'stream')
- self.assertEqual(inst.environ, environ)
-
- def test_get_environment_values_w_scheme_override_untrusted(self):
- inst = self._makeOne()
- request = DummyParser()
- request.headers = {
- 'CONTENT_TYPE': 'abc',
- 'CONTENT_LENGTH': '10',
- 'X_FOO': 'BAR',
- 'X_FORWARDED_PROTO': 'https',
- 'CONNECTION': 'close',
- }
- request.query = 'abc'
- inst.request = request
- environ = inst.get_environment()
- self.assertEqual(environ['wsgi.url_scheme'], 'http')
-
- def test_get_environment_values_w_scheme_override_trusted(self):
- import sys
- inst = self._makeOne()
- inst.channel.addr = ['192.168.1.1']
- inst.channel.server.adj.trusted_proxy = '192.168.1.1'
- request = DummyParser()
- request.headers = {
- 'CONTENT_TYPE': 'abc',
- 'CONTENT_LENGTH': '10',
- 'X_FOO': 'BAR',
- 'X_FORWARDED_PROTO': 'https',
- 'CONNECTION': 'close',
- }
- request.query = 'abc'
- inst.request = request
- environ = inst.get_environment()
-
- # nail the keys of environ
- self.assertEqual(sorted(environ.keys()), [
- 'CONTENT_LENGTH', 'CONTENT_TYPE', 'HTTP_CONNECTION', 'HTTP_X_FOO',
- 'PATH_INFO', 'QUERY_STRING', 'REMOTE_ADDR', 'REQUEST_METHOD',
- 'SCRIPT_NAME', 'SERVER_NAME', 'SERVER_PORT', 'SERVER_PROTOCOL',
- 'SERVER_SOFTWARE', 'wsgi.errors', 'wsgi.file_wrapper', 'wsgi.input',
- 'wsgi.multiprocess', 'wsgi.multithread', 'wsgi.run_once',
- 'wsgi.url_scheme', 'wsgi.version'])
-
- self.assertEqual(environ['REQUEST_METHOD'], 'GET')
- self.assertEqual(environ['SERVER_PORT'], '80')
- self.assertEqual(environ['SERVER_NAME'], 'localhost')
- self.assertEqual(environ['SERVER_SOFTWARE'], 'waitress')
- self.assertEqual(environ['SERVER_PROTOCOL'], 'HTTP/1.0')
- self.assertEqual(environ['SCRIPT_NAME'], '')
- self.assertEqual(environ['HTTP_CONNECTION'], 'close')
- self.assertEqual(environ['PATH_INFO'], '/')
- self.assertEqual(environ['QUERY_STRING'], 'abc')
- self.assertEqual(environ['REMOTE_ADDR'], '192.168.1.1')
- self.assertEqual(environ['CONTENT_TYPE'], 'abc')
- self.assertEqual(environ['CONTENT_LENGTH'], '10')
- self.assertEqual(environ['HTTP_X_FOO'], 'BAR')
- self.assertEqual(environ['wsgi.version'], (1, 0))
- self.assertEqual(environ['wsgi.url_scheme'], 'https')
- self.assertEqual(environ['wsgi.errors'], sys.stderr)
- self.assertEqual(environ['wsgi.multithread'], True)
- self.assertEqual(environ['wsgi.multiprocess'], False)
- self.assertEqual(environ['wsgi.run_once'], False)
- self.assertEqual(environ['wsgi.input'], 'stream')
- self.assertEqual(inst.environ, environ)
-
- def test_get_environment_values_w_bogus_scheme_override(self):
- inst = self._makeOne()
- inst.channel.addr = ['192.168.1.1']
- inst.channel.server.adj.trusted_proxy = '192.168.1.1'
- request = DummyParser()
- request.headers = {
- 'CONTENT_TYPE': 'abc',
- 'CONTENT_LENGTH': '10',
- 'X_FOO': 'BAR',
- 'X_FORWARDED_PROTO': 'http://p02n3e.com?url=http',
- 'CONNECTION': 'close',
- }
- request.query = 'abc'
- inst.request = request
- self.assertRaises(ValueError, inst.get_environment)
-
-class TestErrorTask(unittest.TestCase):
-
- def _makeOne(self, channel=None, request=None):
- if channel is None:
- channel = DummyChannel()
- if request is None:
- request = DummyParser()
- request.error = DummyError()
- from waitress.task import ErrorTask
- return ErrorTask(channel, request)
-
- def test_execute_http_10(self):
- inst = self._makeOne()
- inst.execute()
- lines = filter_lines(inst.channel.written)
- self.assertEqual(len(lines), 9)
- self.assertEqual(lines[0], b'HTTP/1.0 432 Too Ugly')
- self.assertEqual(lines[1], b'Connection: close')
- self.assertEqual(lines[2], b'Content-Length: 43')
- self.assertEqual(lines[3], b'Content-Type: text/plain')
- self.assertTrue(lines[4])
- self.assertEqual(lines[5], b'Server: waitress')
- self.assertEqual(lines[6], b'Too Ugly')
- self.assertEqual(lines[7], b'body')
- self.assertEqual(lines[8], b'(generated by waitress)')
-
- def test_execute_http_11(self):
- inst = self._makeOne()
- inst.version = '1.1'
- inst.execute()
- lines = filter_lines(inst.channel.written)
- self.assertEqual(len(lines), 8)
- self.assertEqual(lines[0], b'HTTP/1.1 432 Too Ugly')
- self.assertEqual(lines[1], b'Content-Length: 43')
- self.assertEqual(lines[2], b'Content-Type: text/plain')
- self.assertTrue(lines[3])
- self.assertEqual(lines[4], b'Server: waitress')
- self.assertEqual(lines[5], b'Too Ugly')
- self.assertEqual(lines[6], b'body')
- self.assertEqual(lines[7], b'(generated by waitress)')
-
- def test_execute_http_11_close(self):
- inst = self._makeOne()
- inst.version = '1.1'
- inst.request.headers['CONNECTION'] = 'close'
- inst.execute()
- lines = filter_lines(inst.channel.written)
- self.assertEqual(len(lines), 9)
- self.assertEqual(lines[0], b'HTTP/1.1 432 Too Ugly')
- self.assertEqual(lines[1], b'Connection: close')
- self.assertEqual(lines[2], b'Content-Length: 43')
- self.assertEqual(lines[3], b'Content-Type: text/plain')
- self.assertTrue(lines[4])
- self.assertEqual(lines[5], b'Server: waitress')
- self.assertEqual(lines[6], b'Too Ugly')
- self.assertEqual(lines[7], b'body')
- self.assertEqual(lines[8], b'(generated by waitress)')
-
- def test_execute_http_11_keep(self):
- inst = self._makeOne()
- inst.version = '1.1'
- inst.request.headers['CONNECTION'] = 'keep-alive'
- inst.execute()
- lines = filter_lines(inst.channel.written)
- self.assertEqual(len(lines), 8)
- self.assertEqual(lines[0], b'HTTP/1.1 432 Too Ugly')
- self.assertEqual(lines[1], b'Content-Length: 43')
- self.assertEqual(lines[2], b'Content-Type: text/plain')
- self.assertTrue(lines[3])
- self.assertEqual(lines[4], b'Server: waitress')
- self.assertEqual(lines[5], b'Too Ugly')
- self.assertEqual(lines[6], b'body')
- self.assertEqual(lines[7], b'(generated by waitress)')
-
-
-class DummyError(object):
- code = '432'
- reason = 'Too Ugly'
- body = 'body'
-
-class DummyTask(object):
- serviced = False
- deferred = False
- cancelled = False
-
- def __init__(self, toraise=None):
- self.toraise = toraise
-
- def service(self):
- self.serviced = True
- if self.toraise:
- raise self.toraise
-
- def defer(self):
- self.deferred = True
- if self.toraise:
- raise self.toraise
-
- def cancel(self):
- self.cancelled = True
-
-class DummyAdj(object):
- log_socket_errors = True
- ident = 'waitress'
- host = '127.0.0.1'
- port = 80
- url_prefix = ''
- trusted_proxy = None
-
-class DummyServer(object):
- server_name = 'localhost'
- effective_port = 80
-
- def __init__(self):
- self.adj = DummyAdj()
-
-class DummyChannel(object):
- closed_when_done = False
- adj = DummyAdj()
- creation_time = 0
- addr = ['127.0.0.1']
-
- def __init__(self, server=None):
- if server is None:
- server = DummyServer()
- self.server = server
- self.written = b''
- self.otherdata = []
-
- def write_soon(self, data):
- if isinstance(data, bytes):
- self.written += data
- else:
- self.otherdata.append(data)
- return len(data)
-
-class DummyParser(object):
- version = '1.0'
- command = 'GET'
- path = '/'
- query = ''
- url_scheme = 'http'
- expect_continue = False
- headers_finished = False
-
- def __init__(self):
- self.headers = {}
-
- def get_body_stream(self):
- return 'stream'
-
-def filter_lines(s):
- return list(filter(None, s.split(b'\r\n')))
-
-class DummyLogger(object):
-
- def __init__(self):
- self.logged = []
-
- def warning(self, msg):
- self.logged.append(msg)
-
- def exception(self, msg):
- self.logged.append(msg)
diff --git a/libs/waitress/tests/test_trigger.py b/libs/waitress/tests/test_trigger.py
deleted file mode 100644
index bfff16e4e..000000000
--- a/libs/waitress/tests/test_trigger.py
+++ /dev/null
@@ -1,105 +0,0 @@
-import unittest
-import os
-import sys
-
-if not sys.platform.startswith("win"):
-
- class Test_trigger(unittest.TestCase):
-
- def _makeOne(self, map):
- from waitress.trigger import trigger
- return trigger(map)
-
- def test__close(self):
- map = {}
- inst = self._makeOne(map)
- fd = os.open(os.path.abspath(__file__), os.O_RDONLY)
- inst._fds = (fd,)
- inst.close()
- self.assertRaises(OSError, os.read, fd, 1)
-
- def test__physical_pull(self):
- map = {}
- inst = self._makeOne(map)
- inst._physical_pull()
- r = os.read(inst._fds[0], 1)
- self.assertEqual(r, b'x')
-
- def test_readable(self):
- map = {}
- inst = self._makeOne(map)
- self.assertEqual(inst.readable(), True)
-
- def test_writable(self):
- map = {}
- inst = self._makeOne(map)
- self.assertEqual(inst.writable(), False)
-
- def test_handle_connect(self):
- map = {}
- inst = self._makeOne(map)
- self.assertEqual(inst.handle_connect(), None)
-
- def test_close(self):
- map = {}
- inst = self._makeOne(map)
- self.assertEqual(inst.close(), None)
- self.assertEqual(inst._closed, True)
-
- def test_handle_close(self):
- map = {}
- inst = self._makeOne(map)
- self.assertEqual(inst.handle_close(), None)
- self.assertEqual(inst._closed, True)
-
- def test_pull_trigger_nothunk(self):
- map = {}
- inst = self._makeOne(map)
- self.assertEqual(inst.pull_trigger(), None)
- r = os.read(inst._fds[0], 1)
- self.assertEqual(r, b'x')
-
- def test_pull_trigger_thunk(self):
- map = {}
- inst = self._makeOne(map)
- self.assertEqual(inst.pull_trigger(True), None)
- self.assertEqual(len(inst.thunks), 1)
- r = os.read(inst._fds[0], 1)
- self.assertEqual(r, b'x')
-
- def test_handle_read_socket_error(self):
- map = {}
- inst = self._makeOne(map)
- result = inst.handle_read()
- self.assertEqual(result, None)
-
- def test_handle_read_no_socket_error(self):
- map = {}
- inst = self._makeOne(map)
- inst.pull_trigger()
- result = inst.handle_read()
- self.assertEqual(result, None)
-
- def test_handle_read_thunk(self):
- map = {}
- inst = self._makeOne(map)
- inst.pull_trigger()
- L = []
- inst.thunks = [lambda: L.append(True)]
- result = inst.handle_read()
- self.assertEqual(result, None)
- self.assertEqual(L, [True])
- self.assertEqual(inst.thunks, [])
-
- def test_handle_read_thunk_error(self):
- map = {}
- inst = self._makeOne(map)
- def errorthunk():
- raise ValueError
- inst.pull_trigger(errorthunk)
- L = []
- inst.log_info = lambda *arg: L.append(arg)
- result = inst.handle_read()
- self.assertEqual(result, None)
- self.assertEqual(len(L), 1)
- self.assertEqual(inst.thunks, [])
diff --git a/libs/waitress/tests/test_utilities.py b/libs/waitress/tests/test_utilities.py
deleted file mode 100644
index 73f6c7b76..000000000
--- a/libs/waitress/tests/test_utilities.py
+++ /dev/null
@@ -1,121 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-
-import unittest
-
-class Test_parse_http_date(unittest.TestCase):
-
- def _callFUT(self, v):
- from waitress.utilities import parse_http_date
- return parse_http_date(v)
-
- def test_rfc850(self):
- val = 'Tuesday, 08-Feb-94 14:15:29 GMT'
- result = self._callFUT(val)
- self.assertEqual(result, 760716929)
-
- def test_rfc822(self):
- val = 'Sun, 08 Feb 1994 14:15:29 GMT'
- result = self._callFUT(val)
- self.assertEqual(result, 760716929)
-
- def test_neither(self):
- val = ''
- result = self._callFUT(val)
- self.assertEqual(result, 0)
-
-class Test_build_http_date(unittest.TestCase):
-
- def test_rountdrip(self):
- from waitress.utilities import build_http_date, parse_http_date
- from time import time
- t = int(time())
- self.assertEqual(t, parse_http_date(build_http_date(t)))
-
-class Test_unpack_rfc850(unittest.TestCase):
-
- def _callFUT(self, val):
- from waitress.utilities import unpack_rfc850, rfc850_reg
- return unpack_rfc850(rfc850_reg.match(val.lower()))
-
- def test_it(self):
- val = 'Tuesday, 08-Feb-94 14:15:29 GMT'
- result = self._callFUT(val)
- self.assertEqual(result, (1994, 2, 8, 14, 15, 29, 0, 0, 0))
-
-class Test_unpack_rfc_822(unittest.TestCase):
-
- def _callFUT(self, val):
- from waitress.utilities import unpack_rfc822, rfc822_reg
- return unpack_rfc822(rfc822_reg.match(val.lower()))
-
- def test_it(self):
- val = 'Sun, 08 Feb 1994 14:15:29 GMT'
- result = self._callFUT(val)
- self.assertEqual(result, (1994, 2, 8, 14, 15, 29, 0, 0, 0))
-
-class Test_find_double_newline(unittest.TestCase):
-
- def _callFUT(self, val):
- from waitress.utilities import find_double_newline
- return find_double_newline(val)
-
- def test_empty(self):
- self.assertEqual(self._callFUT(b''), -1)
-
- def test_one_linefeed(self):
- self.assertEqual(self._callFUT(b'\n'), -1)
-
- def test_double_linefeed(self):
- self.assertEqual(self._callFUT(b'\n\n'), 2)
-
- def test_one_crlf(self):
- self.assertEqual(self._callFUT(b'\r\n'), -1)
-
- def test_double_crfl(self):
- self.assertEqual(self._callFUT(b'\r\n\r\n'), 4)
-
- def test_mixed(self):
- self.assertEqual(self._callFUT(b'\n\n00\r\n\r\n'), 2)
-
-class Test_logging_dispatcher(unittest.TestCase):
-
- def _makeOne(self):
- from waitress.utilities import logging_dispatcher
- return logging_dispatcher(map={})
-
- def test_log_info(self):
- import logging
- inst = self._makeOne()
- logger = DummyLogger()
- inst.logger = logger
- inst.log_info('message', 'warning')
- self.assertEqual(logger.severity, logging.WARN)
- self.assertEqual(logger.message, 'message')
-
-class TestBadRequest(unittest.TestCase):
-
- def _makeOne(self):
- from waitress.utilities import BadRequest
- return BadRequest(1)
-
- def test_it(self):
- inst = self._makeOne()
- self.assertEqual(inst.body, 1)
-
-class DummyLogger(object):
-
- def log(self, severity, message):
- self.severity = severity
- self.message = message
diff --git a/libs/waitress/trigger.py b/libs/waitress/trigger.py
deleted file mode 100644
index cac8e2649..000000000
--- a/libs/waitress/trigger.py
+++ /dev/null
@@ -1,198 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001-2005 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE
-#
-##############################################################################
-
-import asyncore
-import os
-import socket
-import errno
-import threading
-
-# Wake up a call to select() running in the main thread.
-#
-# This is useful in a context where you are using Medusa's I/O
-# subsystem to deliver data, but the data is generated by another
-# thread. Normally, if Medusa is in the middle of a call to
-# select(), new output data generated by another thread will have
-# to sit until the call to select() either times out or returns.
-# If the trigger is 'pulled' by another thread, it should immediately
-# generate a READ event on the trigger object, which will force the
-# select() invocation to return.
-#
-# A common use for this facility: letting Medusa manage I/O for a
-# large number of connections; but routing each request through a
-# thread chosen from a fixed-size thread pool. When a thread is
-# acquired, a transaction is performed, but output data is
-# accumulated into buffers that will be emptied more efficiently
-# by Medusa. [picture a server that can process database queries
-# rapidly, but doesn't want to tie up threads waiting to send data
-# to low-bandwidth connections]
-#
-# The other major feature provided by this class is the ability to
-# move work back into the main thread: if you call pull_trigger()
-# with a thunk argument, when select() wakes up and receives the
-# event it will call your thunk from within that thread. The main
-# purpose of this is to remove the need to wrap thread locks around
-# Medusa's data structures, which normally do not need them. [To see
-# why this is true, imagine this scenario: A thread tries to push some
-# new data onto a channel's outgoing data queue at the same time that
-# the main thread is trying to remove some]
-
-class _triggerbase(object):
- """OS-independent base class for OS-dependent trigger class."""
-
- kind = None # subclass must set to "pipe" or "loopback"; used by repr
-
- def __init__(self):
- self._closed = False
-
- # `lock` protects the `thunks` list from being traversed and
- # appended to simultaneously.
- self.lock = threading.Lock()
-
- # List of no-argument callbacks to invoke when the trigger is
- # pulled. These run in the thread running the asyncore mainloop,
- # regardless of which thread pulls the trigger.
- self.thunks = []
-
- def readable(self):
- return True
-
- def writable(self):
- return False
-
- def handle_connect(self):
- pass
-
- def handle_close(self):
- self.close()
-
- # Override the asyncore close() method, because it doesn't know about
- # (so can't close) all the gimmicks we have open. Subclass must
- # supply a _close() method to do platform-specific closing work. _close()
- # will be called iff we're not already closed.
- def close(self):
- if not self._closed:
- self._closed = True
- self.del_channel()
- self._close() # subclass does OS-specific stuff
-
- def pull_trigger(self, thunk=None):
- if thunk:
- with self.lock:
- self.thunks.append(thunk)
- self._physical_pull()
-
- def handle_read(self):
- try:
- self.recv(8192)
- except (OSError, socket.error):
- return
- with self.lock:
- for thunk in self.thunks:
- try:
- thunk()
- except:
- nil, t, v, tbinfo = asyncore.compact_traceback()
- self.log_info(
- 'exception in trigger thunk: (%s:%s %s)' %
- (t, v, tbinfo))
- self.thunks = []
-
-if os.name == 'posix':
-
- class trigger(_triggerbase, asyncore.file_dispatcher):
- kind = "pipe"
-
- def __init__(self, map):
- _triggerbase.__init__(self)
- r, self.trigger = self._fds = os.pipe()
- asyncore.file_dispatcher.__init__(self, r, map=map)
-
- def _close(self):
- for fd in self._fds:
- os.close(fd)
- self._fds = []
-
- def _physical_pull(self):
- os.write(self.trigger, b'x')
-
-else: # pragma: no cover
- # Windows version; uses just sockets, because a pipe isn't select'able
- # on Windows.
-
- class trigger(_triggerbase, asyncore.dispatcher):
- kind = "loopback"
-
- def __init__(self, map):
- _triggerbase.__init__(self)
-
- # Get a pair of connected sockets. The trigger is the 'w'
- # end of the pair, which is connected to 'r'. 'r' is put
- # in the asyncore socket map. "pulling the trigger" then
- # means writing something on w, which will wake up r.
-
- w = socket.socket()
- # Disable buffering -- pulling the trigger sends 1 byte,
- # and we want that sent immediately, to wake up asyncore's
- # select() ASAP.
- w.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
-
- count = 0
- while True:
- count += 1
- # Bind to a local port; for efficiency, let the OS pick
- # a free port for us.
- # Unfortunately, stress tests showed that we may not
- # be able to connect to that port ("Address already in
- # use") despite that the OS picked it. This appears
- # to be a race bug in the Windows socket implementation.
- # So we loop until a connect() succeeds (almost always
- # on the first try). See the long thread at
- # http://mail.zope.org/pipermail/zope/2005-July/160433.html
- # for hideous details.
- a = socket.socket()
- a.bind(("127.0.0.1", 0))
- connect_address = a.getsockname() # assigned (host, port) pair
- a.listen(1)
- try:
- w.connect(connect_address)
- break # success
- except socket.error as detail:
- if detail[0] != errno.WSAEADDRINUSE:
- # "Address already in use" is the only error
- # I've seen on two WinXP Pro SP2 boxes, under
- # Pythons 2.3.5 and 2.4.1.
- raise
- # (10048, 'Address already in use')
- # assert count <= 2 # never triggered in Tim's tests
- if count >= 10: # I've never seen it go above 2
- a.close()
- w.close()
- raise RuntimeError("Cannot bind trigger!")
- # Close `a` and try again. Note: I originally put a short
- # sleep() here, but it didn't appear to help or hurt.
- a.close()
-
- r, addr = a.accept() # r becomes asyncore's (self.)socket
- a.close()
- self.trigger = w
- asyncore.dispatcher.__init__(self, r, map=map)
-
- def _close(self):
- # self.socket is r, and self.trigger is w, from __init__
- self.socket.close()
- self.trigger.close()
-
- def _physical_pull(self):
- self.trigger.send(b'x')
diff --git a/libs/waitress/utilities.py b/libs/waitress/utilities.py
deleted file mode 100644
index 943c92fd1..000000000
--- a/libs/waitress/utilities.py
+++ /dev/null
@@ -1,216 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Foundation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""Utility functions
-"""
-
-import asyncore
-import errno
-import logging
-import os
-import re
-import stat
-import time
-import calendar
-
-logger = logging.getLogger('waitress')
-
-def find_double_newline(s):
- """Returns the position just after a double newline in the given string."""
- pos1 = s.find(b'\n\r\n') # One kind of double newline
- if pos1 >= 0:
- pos1 += 3
- pos2 = s.find(b'\n\n') # Another kind of double newline
- if pos2 >= 0:
- pos2 += 2
-
- if pos1 >= 0:
- if pos2 >= 0:
- return min(pos1, pos2)
- else:
- return pos1
- else:
- return pos2
-
-def concat(*args):
- return ''.join(args)
-
-def join(seq, field=' '):
- return field.join(seq)
-
-def group(s):
- return '(' + s + ')'
-
-short_days = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
-long_days = ['sunday', 'monday', 'tuesday', 'wednesday',
- 'thursday', 'friday', 'saturday']
-
-short_day_reg = group(join(short_days, '|'))
-long_day_reg = group(join(long_days, '|'))
-
-daymap = {}
-for i in range(7):
- daymap[short_days[i]] = i
- daymap[long_days[i]] = i
-
-hms_reg = join(3 * [group('[0-9][0-9]')], ':')
-
-months = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', 'jul',
- 'aug', 'sep', 'oct', 'nov', 'dec']
-
-monmap = {}
-for i in range(12):
- monmap[months[i]] = i + 1
-
-months_reg = group(join(months, '|'))
-
-# From draft-ietf-http-v11-spec-07.txt/3.3.1
-# Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
-# Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
-# Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
-
-# rfc822 format
-rfc822_date = join(
- [concat(short_day_reg, ','), # day
- group('[0-9][0-9]?'), # date
- months_reg, # month
- group('[0-9]+'), # year
- hms_reg, # hour minute second
- 'gmt'
- ],
- ' '
-)
-
-rfc822_reg = re.compile(rfc822_date)
-
-def unpack_rfc822(m):
- g = m.group
- return (
- int(g(4)), # year
- monmap[g(3)], # month
- int(g(2)), # day
- int(g(5)), # hour
- int(g(6)), # minute
- int(g(7)), # second
- 0,
- 0,
- 0,
- )
-
-# rfc850 format
-rfc850_date = join(
- [concat(long_day_reg, ','),
- join(
- [group('[0-9][0-9]?'),
- months_reg,
- group('[0-9]+')
- ],
- '-'
- ),
- hms_reg,
- 'gmt'
- ],
- ' '
-)
-
-rfc850_reg = re.compile(rfc850_date)
-# they actually unpack the same way
-def unpack_rfc850(m):
- g = m.group
- yr = g(4)
- if len(yr) == 2:
- yr = '19' + yr
- return (
- int(yr), # year
- monmap[g(3)], # month
- int(g(2)), # day
- int(g(5)), # hour
- int(g(6)), # minute
- int(g(7)), # second
- 0,
- 0,
- 0
- )
-
-# parsdate.parsedate - ~700/sec.
-# parse_http_date - ~1333/sec.
-
-weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-monthname = [None, 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
- 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
-
-def build_http_date(when):
- year, month, day, hh, mm, ss, wd, y, z = time.gmtime(when)
- return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
- weekdayname[wd],
- day, monthname[month], year,
- hh, mm, ss)
-
-def parse_http_date(d):
- d = d.lower()
- m = rfc850_reg.match(d)
- if m and m.end() == len(d):
- retval = int(calendar.timegm(unpack_rfc850(m)))
- else:
- m = rfc822_reg.match(d)
- if m and m.end() == len(d):
- retval = int(calendar.timegm(unpack_rfc822(m)))
- else:
- return 0
- return retval
-
-class logging_dispatcher(asyncore.dispatcher):
- logger = logger
-
- def log_info(self, message, type='info'):
- severity = {
- 'info': logging.INFO,
- 'warning': logging.WARN,
- 'error': logging.ERROR,
- }
- self.logger.log(severity.get(type, logging.INFO), message)
-
-def cleanup_unix_socket(path):
- try:
- st = os.stat(path)
- except OSError as exc:
- if exc.errno != errno.ENOENT:
- raise # pragma: no cover
- else:
- if stat.S_ISSOCK(st.st_mode):
- try:
- os.remove(path)
- except OSError: # pragma: no cover
- # avoid race condition error during tests
- pass
-
-class Error(object):
-
- def __init__(self, body):
- self.body = body
-
-class BadRequest(Error):
- code = 400
- reason = 'Bad Request'
-
-class RequestHeaderFieldsTooLarge(BadRequest):
- code = 431
- reason = 'Request Header Fields Too Large'
-
-class RequestEntityTooLarge(BadRequest):
- code = 413
- reason = 'Request Entity Too Large'
-
-class InternalServerError(Error):
- code = 500
- reason = 'Internal Server Error'