diff options
-rw-r--r-- | CHANGES.txt | 7 | ||||
-rw-r--r-- | docs/arguments.rst | 3 | ||||
-rw-r--r-- | docs/differences.rst | 3 | ||||
-rw-r--r-- | docs/index.rst | 22 | ||||
-rw-r--r-- | waitress/task.py | 6 | ||||
-rw-r--r-- | waitress/tests/test_task.py | 46 |
6 files changed, 82 insertions, 5 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 8718f1b..8ae234c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,10 @@ +Unreleased +---------- + +- Allow override of ``wdgi.url_scheme`` via a request header, + ``X_WSGI_URL_SCHEME``. Allows proxies which serve mixed HTTP / HTTPS + requests to control signal which are served as HTTPS. + 0.8.7 (2013-08-29) ------------------ diff --git a/docs/arguments.rst b/docs/arguments.rst index 5e75db3..16936ab 100644 --- a/docs/arguments.rst +++ b/docs/arguments.rst @@ -29,7 +29,8 @@ threads ``4`` url_scheme - default ``wsgi.url_scheme`` value (string), default ``http`` + default ``wsgi.url_scheme`` value (string), default ``http``; can be + overridden per-request by the value of the ``X_WSGI_URL_SCHEME`` header. ident server identity (string) used in "Server:" header in responses, default diff --git a/docs/differences.rst b/docs/differences.rst index 07783fa..e2279c3 100644 --- a/docs/differences.rst +++ b/docs/differences.rst @@ -13,6 +13,9 @@ Differences from ``zope.server`` - Calls "close()" on the app_iter object returned by the WSGI application. +- ALlows per-request override of ``wsgi.url_scheme`` by the value of the + ``X_WSGI_URL_SCHEME`` header + - Supports an explicit ``wsgi.url_scheme`` parameter for ease of deployment behind SSL proxies. diff --git a/docs/index.rst b/docs/index.rst index c09566f..b5ddbd2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -119,12 +119,16 @@ which start with ``https://``), the URLs generated by your application when used behind a reverse proxy served by Waitress might inappropriately be ``http://foo`` rather than ``https://foo``. To fix this, you'll want to change the ``wsgi.url_scheme`` in the WSGI environment before it reaches your -application. You can do this in one of two ways: +application. You can do this in one of three ways: 1. You can pass a ``url_scheme`` configuration variable to the ``waitress.serve`` function. -2. You can use Paste's ``PrefixMiddleware`` in conjunction with +2. You can configure the proxy reverse server to pass a header, + ``X_WSGI_URL_SCHEME``, whose value will be set for that request as + the ``wsgi.url_scheme`` environment value. + +3. You can use Paste's ``PrefixMiddleware`` in conjunction with configuration settings on the reverse proxy server. Using ``url_scheme`` to set ``wsgi.url_scheme`` @@ -138,6 +142,20 @@ You can have the Waitress server use the ``https`` url scheme by default.:: This works if all URLs generated by your application should use the ``https`` scheme. +Passing the ``X_WSGI_URL_SCHEME`` header to set ``wsgi.url_scheme`` +------------------------------------------------------------------- + +If your proxy accepts both HTTP and HTTPS URLs, and you want your application +to generate the appropriate url based on the incoming scheme, also set up +your proxy to send a ``X-WSGI-Scheme`` with the original URL scheme along +with each proxied request. For example, when using Nginx:: + + proxy_set_header X-WSGI-Scheme $scheme; + +or via Apache:: + + RequestHeader set X-WSGI-Scheme https + Using ``url_prefix`` to influence ``SCRIPT_NAME`` and ``PATH_INFO`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/waitress/task.py b/waitress/task.py index 3031de5..69a3f89 100644 --- a/waitress/task.py +++ b/waitress/task.py @@ -466,7 +466,9 @@ class WSGITask(Task): environ['QUERY_STRING'] = request.query environ['REMOTE_ADDR'] = channel.addr[0] - for key, value in request.headers.items(): + headers = dict(request.headers) + wsgi_url_scheme = headers.pop('X_WSGI_URL_SCHEME', request.url_scheme) + for key, value in headers.items(): value = value.strip() mykey = rename_headers.get(key, None) if mykey is None: @@ -476,7 +478,7 @@ class WSGITask(Task): # the following environment variables are required by the WSGI spec environ['wsgi.version'] = (1, 0) - environ['wsgi.url_scheme'] = request.url_scheme + 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 diff --git a/waitress/tests/test_task.py b/waitress/tests/test_task.py index 43fe321..feb1f1e 100644 --- a/waitress/tests/test_task.py +++ b/waitress/tests/test_task.py @@ -630,6 +630,52 @@ class TestWSGITask(unittest.TestCase): self.assertEqual(environ['wsgi.input'], 'stream') self.assertEqual(inst.environ, environ) + def test_get_environment_values_w_scheme_override(self): + import sys + inst = self._makeOne() + request = DummyParser() + request.headers = { + 'CONTENT_TYPE': 'abc', + 'CONTENT_LENGTH': '10', + 'X_FOO': 'BAR', + 'X_WSGI_URL_SCHEME': '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'], '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'], '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) + class TestErrorTask(unittest.TestCase): def _makeOne(self, channel=None, request=None): |