summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTres Seaver <tseaver@palladion.com>2013-09-03 14:32:41 -0400
committerTres Seaver <tseaver@palladion.com>2013-09-03 14:32:41 -0400
commit06df318f7a2b0106333e5893572a1fd30333f663 (patch)
tree91b401c598985ad1efedf54187c0234bee60807f
parentc957f1d70ab82ba15ce0dce8fca7cf70fe2f2b97 (diff)
downloadwaitress-06df318f7a2b0106333e5893572a1fd30333f663.tar.gz
Override '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.
-rw-r--r--CHANGES.txt7
-rw-r--r--docs/arguments.rst3
-rw-r--r--docs/differences.rst3
-rw-r--r--docs/index.rst22
-rw-r--r--waitress/task.py6
-rw-r--r--waitress/tests/test_task.py46
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):