diff -Nru waitress-0.8.10/appveyor.yml waitress-1.0.1/appveyor.yml --- waitress-0.8.10/appveyor.yml 1970-01-01 00:00:00.000000000 +0000 +++ waitress-1.0.1/appveyor.yml 2016-10-22 19:40:39.000000000 +0000 @@ -0,0 +1,14 @@ +environment: + matrix: + - PYTHON: "C:\\Python27" + TOXENV: "py27" + - PYTHON: "C:\\Python35" + TOXENV: "py35" + +install: + - "%PYTHON%\\python.exe -m pip install tox" + +build: off + +test_script: + - "%PYTHON%\\Scripts\\tox.exe" diff -Nru waitress-0.8.10/CHANGES.txt waitress-1.0.1/CHANGES.txt --- waitress-0.8.10/CHANGES.txt 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/CHANGES.txt 2016-10-22 19:40:39.000000000 +0000 @@ -1,385 +1,56 @@ -0.8.10 (2015-08-02) -------------------- - -- Add support for Python 3.4, 3.5b2, and PyPy3. - -- Use a nonglobal asyncore socket map by default, trying to prevent conflicts - with apps and libs that use the asyncore global socket map ala - https://github.com/Pylons/waitress/issues/63. You can get the old - use-global-socket-map behavior back by passing ``asyncore.socket_map`` to the - ``create_server`` function as the ``map`` argument. - -- Waitress violated PEP 3333 with respect to reraising an exception when - ``start_response`` was called with an ``exc_info`` argument. It would - reraise the exception even if no data had been sent to the client. It now - only reraises the exception if data has actually been sent to the client. - See https://github.com/Pylons/waitress/pull/52 and - https://github.com/Pylons/waitress/issues/51 - -- Add a ``docs`` section to tox.ini that, when run, ensures docs can be built. - -- If an ``application`` value of ``None`` is supplied to the ``create_server`` - constructor function, a ValueError is now raised eagerly instead of an error - occuring during runtime. See https://github.com/Pylons/waitress/pull/60 - -- Fix parsing of multi-line (folded) headers. - See https://github.com/Pylons/waitress/issues/53 and - https://github.com/Pylons/waitress/pull/90 - -- Switch from the low level Python thread/_thread module to the threading - module. - -- Improved exception information should module import go awry. - -0.8.9 (2014-05-16) ------------------- - -- Fix tests under Windows. NB: to run tests under Windows, you cannot run - "setup.py test" or "setup.py nosetests". Instead you must run ``python.exe - -c "import nose; nose.main()"``. If you try to run the tests using the - normal method under Windows, each subprocess created by the test suite will - attempt to run the test suite again. See - https://github.com/nose-devs/nose/issues/407 for more information. - -- Give the WSGI app_iter generated when ``wsgi.file_wrapper`` is used - (ReadOnlyFileBasedBuffer) a ``close`` method. Do not call ``close`` on an - instance of such a class when it's used as a WSGI app_iter, however. This is - part of a fix which prevents a leakage of file descriptors; the other part of - the fix was in WebOb - (https://github.com/Pylons/webob/commit/951a41ce57bd853947f842028bccb500bd5237da). - -- Allow trusted proxies to override ``wsgi.url_scheme`` via a request header, - ``X_FORWARDED_PROTO``. Allows proxies which serve mixed HTTP / HTTPS - requests to control signal which are served as HTTPS. See - https://github.com/Pylons/waitress/pull/42. - -0.8.8 (2013-11-30) ------------------- - -- Fix some cases where the creation of extremely large output buffers (greater - than 2GB, suspected to be buffers added via ``wsgi.file_wrapper``) might - cause an OverflowError on Python 2. See - https://github.com/Pylons/waitress/issues/47. - -- When the ``url_prefix`` adjustment starts with more than one slash, all - slashes except one will be stripped from its beginning. This differs from - older behavior where more than one leading slash would be preserved in - ``url_prefix``. - -- If a client somehow manages to send an empty path, we no longer convert the - empty path to a single slash in ``PATH_INFO``. Instead, the path remains - empty. According to RFC 2616 section "5.1.2 Request-URI", the scenario of a - client sending an empty path is actually not possible because the request URI - portion cannot be empty. - -- If the ``url_prefix`` adjustment matches the request path exactly, we now - compute ``SCRIPT_NAME`` and ``PATH_INFO`` properly. Previously, if the - ``url_prefix`` was ``/foo`` and the path received from a client was ``/foo``, - we would set *both* ``SCRIPT_NAME`` and ``PATH_INFO`` to ``/foo``. This was - incorrect. Now in such a case we set ``PATH_INFO`` to the empty string and - we set ``SCRIPT_NAME`` to ``/foo``. Note that the change we made has no - effect on paths that do not match the ``url_prefix`` exactly (such as - ``/foo/bar``); these continue to operate as they did. See - https://github.com/Pylons/waitress/issues/46 - -- Preserve header ordering of headers with the same name as per RFC 2616. See - https://github.com/Pylons/waitress/pull/44 - -- When waitress receives a ``Transfer-Encoding: chunked`` request, we no longer - send the ``TRANSFER_ENCODING`` nor the ``HTTP_TRANSFER_ENCODING`` value to - the application in the environment. Instead, we pop this header. Since we - cope with chunked requests by buffering the data in the server, we also know - when a chunked request has ended, and therefore we know the content length. - We set the content-length header in the environment, such that applications - effectively never know the original request was a T-E: chunked request; it - will appear to them as if the request is a non-chunked request with an - accurate content-length. - -- Cope with the fact that the ``Transfer-Encoding`` value is case-insensitive. - -- When the ``--unix-socket-perms`` option was used as an argument to - ``waitress-serve``, a ``TypeError`` would be raised. See - https://github.com/Pylons/waitress/issues/50. - -0.8.7 (2013-08-29) ------------------- - -- The HTTP version of the response returned by waitress when it catches an - exception will now match the HTTP request version. - -- Fix: CONNECTION header will be HTTP_CONNECTION and not CONNECTION_TYPE - (see https://github.com/Pylons/waitress/issues/13) - -0.8.6 (2013-08-12) ------------------- - -- Do alternate type of checking for UNIX socket support, instead of checking - for platform == windows. - -- Functional tests now use multiprocessing module instead of subprocess module, - speeding up test suite and making concurrent execution more reliable. - -- Runner now appends the current working directory to ``sys.path`` to support - running WSGI applications from a directory (i.e., not installed in a - virtualenv). - -- Add a ``url_prefix`` adjustment setting. You can use it by passing - ``script_name='/foo'`` to ``waitress.serve`` or you can use it in a - ``PasteDeploy`` ini file as ``script_name = /foo``. This 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. You can use this instead of - PasteDeploy's ``prefixmiddleware`` to always prefix the path. - -0.8.5 (2013-05-27) ------------------- - -- Fix runner multisegment imports in some Python 2 revisions (see - https://github.com/Pylons/waitress/pull/34). - -- For compatibility, WSGIServer is now an alias of TcpWSGIServer. The - signature of BaseWSGIServer is now compatible with WSGIServer pre-0.8.4. - -0.8.4 (2013-05-24) ------------------- - -- Add a command-line runner called ``waitress-serve`` to allow Waitress - to run WSGI applications without any addional machinery. This is - essentially a thin wrapper around the ``waitress.serve()`` function. - -- Allow parallel testing (e.g., under ``detox`` or ``nosetests --processes``) - using PID-dependent port / socket for functest servers. - -- Fix integer overflow errors on large buffers. Thanks to Marcin Kuzminski - for the patch. See: https://github.com/Pylons/waitress/issues/22 - -- Add support for listening on Unix domain sockets. - -0.8.3 (2013-04-28) ------------------- - -Features -~~~~~~~~ - -- Add an ``asyncore_loop_timeout`` adjustment value, which controls the - ``timeout`` value passed to ``asyncore.loop``; defaults to 1. - -Bug Fixes -~~~~~~~~~ - -- The default asyncore loop timeout is now 1 second. This prevents slow - shutdown on Windows. See https://github.com/Pylons/waitress/issues/6 . This - shouldn't matter to anyone in particular, but it can be changed via the - ``asyncore_loop_timeout`` adjustment (it used to previously default to 30 - seconds). - -- Don't complain if there's a response to a HEAD request that contains a - Content-Length > 0. See https://github.com/Pylons/waitress/pull/7. - -- Fix bug in HTTP Expect/Continue support. See - https://github.com/Pylons/waitress/issues/9 . - - -0.8.2 (2012-11-14) +1.0.1 (2016-10-22) ------------------ -Bug Fixes -~~~~~~~~~ - -- http://corte.si/posts/code/pathod/pythonservers/index.html pointed out that - sending a bad header resulted in an exception leading to a 500 response - instead of the more proper 400 response without an exception. - -- Fix a race condition in the test suite. - -- Allow "ident" to be used as a keyword to ``serve()`` as per docs. - -- Add py33 to tox.ini. - -0.8.1 (2012-02-13) ------------------- - -Bug Fixes -~~~~~~~~~ - -- A brown-bag bug prevented request concurrency. A slow request would block - subsequent the responses of subsequent requests until the slow request's - response was fully generated. This was due to a "task lock" being declared - as a class attribute rather than as an instance attribute on HTTPChannel. - Also took the opportunity to move another lock named "outbuf lock" to the - channel instance rather than the class. See - https://github.com/Pylons/waitress/pull/1 . - -0.8 (2012-01-31) ----------------- - -Features +Bugfixes ~~~~~~~~ -- Support the WSGI ``wsgi.file_wrapper`` protocol as per - http://www.python.org/dev/peps/pep-0333/#optional-platform-specific-file-handling. - Here's a usage example:: - - import os - - here = os.path.dirname(os.path.abspath(__file__)) - - def myapp(environ, start_response): - f = open(os.path.join(here, 'myphoto.jpg'), 'rb') - headers = [('Content-Type', 'image/jpeg')] - start_response( - '200 OK', - headers - ) - return environ['wsgi.file_wrapper'](f, 32768) - - The signature of the file wrapper constructor is ``(filelike_object, - block_size)``. Both arguments must be passed as positional (not keyword) - arguments. The result of creating a file wrapper should be **returned** as - the ``app_iter`` from a WSGI application. - - The object passed as ``filelike_object`` to the wrapper must be a file-like - object which supports *at least* the ``read()`` method, and the ``read()`` - method must support an optional size hint argument. It *should* support - the ``seek()`` and ``tell()`` methods. If it does not, normal iteration - over the filelike object using the provided block_size is used (and copying - is done, negating any benefit of the file wrapper). It *should* support a - ``close()`` method. - - The specified ``block_size`` argument to the file wrapper constructor will - be used only when the ``filelike_object`` doesn't support ``seek`` and/or - ``tell`` methods. Waitress needs to use normal iteration to serve the file - in this degenerate case (as per the WSGI spec), and this block size will be - used as the iteration chunk size. The ``block_size`` argument is optional; - if it is not passed, a default value``32768`` is used. - - Waitress will set a ``Content-Length`` header on the behalf of an - application when a file wrapper with a sufficiently filelike object is used - if the application hasn't already set one. - - The machinery which handles a file wrapper currently doesn't do anything - particularly special using fancy system calls (it doesn't use ``sendfile`` - for example); using it currently just prevents the system from needing to - copy data to a temporary buffer in order to send it to the client. No - copying of data is done when a WSGI app returns a file wrapper that wraps a - sufficiently filelike object. It may do something fancier in the future. - -0.7 (2012-01-11) ----------------- - -Features -~~~~~~~~ +- IPv6 support on Windows was broken due to missing constants in the socket + module. This has been resolved by setting the constants on Windows if they + are missing. See https://github.com/Pylons/waitress/issues/138 -- Default ``send_bytes`` value is now 18000 instead of 9000. The larger - default value prevents asyncore from needing to execute select so many - times to serve large files, speeding up file serving by about 15%-20% or - so. This is probably only an optimization for LAN communications, and - could slow things down across a WAN (due to higher TCP overhead), but we're - likely to be behind a reverse proxy on a LAN anyway if in production. +- A ValueError was raised on Windows when passing a string for the port, on + Windows in Python 2 using service names instead of port numbers doesn't work + with `getaddrinfo`. This has been resolved by attempting to convert the port + number to an integer, if that fails a ValueError will be raised. See + https://github.com/Pylons/waitress/issues/139 -- Added an (undocumented) profiling feature to the ``serve()`` command. -0.6.1 (2012-01-08) +1.0.0 (2016-08-31) ------------------ -Bug Fixes -~~~~~~~~~ - -- Remove performance-sapping call to ``pull_trigger`` in the channel's - ``write_soon`` method added mistakenly in 0.6. - -0.6 (2012-01-07) ----------------- - -Bug Fixes -~~~~~~~~~ - -- A logic error prevented the internal outbuf buffer of a channel from being - flushed when the client could not accept the entire contents of the output - buffer in a single succession of socket.send calls when the channel was in - a "pending close" state. The socket in such a case would be closed - prematurely, sometimes resulting in partially delivered content. This was - discovered by a user using waitress behind an Nginx reverse proxy, which - apparently is not always ready to receive data. The symptom was that he - received "half" of a large CSS file (110K) while serving content via - waitress behind the proxy. - -0.5 (2012-01-03) ----------------- - -Bug Fixes -~~~~~~~~~ - -- Fix PATH_INFO encoding/decoding on Python 3 (as per PEP 3333, tunnel - bytes-in-unicode-as-latin-1-after-unquoting). - -0.4 (2012-01-02) ----------------- - -Features +Bugfixes ~~~~~~~~ -- Added "design" document to docs. - -Bug Fixes -~~~~~~~~~ - -- Set default ``connection_limit`` back to 100 for benefit of maximal - platform compatibility. - -- Normalize setting of ``last_activity`` during send. +- Removed `AI_ADDRCONFIG` from the call to `getaddrinfo`, this resolves an + issue whereby `getaddrinfo` wouldn't return any addresses to `bind` to on + hosts where there is no internet connection but localhost is requested to be + bound to. See https://github.com/Pylons/waitress/issues/131 for more + information. -- Minor resource cleanups during tests. +Deprecations +~~~~~~~~~~~~ -- Channel timeout cleanup was broken. - -0.3 (2012-01-02) ----------------- +- Python 2.6 is no longer supported. Features ~~~~~~~~ -- Dont hang a thread up trying to send data to slow clients. - -- Use self.logger to log socket errors instead of self.log_info (normalize). - -- Remove pointless handle_error method from channel. - -- Queue requests instead of tasks in a channel. - -Bug Fixes -~~~~~~~~~ +- IPv6 support -- Expect: 100-continue responses were broken. +- Waitress is now able to listen on multiple sockets, including IPv4 and IPv6. + Instead of passing in a host/port combination you now provide waitress with a + space delineated list, and it will create as many sockets as required. + .. code-block:: python -0.2 (2011-12-31) ----------------- + from waitress import serve + serve(wsgiapp, listen='0.0.0.0:8080 [::]:9090 *:6543') -Bug Fixes -~~~~~~~~~ - -- Set up logging by calling logging.basicConfig() when ``serve`` is called - (show tracebacks and other warnings to console by default). - -- Disallow WSGI applications to set "hop-by-hop" headers (Connection, - Transfer-Encoding, etc). - -- Don't treat 304 status responses specially in HTTP/1.1 mode. - -- Remove out of date ``interfaces.py`` file. - -- Normalize logging (all output is now sent to the ``waitress`` logger rather - than in degenerate cases some output being sent directly to stderr). - -Features +Security ~~~~~~~~ -- Support HTTP/1.1 ``Transfer-Encoding: chunked`` responses. - -- Slightly better docs about logging. - -0.1 (2011-12-30) ----------------- - -- Initial release. +- Waitress will now drop HTTP headers that contain an underscore in the key + when received from a client. This is to stop any possible underscore/dash + conflation that may lead to security issues. See + https://github.com/Pylons/waitress/pull/80 and + https://www.djangoproject.com/weblog/2015/jan/13/security/ diff -Nru waitress-0.8.10/CONTRIBUTORS.txt waitress-1.0.1/CONTRIBUTORS.txt --- waitress-0.8.10/CONTRIBUTORS.txt 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/CONTRIBUTORS.txt 2016-10-22 19:40:39.000000000 +0000 @@ -121,6 +121,8 @@ - Adam Groszer, 2013/08/15 +- Matt Russell, 2015/01/14 + - David Glick, 2015/04/13 - Shane Hathaway, 2015-04-20 @@ -128,3 +130,10 @@ - Steve Piercy, 2015-04-21 - Ben Warren, 2015-05-17 + +- Bert JW Regeer, 2015-09-23 + +- Yu Zhou, 2015-09-24 + +- Jason Madden, 2016-03-19 + diff -Nru waitress-0.8.10/debian/changelog waitress-1.0.1/debian/changelog --- waitress-0.8.10/debian/changelog 2015-12-26 13:44:30.000000000 +0000 +++ waitress-1.0.1/debian/changelog 2016-12-13 13:36:48.000000000 +0000 @@ -1,3 +1,11 @@ +waitress (1.0.1-1) unstable; urgency=medium + + * New upstream release. + * Update package descriptions. + * Build-Depend on Python 2.7+/3.3+. + + -- Andrew Shadura Tue, 13 Dec 2016 14:34:36 +0100 + waitress (0.8.10-1) unstable; urgency=medium [ Juan Picca ] diff -Nru waitress-0.8.10/debian/control waitress-1.0.1/debian/control --- waitress-0.8.10/debian/control 2015-12-26 13:12:38.000000000 +0000 +++ waitress-1.0.1/debian/control 2016-12-13 13:36:10.000000000 +0000 @@ -7,8 +7,9 @@ dh-python, python-setuptools (>= 0.6.24), python3-setuptools (>= 0.6.24), - python-all (>= 2.6.6-3), - python3-all, python-sphinx (>= 1.0.7+dfsg) | python3-sphinx, + python-all (>= 2.7), + python3-all (>= 3.3), + python-sphinx (>= 1.0.7+dfsg) | python3-sphinx, debhelper (>= 9) Standards-Version: 3.9.5 @@ -21,7 +22,7 @@ Waitress is meant to be a production-quality pure-Python WSGI server with very acceptable performance. It has no dependencies except ones which live in the Python standard library. It runs on CPython on Unix and Windows under - Python 2.6+ and Python 3.2. It is also known to run on PyPy 1.6.0 on UNIX. + Python 2.7+ and Python 3.3+. It is also known to run on PyPy 1.6.0 on UNIX. It supports HTTP/1.0 and HTTP/1.1. Package: python3-waitress @@ -32,7 +33,7 @@ Waitress is meant to be a production-quality pure-Python WSGI server with very acceptable performance. It has no dependencies except ones which live in the Python standard library. It runs on CPython on Unix and Windows under - Python 2.6+ and Python 3.2. It is also known to run on PyPy 1.6.0 on UNIX. + Python 2.7+ and Python 3.3+. It is also known to run on PyPy 1.6.0 on UNIX. It supports HTTP/1.0 and HTTP/1.1. . This is the Python 3 version of the package. @@ -45,7 +46,7 @@ Waitress is meant to be a production-quality pure-Python WSGI server with very acceptable performance. It has no dependencies except ones which live in the Python standard library. It runs on CPython on Unix and Windows under - Python 2.6+ and Python 3.2. It is also known to run on PyPy 1.6.0 on UNIX. + Python 2.7+ and Python 3.3+. It is also known to run on PyPy 1.6.0 on UNIX. It supports HTTP/1.0 and HTTP/1.1. . This package only contains the documentation. diff -Nru waitress-0.8.10/debian/patches/01-fix-sphinxdoc-conf.patch waitress-1.0.1/debian/patches/01-fix-sphinxdoc-conf.patch --- waitress-0.8.10/debian/patches/01-fix-sphinxdoc-conf.patch 2015-12-26 13:33:19.000000000 +0000 +++ waitress-1.0.1/debian/patches/01-fix-sphinxdoc-conf.patch 2016-12-13 13:33:08.000000000 +0000 @@ -17,21 +17,19 @@ # # The short X.Y version. -version = pkg_resources.get_distribution('waitress').version -+version = '0.8.10' ++version = '1.0.1' # The full version, including alpha/beta/rc tags. release = version -@@ -89,10 +89,10 @@ +@@ -89,9 +89,9 @@ # ----------------------- # Add and use Pylons theme --sys.path.append(os.path.abspath('_themes')) -html_theme = 'pylons' -html_theme_path = pylons_sphinx_themes.get_html_themes_path() -html_theme_options = dict(github_url='http://github.com/Pylons/waitress') -+# sys.path.append(os.path.abspath('_themes')) +# html_theme = 'pylons' -+# html_theme_path = pylons_sphinx_themes.get_html_themes_path() ++# html_theme_path = # pylons_sphinx_themes.get_html_themes_path() +# html_theme_options = dict(github_url='http://github.com/Pylons/waitress') # The style sheet to use for HTML and HTML Help pages. A file of that name diff -Nru waitress-0.8.10/docs/api.rst waitress-1.0.1/docs/api.rst --- waitress-0.8.10/docs/api.rst 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/docs/api.rst 2016-10-22 19:40:39.000000000 +0000 @@ -5,6 +5,6 @@ .. module:: waitress -.. function:: serve(app, host='0.0.0.0', port=8080, unix_socket=None, unix_socket_perms='600', threads=4, url_scheme='http', url_prefix='', ident='waitress', backlog=1204, recv_bytes=8192, send_bytes=18000, outbuf_overflow=104856, inbuf_overflow=52488, connection_limit=1000, cleanup_interval=30, channel_timeout=120, log_socket_errors=True, max_request_header_size=262144, max_request_body_size=1073741824, expose_tracebacks=False) +.. function:: serve(app, listen='0.0.0.0:8080', unix_socket=None, unix_socket_perms='600', threads=4, url_scheme='http', url_prefix='', ident='waitress', backlog=1204, recv_bytes=8192, send_bytes=18000, outbuf_overflow=104856, inbuf_overflow=52488, connection_limit=1000, cleanup_interval=30, channel_timeout=120, log_socket_errors=True, max_request_header_size=262144, max_request_body_size=1073741824, expose_tracebacks=False) See :ref:`arguments` for more information. diff -Nru waitress-0.8.10/docs/arguments.rst waitress-1.0.1/docs/arguments.rst --- waitress-0.8.10/docs/arguments.rst 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/docs/arguments.rst 2016-10-22 19:40:39.000000000 +0000 @@ -10,9 +10,37 @@ hostname or IP address (string) on which to listen, default ``0.0.0.0``, which means "all IP addresses on this host". + .. warning:: + May not be used with ``listen`` + port TCP port (integer) on which to listen, default ``8080`` + .. warning:: + May not be used with ``listen`` + +listen + Tell waitress to listen on an host/port combination. It is to be provided + as a space delineated list of host/port: + + Examples: + + - ``listen="127.0.0.1:8080 [::1]:8080"`` + - ``listen="*:8080 *:6543"`` + + A wildcard for the hostname is also supported and will bind to both + IPv4/IPv6 depending on whether they are enabled or disabled. + + IPv6 IP addresses are supported by surrounding the IP address with brackets. + + .. versionadded:: 1.0 + +ipv4 + Enable or disable IPv4 (boolean) + +ipv6 + Enable or disable IPv6 (boolean) + unix_socket Path of Unix socket (string), default is ``None``. If a socket path is specified, a Unix domain socket is made instead of the usual inet domain diff -Nru waitress-0.8.10/docs/conf.py waitress-1.0.1/docs/conf.py --- waitress-0.8.10/docs/conf.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/docs/conf.py 2016-10-22 19:40:39.000000000 +0000 @@ -89,7 +89,6 @@ # ----------------------- # Add and use Pylons theme -sys.path.append(os.path.abspath('_themes')) html_theme = 'pylons' html_theme_path = pylons_sphinx_themes.get_html_themes_path() html_theme_options = dict(github_url='http://github.com/Pylons/waitress') @@ -181,7 +180,7 @@ # The name of an image file (relative to this directory) to place at the # top of the title page. -latex_logo = '.static/logo_hi.gif' +#latex_logo = '.static/logo_hi.gif' # For "manual" documents, if this is true, then toplevel headings are # parts, not chapters. diff -Nru waitress-0.8.10/docs/index.rst waitress-1.0.1/docs/index.rst --- waitress-0.8.10/docs/index.rst 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/docs/index.rst 2016-10-22 19:40:39.000000000 +0000 @@ -15,8 +15,19 @@ .. code-block:: python from waitress import serve + serve(wsgiapp, listen='*:8080') + +This will run waitress on port 8080 on all available IP addresses, both IPv4 +and IPv6. + + +.. code-block:: python + + from waitress impot serve serve(wsgiapp, host='0.0.0.0', port=8080) +This will run waitress on port 8080 on all available IPv4 addresses. + If you want to serve your application on all IP addresses, on port 8080, you can omit the ``host`` and ``port`` arguments and just call ``serve`` with the WSGI app as a single argument: @@ -28,6 +39,13 @@ Press Ctrl-C (or Ctrl-Break on Windows) to exit the server. +The default is to bind to any IPv4 address on port 8080: + +.. code-block:: python + + from waitress import serve + serve(wsgiapp) + If you want to serve your application through a UNIX domain socket (to serve a downstream HTTP server/proxy, e.g. nginx, lighttpd, etc.), call ``serve`` with the ``unix_socket`` argument: @@ -49,6 +67,13 @@ [server:main] use = egg:waitress#main + listen = 127.0.0.1:8080 + +Using ``host`` and ``port`` is also supported: + +.. code-block:: ini + + [server:main] host = 127.0.0.1 port = 8080 @@ -65,9 +90,15 @@ Additionally, there is a command line runner called ``waitress-serve``, which can be used in development and in situations where the likes of -:term:`PasteDeploy` is not necessary:: +:term:`PasteDeploy` is not necessary: + +.. code-block:: bash - waitress-serve --port=8041 myapp:wsgifunc + # Listen on both IPv4 and IPv6 on port 8041 + waitress-serve --listen=*:8041 myapp:wsgifunc + + # Listen on only IPv4 on port 8041 + waitress-serve --port=8041 myapp:wsgifunc For more information on this, see :ref:`runner`. @@ -153,7 +184,7 @@ .. code-block:: python from waitress import serve - serve(wsgiapp, host='0.0.0.0', port=8080, url_scheme='https') + serve(wsgiapp, listen='0.0.0.0:8080', url_scheme='https') This works if all URLs generated by your application should use the ``https`` scheme. @@ -188,7 +219,7 @@ .. code-block:: python from waitress import serve - serve(wsgiapp, host='0.0.0.0', port=8080, url_prefix='/foo') + serve(wsgiapp, listen='0.0.0.0:8080', url_prefix='/foo') Setting this to any value except the empty string will cause the WSGI ``SCRIPT_NAME`` value to be that value, minus any trailing slashes you add, and @@ -257,8 +288,7 @@ [server:main] use = egg:waitress#main - host = 127.0.0.1 - port = 8080 + listen = 127.0.0.1:8080 Note that you can also set ``PATH_INFO`` and ``SCRIPT_NAME`` using PrefixMiddleware too (its original purpose, really) instead of using Waitress' @@ -282,12 +312,11 @@ -------------- .. include:: ../CHANGES.txt +.. include:: ../HISTORY.txt Known Issues ------------ -- Does not yet support IPv6 natively. - - Does not support SSL natively. Support and Development diff -Nru waitress-0.8.10/docs/runner.rst waitress-1.0.1/docs/runner.rst --- waitress-0.8.10/docs/runner.rst 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/docs/runner.rst 2016-10-22 19:40:39.000000000 +0000 @@ -85,6 +85,31 @@ ``--port=PORT`` TCP port on which to listen, default is '8080' +``--listen=host: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. + + This affects wildcard matching when listening on a wildcard address/port + combination. + +``--[no-]ipv6`` + Toggle on/off IPv6 support. + + This affects wildcard matching when listening on a wildcard address/port + combination. + ``--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. diff -Nru waitress-0.8.10/.gitignore waitress-1.0.1/.gitignore --- waitress-0.8.10/.gitignore 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/.gitignore 2016-10-22 19:40:39.000000000 +0000 @@ -8,4 +8,7 @@ waitress/coverage.xml dist/ keep/ +build/ coverage.xml +nosetests*.xml +py*-cover.xml diff -Nru waitress-0.8.10/HISTORY.txt waitress-1.0.1/HISTORY.txt --- waitress-0.8.10/HISTORY.txt 1970-01-01 00:00:00.000000000 +0000 +++ waitress-1.0.1/HISTORY.txt 2016-10-22 19:40:39.000000000 +0000 @@ -0,0 +1,422 @@ +0.9.0 (2016-04-15) +------------------ + +Deprecations +~~~~~~~~~~~~ + +- Python 3.2 is no longer supported by Waitress. + +- Python 2.6 will no longer be supported by Waitress in future releases. + +Security/Protections +~~~~~~~~~~~~~~~~~~~~ + +- Building on the changes made in pull request 117, add in checking for line + feed/carriage return HTTP Response Splitting in the status line, as well as + the key of a header. See https://github.com/Pylons/waitress/pull/124 and + https://github.com/Pylons/waitress/issues/122. + +- Waitress will no longer accept headers or status lines with + newline/carriage returns in them, thereby disallowing HTTP Response + Splitting. See https://github.com/Pylons/waitress/issues/117 for + more information, as well as + https://www.owasp.org/index.php/HTTP_Response_Splitting. + +Bugfixes +~~~~~~~~ + +- FileBasedBuffer and more important ReadOnlyFileBasedBuffer no longer report + False when tested with bool(), instead always returning True, and becoming + more iterator like. + See: https://github.com/Pylons/waitress/pull/82 and + https://github.com/Pylons/waitress/issues/76 + +- Call prune() on the output buffer at the end of a request so that it doesn't + continue to grow without bounds. See + https://github.com/Pylons/waitress/issues/111 for more information. + +0.8.10 (2015-09-02) +------------------- + +- Add support for Python 3.4, 3.5b2, and PyPy3. + +- Use a nonglobal asyncore socket map by default, trying to prevent conflicts + with apps and libs that use the asyncore global socket map ala + https://github.com/Pylons/waitress/issues/63. You can get the old + use-global-socket-map behavior back by passing ``asyncore.socket_map`` to the + ``create_server`` function as the ``map`` argument. + +- Waitress violated PEP 3333 with respect to reraising an exception when + ``start_response`` was called with an ``exc_info`` argument. It would + reraise the exception even if no data had been sent to the client. It now + only reraises the exception if data has actually been sent to the client. + See https://github.com/Pylons/waitress/pull/52 and + https://github.com/Pylons/waitress/issues/51 + +- Add a ``docs`` section to tox.ini that, when run, ensures docs can be built. + +- If an ``application`` value of ``None`` is supplied to the ``create_server`` + constructor function, a ValueError is now raised eagerly instead of an error + occuring during runtime. See https://github.com/Pylons/waitress/pull/60 + +- Fix parsing of multi-line (folded) headers. + See https://github.com/Pylons/waitress/issues/53 and + https://github.com/Pylons/waitress/pull/90 + +- Switch from the low level Python thread/_thread module to the threading + module. + +- Improved exception information should module import go awry. + +0.8.9 (2014-05-16) +------------------ + +- Fix tests under Windows. NB: to run tests under Windows, you cannot run + "setup.py test" or "setup.py nosetests". Instead you must run ``python.exe + -c "import nose; nose.main()"``. If you try to run the tests using the + normal method under Windows, each subprocess created by the test suite will + attempt to run the test suite again. See + https://github.com/nose-devs/nose/issues/407 for more information. + +- Give the WSGI app_iter generated when ``wsgi.file_wrapper`` is used + (ReadOnlyFileBasedBuffer) a ``close`` method. Do not call ``close`` on an + instance of such a class when it's used as a WSGI app_iter, however. This is + part of a fix which prevents a leakage of file descriptors; the other part of + the fix was in WebOb + (https://github.com/Pylons/webob/commit/951a41ce57bd853947f842028bccb500bd5237da). + +- Allow trusted proxies to override ``wsgi.url_scheme`` via a request header, + ``X_FORWARDED_PROTO``. Allows proxies which serve mixed HTTP / HTTPS + requests to control signal which are served as HTTPS. See + https://github.com/Pylons/waitress/pull/42. + +0.8.8 (2013-11-30) +------------------ + +- Fix some cases where the creation of extremely large output buffers (greater + than 2GB, suspected to be buffers added via ``wsgi.file_wrapper``) might + cause an OverflowError on Python 2. See + https://github.com/Pylons/waitress/issues/47. + +- When the ``url_prefix`` adjustment starts with more than one slash, all + slashes except one will be stripped from its beginning. This differs from + older behavior where more than one leading slash would be preserved in + ``url_prefix``. + +- If a client somehow manages to send an empty path, we no longer convert the + empty path to a single slash in ``PATH_INFO``. Instead, the path remains + empty. According to RFC 2616 section "5.1.2 Request-URI", the scenario of a + client sending an empty path is actually not possible because the request URI + portion cannot be empty. + +- If the ``url_prefix`` adjustment matches the request path exactly, we now + compute ``SCRIPT_NAME`` and ``PATH_INFO`` properly. Previously, if the + ``url_prefix`` was ``/foo`` and the path received from a client was ``/foo``, + we would set *both* ``SCRIPT_NAME`` and ``PATH_INFO`` to ``/foo``. This was + incorrect. Now in such a case we set ``PATH_INFO`` to the empty string and + we set ``SCRIPT_NAME`` to ``/foo``. Note that the change we made has no + effect on paths that do not match the ``url_prefix`` exactly (such as + ``/foo/bar``); these continue to operate as they did. See + https://github.com/Pylons/waitress/issues/46 + +- Preserve header ordering of headers with the same name as per RFC 2616. See + https://github.com/Pylons/waitress/pull/44 + +- When waitress receives a ``Transfer-Encoding: chunked`` request, we no longer + send the ``TRANSFER_ENCODING`` nor the ``HTTP_TRANSFER_ENCODING`` value to + the application in the environment. Instead, we pop this header. Since we + cope with chunked requests by buffering the data in the server, we also know + when a chunked request has ended, and therefore we know the content length. + We set the content-length header in the environment, such that applications + effectively never know the original request was a T-E: chunked request; it + will appear to them as if the request is a non-chunked request with an + accurate content-length. + +- Cope with the fact that the ``Transfer-Encoding`` value is case-insensitive. + +- When the ``--unix-socket-perms`` option was used as an argument to + ``waitress-serve``, a ``TypeError`` would be raised. See + https://github.com/Pylons/waitress/issues/50. + +0.8.7 (2013-08-29) +------------------ + +- The HTTP version of the response returned by waitress when it catches an + exception will now match the HTTP request version. + +- Fix: CONNECTION header will be HTTP_CONNECTION and not CONNECTION_TYPE + (see https://github.com/Pylons/waitress/issues/13) + +0.8.6 (2013-08-12) +------------------ + +- Do alternate type of checking for UNIX socket support, instead of checking + for platform == windows. + +- Functional tests now use multiprocessing module instead of subprocess module, + speeding up test suite and making concurrent execution more reliable. + +- Runner now appends the current working directory to ``sys.path`` to support + running WSGI applications from a directory (i.e., not installed in a + virtualenv). + +- Add a ``url_prefix`` adjustment setting. You can use it by passing + ``script_name='/foo'`` to ``waitress.serve`` or you can use it in a + ``PasteDeploy`` ini file as ``script_name = /foo``. This 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. You can use this instead of + PasteDeploy's ``prefixmiddleware`` to always prefix the path. + +0.8.5 (2013-05-27) +------------------ + +- Fix runner multisegment imports in some Python 2 revisions (see + https://github.com/Pylons/waitress/pull/34). + +- For compatibility, WSGIServer is now an alias of TcpWSGIServer. The + signature of BaseWSGIServer is now compatible with WSGIServer pre-0.8.4. + +0.8.4 (2013-05-24) +------------------ + +- Add a command-line runner called ``waitress-serve`` to allow Waitress + to run WSGI applications without any addional machinery. This is + essentially a thin wrapper around the ``waitress.serve()`` function. + +- Allow parallel testing (e.g., under ``detox`` or ``nosetests --processes``) + using PID-dependent port / socket for functest servers. + +- Fix integer overflow errors on large buffers. Thanks to Marcin Kuzminski + for the patch. See: https://github.com/Pylons/waitress/issues/22 + +- Add support for listening on Unix domain sockets. + +0.8.3 (2013-04-28) +------------------ + +Features +~~~~~~~~ + +- Add an ``asyncore_loop_timeout`` adjustment value, which controls the + ``timeout`` value passed to ``asyncore.loop``; defaults to 1. + +Bug Fixes +~~~~~~~~~ + +- The default asyncore loop timeout is now 1 second. This prevents slow + shutdown on Windows. See https://github.com/Pylons/waitress/issues/6 . This + shouldn't matter to anyone in particular, but it can be changed via the + ``asyncore_loop_timeout`` adjustment (it used to previously default to 30 + seconds). + +- Don't complain if there's a response to a HEAD request that contains a + Content-Length > 0. See https://github.com/Pylons/waitress/pull/7. + +- Fix bug in HTTP Expect/Continue support. See + https://github.com/Pylons/waitress/issues/9 . + + +0.8.2 (2012-11-14) +------------------ + +Bug Fixes +~~~~~~~~~ + +- http://corte.si/posts/code/pathod/pythonservers/index.html pointed out that + sending a bad header resulted in an exception leading to a 500 response + instead of the more proper 400 response without an exception. + +- Fix a race condition in the test suite. + +- Allow "ident" to be used as a keyword to ``serve()`` as per docs. + +- Add py33 to tox.ini. + +0.8.1 (2012-02-13) +------------------ + +Bug Fixes +~~~~~~~~~ + +- A brown-bag bug prevented request concurrency. A slow request would block + subsequent the responses of subsequent requests until the slow request's + response was fully generated. This was due to a "task lock" being declared + as a class attribute rather than as an instance attribute on HTTPChannel. + Also took the opportunity to move another lock named "outbuf lock" to the + channel instance rather than the class. See + https://github.com/Pylons/waitress/pull/1 . + +0.8 (2012-01-31) +---------------- + +Features +~~~~~~~~ + +- Support the WSGI ``wsgi.file_wrapper`` protocol as per + http://www.python.org/dev/peps/pep-0333/#optional-platform-specific-file-handling. + Here's a usage example:: + + import os + + here = os.path.dirname(os.path.abspath(__file__)) + + def myapp(environ, start_response): + f = open(os.path.join(here, 'myphoto.jpg'), 'rb') + headers = [('Content-Type', 'image/jpeg')] + start_response( + '200 OK', + headers + ) + return environ['wsgi.file_wrapper'](f, 32768) + + The signature of the file wrapper constructor is ``(filelike_object, + block_size)``. Both arguments must be passed as positional (not keyword) + arguments. The result of creating a file wrapper should be **returned** as + the ``app_iter`` from a WSGI application. + + The object passed as ``filelike_object`` to the wrapper must be a file-like + object which supports *at least* the ``read()`` method, and the ``read()`` + method must support an optional size hint argument. It *should* support + the ``seek()`` and ``tell()`` methods. If it does not, normal iteration + over the filelike object using the provided block_size is used (and copying + is done, negating any benefit of the file wrapper). It *should* support a + ``close()`` method. + + The specified ``block_size`` argument to the file wrapper constructor will + be used only when the ``filelike_object`` doesn't support ``seek`` and/or + ``tell`` methods. Waitress needs to use normal iteration to serve the file + in this degenerate case (as per the WSGI spec), and this block size will be + used as the iteration chunk size. The ``block_size`` argument is optional; + if it is not passed, a default value``32768`` is used. + + Waitress will set a ``Content-Length`` header on the behalf of an + application when a file wrapper with a sufficiently filelike object is used + if the application hasn't already set one. + + The machinery which handles a file wrapper currently doesn't do anything + particularly special using fancy system calls (it doesn't use ``sendfile`` + for example); using it currently just prevents the system from needing to + copy data to a temporary buffer in order to send it to the client. No + copying of data is done when a WSGI app returns a file wrapper that wraps a + sufficiently filelike object. It may do something fancier in the future. + +0.7 (2012-01-11) +---------------- + +Features +~~~~~~~~ + +- Default ``send_bytes`` value is now 18000 instead of 9000. The larger + default value prevents asyncore from needing to execute select so many + times to serve large files, speeding up file serving by about 15%-20% or + so. This is probably only an optimization for LAN communications, and + could slow things down across a WAN (due to higher TCP overhead), but we're + likely to be behind a reverse proxy on a LAN anyway if in production. + +- Added an (undocumented) profiling feature to the ``serve()`` command. + +0.6.1 (2012-01-08) +------------------ + +Bug Fixes +~~~~~~~~~ + +- Remove performance-sapping call to ``pull_trigger`` in the channel's + ``write_soon`` method added mistakenly in 0.6. + +0.6 (2012-01-07) +---------------- + +Bug Fixes +~~~~~~~~~ + +- A logic error prevented the internal outbuf buffer of a channel from being + flushed when the client could not accept the entire contents of the output + buffer in a single succession of socket.send calls when the channel was in + a "pending close" state. The socket in such a case would be closed + prematurely, sometimes resulting in partially delivered content. This was + discovered by a user using waitress behind an Nginx reverse proxy, which + apparently is not always ready to receive data. The symptom was that he + received "half" of a large CSS file (110K) while serving content via + waitress behind the proxy. + +0.5 (2012-01-03) +---------------- + +Bug Fixes +~~~~~~~~~ + +- Fix PATH_INFO encoding/decoding on Python 3 (as per PEP 3333, tunnel + bytes-in-unicode-as-latin-1-after-unquoting). + +0.4 (2012-01-02) +---------------- + +Features +~~~~~~~~ + +- Added "design" document to docs. + +Bug Fixes +~~~~~~~~~ + +- Set default ``connection_limit`` back to 100 for benefit of maximal + platform compatibility. + +- Normalize setting of ``last_activity`` during send. + +- Minor resource cleanups during tests. + +- Channel timeout cleanup was broken. + +0.3 (2012-01-02) +---------------- + +Features +~~~~~~~~ + +- Dont hang a thread up trying to send data to slow clients. + +- Use self.logger to log socket errors instead of self.log_info (normalize). + +- Remove pointless handle_error method from channel. + +- Queue requests instead of tasks in a channel. + +Bug Fixes +~~~~~~~~~ + +- Expect: 100-continue responses were broken. + + +0.2 (2011-12-31) +---------------- + +Bug Fixes +~~~~~~~~~ + +- Set up logging by calling logging.basicConfig() when ``serve`` is called + (show tracebacks and other warnings to console by default). + +- Disallow WSGI applications to set "hop-by-hop" headers (Connection, + Transfer-Encoding, etc). + +- Don't treat 304 status responses specially in HTTP/1.1 mode. + +- Remove out of date ``interfaces.py`` file. + +- Normalize logging (all output is now sent to the ``waitress`` logger rather + than in degenerate cases some output being sent directly to stderr). + +Features +~~~~~~~~ + +- Support HTTP/1.1 ``Transfer-Encoding: chunked`` responses. + +- Slightly better docs about logging. + +0.1 (2011-12-30) +---------------- + +- Initial release. diff -Nru waitress-0.8.10/README.rst waitress-1.0.1/README.rst --- waitress-0.8.10/README.rst 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/README.rst 2016-10-22 19:40:39.000000000 +0000 @@ -1,7 +1,7 @@ Waitress is meant to be a production-quality pure-Python WSGI server with very acceptable performance. It has no dependencies except ones which live in the Python standard library. It runs on CPython on Unix and Windows under Python -2.6+ and Python 3.2+. It is also known to run on PyPy 1.6.0+ on UNIX. It +2.7+ and Python 3.3+. It is also known to run on PyPy 1.6.0+ on UNIX. It supports HTTP/1.0 and HTTP/1.1. For more information, see the "docs" directory of the Waitress package or diff -Nru waitress-0.8.10/rtd.txt waitress-1.0.1/rtd.txt --- waitress-0.8.10/rtd.txt 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/rtd.txt 2016-10-22 19:40:39.000000000 +0000 @@ -1,3 +1,3 @@ -Sphinx >= 1.2.3 +Sphinx >= 1.3.1 repoze.sphinx.autointerface pylons-sphinx-themes diff -Nru waitress-0.8.10/setup.cfg waitress-1.0.1/setup.cfg --- waitress-0.8.10/setup.cfg 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/setup.cfg 2016-10-22 19:40:39.000000000 +0000 @@ -8,6 +8,9 @@ cover-package=waitress cover-erase=1 +[bdist_wheel] +universal = 1 + [aliases] dev = develop easy_install waitress[testing] docs = develop easy_install waitress[docs] diff -Nru waitress-0.8.10/setup.py waitress-1.0.1/setup.py --- waitress-0.8.10/setup.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/setup.py 2016-10-22 19:40:39.000000000 +0000 @@ -12,7 +12,6 @@ # ############################################################################## import os -import sys from setuptools import setup, find_packages here = os.path.abspath(os.path.dirname(__file__)) @@ -33,16 +32,13 @@ 'coverage', ] -if sys.version_info[:2] == (2, 6): - testing_extras.append('unittest2') - setup( name='waitress', - version='0.8.10', + version='1.0.1', author='Zope Foundation and Contributors', author_email='zope-dev@zope.org', - maintainer="Chris McDonough", - maintainer_email="chrism@plope.com", + maintainer="Pylons Project", + maintainer_email="pylons-discuss@googlegroups.com", description='Waitress WSGI server', long_description=README + '\n\n' + CHANGES, license='ZPL 2.1', @@ -54,10 +50,8 @@ 'License :: OSI Approved :: Zope Public License', 'Programming Language :: Python', 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', diff -Nru waitress-0.8.10/tox.ini waitress-1.0.1/tox.ini --- waitress-0.8.10/tox.ini 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/tox.ini 2016-10-22 19:40:39.000000000 +0000 @@ -1,24 +1,61 @@ [tox] -envlist = - py26,py27,pypy,py32,py33,py34,py35,pypy3,cover,docs +envlist = + py27,py33,py34,py35,pypy,pypy3, + docs, + {py2,py3}-cover,coverage [testenv] -commands = - pip install waitress[testing] - nosetests --processes=4 - -[testenv:cover] +# Most of these are defaults but if you specify any you can't fall back +# to defaults for others. basepython = - python2.6 -commands = + py27: python2.7 + py33: python3.3 + py34: python3.4 + py35: python3.5 + pypy: pypy + pypy3: pypy3 + py2: python2.7 + py3: python3.5 + +commands = pip install waitress[testing] - nosetests --with-xunit --with-xcoverage -deps = - nosexcover + nosetests --with-xunit --xunit-file=nosetests-{envname}.xml {posargs:} [testenv:docs] -basepython = - python2.7 +basepython = python3.5 +whitelist_externals = make commands = pip install waitress[docs] - sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html + make -C docs html epub BUILDDIR={envdir} "SPHINXOPTS=-W -E" + +[py-cover] +commands = + pip install waitress[testing] + coverage run --source=waitress --parallel-mode {envbindir}/nosetests + coverage combine + coverage xml -o {envname}.xml + +[testenv:py2-cover] +commands = + {[py-cover]commands} +setenv = + COVERAGE_FILE=.coverage.py2 + +[testenv:py3-cover] +commands = + {[py-cover]commands} +setenv = + COVERAGE_FILE=.coverage.py3 + +[testenv:coverage] +basepython = python3.5 +commands = + coverage erase + coverage combine + coverage xml + coverage report --show-missing --fail-under=100 --omit=waitress/tests/fixtureapps/getline.py +deps = + coverage +setenv = + COVERAGE_FILE=.coverage + diff -Nru waitress-0.8.10/.travis.yml waitress-1.0.1/.travis.yml --- waitress-0.8.10/.travis.yml 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/.travis.yml 2016-10-22 19:40:39.000000000 +0000 @@ -2,15 +2,22 @@ language: python sudo: false -env: - - TOXENV=py26 - - TOXENV=py27 - - TOXENV=py32 - - TOXENV=py33 - - TOXENV=py34 - - TOXENV=pypy - - TOXENV=pypy3 - - TOXENV=cover +matrix: + include: + - python: 2.7 + env: TOXENV=py27 + - python: 3.3 + env: TOXENV=py33 + - python: 3.4 + env: TOXENV=py34 + - python: 3.5 + env: TOXENV=py35 + - python: pypy + env: TOXENV=pypy + - python: pypy3 + env: TOXENV=pypy3 + - python: 3.5 + env: TOXENV=py2-cover,py3-cover,coverage install: - travis_retry pip install tox @@ -21,3 +28,6 @@ notifications: email: - pyramid-checkins@lists.repoze.org + irc: + channels: + - "chat.freenode.net#pyramid" diff -Nru waitress-0.8.10/waitress/adjustments.py waitress-1.0.1/waitress/adjustments.py --- waitress-0.8.10/waitress/adjustments.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/adjustments.py 2016-10-22 19:40:39.000000000 +0000 @@ -15,7 +15,13 @@ """ import getopt import socket -import sys + +from waitress.compat import ( + PY2, + WIN, + string_types, + HAS_IPV6, + ) truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1')) @@ -36,6 +42,22 @@ """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: @@ -44,6 +66,12 @@ s = '/' + s.lstrip('/').rstrip('/') return s +class _str_marker(str): + pass + +class _int_marker(int): + pass + class Adjustments(object): """This class contains tunable parameters. """ @@ -51,6 +79,9 @@ _params = ( ('host', str), ('port', int), + ('ipv4', asbool), + ('ipv6', asbool), + ('listen', aslist), ('threads', int), ('trusted_proxy', str), ('url_scheme', str), @@ -77,10 +108,12 @@ _param_map = dict(_params) # hostname or IP address to listen on - host = '0.0.0.0' + host = _str_marker('0.0.0.0') # TCP port to listen on - port = 8080 + port = _int_marker(8080) + + listen = ['{}:{}'.format(host, port)] # mumber of threads available for tasks threads = 4 @@ -174,14 +207,96 @@ # 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 (sys.platform[:3] == "win" and - self.host == 'localhost'): # pragma: no cover - self.host = '' + + 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): @@ -203,9 +318,15 @@ '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' @@ -215,4 +336,5 @@ kw[param] = 'true' else: kw[param] = value + return kw, args diff -Nru waitress-0.8.10/waitress/buffers.py waitress-1.0.1/waitress/buffers.py --- waitress-0.8.10/waitress/buffers.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/buffers.py 2016-10-22 19:40:39.000000000 +0000 @@ -44,7 +44,7 @@ return self.remain def __nonzero__(self): - return self.remain > 0 + return True __bool__ = __nonzero__ # py3 diff -Nru waitress-0.8.10/waitress/channel.py waitress-1.0.1/waitress/channel.py --- waitress-0.8.10/waitress/channel.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/channel.py 2016-10-22 19:40:39.000000000 +0000 @@ -249,6 +249,8 @@ '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: diff -Nru waitress-0.8.10/waitress/compat.py waitress-1.0.1/waitress/compat.py --- waitress-0.8.10/waitress/compat.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/compat.py 2016-10-22 19:40:39.000000000 +0000 @@ -1,5 +1,7 @@ import sys import types +import platform +import warnings try: import urlparse @@ -7,8 +9,12 @@ 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, @@ -109,3 +115,26 @@ 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 -Nru waitress-0.8.10/waitress/__init__.py waitress-1.0.1/waitress/__init__.py --- waitress-0.8.10/waitress/__init__.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/__init__.py 2016-10-22 19:40:39.000000000 +0000 @@ -10,8 +10,7 @@ logging.basicConfig() server = _server(app, **kw) if not _quiet: # pragma: no cover - print('serving on http://%s:%s' % (server.effective_host, - server.effective_port)) + server.print_listen('Serving on http://{}:{}') if _profile: # pragma: no cover profile('server.run()', globals(), locals(), (), False) else: diff -Nru waitress-0.8.10/waitress/parser.py waitress-1.0.1/waitress/parser.py --- waitress-0.8.10/waitress/parser.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/parser.py 2016-10-22 19:40:39.000000000 +0000 @@ -182,6 +182,8 @@ 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 diff -Nru waitress-0.8.10/waitress/runner.py waitress-1.0.1/waitress/runner.py --- waitress-0.8.10/waitress/runner.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/runner.py 2016-10-22 19:40:39.000000000 +0000 @@ -42,9 +42,46 @@ 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. diff -Nru waitress-0.8.10/waitress/server.py waitress-1.0.1/waitress/server.py --- waitress-0.8.10/waitress/server.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/server.py 2016-10-22 19:40:39.000000000 +0000 @@ -22,7 +22,14 @@ 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.utilities import ( + cleanup_unix_socket, + logging_dispatcher, + ) +from waitress.compat import ( + IPPROTO_IPV6, + IPV6_V6ONLY, + ) def create_server(application, map=None, @@ -42,11 +49,90 @@ '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'): - cls = UnixWSGIServer - else: - cls = TcpWSGIServer - return cls(application, map, _start, _sock, _dispatcher, adj) + 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): @@ -54,15 +140,15 @@ next_channel_cleanup = 0 socketmod = socket # test shim asyncore = asyncore # test shim - family = None def __init__(self, application, map=None, _start=True, # test shim _sock=None, # test shim - _dispatcher=None, # test shim + dispatcher=None, # dispatcher adj=None, # adjustments + sockinfo=None, # opaque object **kw ): if adj is None: @@ -72,20 +158,30 @@ # 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 + 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, socket.SOCK_STREAM) + 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.adj.host) + self.server_name = self.get_server_name(self.effective_host) self.active_channels = {} if _start: self.accept_connections() @@ -99,12 +195,13 @@ 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': + 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 @@ -186,25 +283,51 @@ if (not channel.requests) and channel.last_activity < cutoff: channel.will_close = True -class TcpWSGIServer(BaseWSGIServer): + def print_listen(self, format_str): # pragma: nocover + print(format_str.format(self.effective_host, self.effective_port)) - family = socket.AF_INET + +class TcpWSGIServer(BaseWSGIServer): def bind_server_socket(self): - self.bind((self.adj.host, self.adj.port)) + (_, _, _, sockaddr) = self.sockinfo + self.bind(sockaddr) def getsockname(self): - return self.socket.getsockname() + return self.socketmod.getnameinfo( + self.socket.getsockname(), + 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): - family = socket.AF_UNIX + 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) diff -Nru waitress-0.8.10/waitress/task.py waitress-1.0.1/waitress/task.py --- waitress-0.8.10/waitress/task.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/task.py 2016-10-22 19:40:39.000000000 +0000 @@ -358,6 +358,9 @@ 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 @@ -371,6 +374,14 @@ 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) diff -Nru waitress-0.8.10/waitress/tests/test_adjustments.py waitress-1.0.1/waitress/tests/test_adjustments.py --- waitress-0.8.10/waitress/tests/test_adjustments.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/tests/test_adjustments.py 2016-10-22 19:40:39.000000000 +0000 @@ -1,4 +1,10 @@ import sys +import socket + +from waitress.compat import ( + PY2, + WIN, + ) if sys.version_info[:2] == (2, 6): # pragma: no cover import unittest2 as unittest @@ -45,13 +51,35 @@ 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='host', + host='localhost', port='8080', threads='5', trusted_proxy='192.168.1.1', @@ -74,8 +102,11 @@ unix_socket='/tmp/waitress.sock', unix_socket_perms='777', url_prefix='///foo/', + ipv4=True, + ipv6=False, ) - self.assertEqual(inst.host, 'host') + + self.assertEqual(inst.host, 'localhost') self.assertEqual(inst.port, 8080) self.assertEqual(inst.threads, 5) self.assertEqual(inst.trusted_proxy, '192.168.1.1') @@ -98,10 +129,96 @@ 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): @@ -147,10 +264,31 @@ self.assertDictContainsSubset({ 'host': 'localhost', 'port': '80', - 'unix_socket_perms':'777', + '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 -Nru waitress-0.8.10/waitress/tests/test_buffers.py waitress-1.0.1/waitress/tests/test_buffers.py --- waitress-0.8.10/waitress/tests/test_buffers.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/tests/test_buffers.py 2016-10-22 19:40:39.000000000 +0000 @@ -31,7 +31,7 @@ inst.remain = 10 self.assertEqual(bool(inst), True) inst.remain = 0 - self.assertEqual(bool(inst), False) + self.assertEqual(bool(inst), True) def test_append(self): f = io.BytesIO(b'data') diff -Nru waitress-0.8.10/waitress/tests/test_functional.py waitress-1.0.1/waitress/tests/test_functional.py --- waitress-0.8.10/waitress/tests/test_functional.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/tests/test_functional.py 2016-10-22 19:40:39.000000000 +0000 @@ -34,6 +34,8 @@ """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. @@ -1386,6 +1388,8 @@ """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. diff -Nru waitress-0.8.10/waitress/tests/test_parser.py waitress-1.0.1/waitress/tests/test_parser.py --- waitress-0.8.10/waitress/tests/test_parser.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/tests/test_parser.py 2016-10-22 19:40:39.000000000 +0000 @@ -408,9 +408,24 @@ self.assertEqual(self.parser.headers, { 'CONTENT_LENGTH': '7', 'X_FORWARDED_FOR': - '10.11.12.13, unknown,127.0.0.1, 255.255.255.255', + '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): diff -Nru waitress-0.8.10/waitress/tests/test_server.py waitress-1.0.1/waitress/tests/test_server.py --- waitress-0.8.10/waitress/tests/test_server.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/tests/test_server.py 2016-10-22 19:40:39.000000000 +0000 @@ -34,10 +34,23 @@ _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) @@ -72,6 +85,10 @@ 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() @@ -79,6 +96,13 @@ 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() @@ -242,6 +266,16 @@ [(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 diff -Nru waitress-0.8.10/waitress/tests/test_task.py waitress-1.0.1/waitress/tests/test_task.py --- waitress-0.8.10/waitress/tests/test_task.py 2015-09-02 16:08:38.000000000 +0000 +++ waitress-1.0.1/waitress/tests/test_task.py 2016-10-22 19:40:39.000000000 +0000 @@ -409,6 +409,27 @@ 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')])