fix: WSGI apps must not set hop-by-hop headers per PEP 333
This commit is contained in:
5
NOTES.md
5
NOTES.md
@@ -1,7 +1,10 @@
|
||||
* Keep-Alive is intentially disabled for HTTP/1.0 clients to mitigate problems with proxies. See also http://tools.ietf.org/html/rfc2616#section-19.6.2
|
||||
* resp.set_header assumes second param is a string. App may crash otherwise.
|
||||
* Don't set content-length. It will only be overridden.
|
||||
* Header names are case-insensitive in req.get_header
|
||||
* Set body to a byte string, as per PEP 333 - http://www.python.org/dev/peps/pep-0333/#unicode-issues - if it is textual, it's up to the app to set the proper media type
|
||||
* If both body and stream are set, body will be used
|
||||
* For streaming large items, assign a generator to resp.stream that yields data in reasonable chunk sizes (what's a reasonable size?). If stream is set, Falcon will assume Transfer-Encoding: chunked unless you specify stream\_len, in which case Falcon will set Content-Length to stream\_len. Be sure stream_len is accurate; if you lie about it, then clients and possibly your WSGI server) will hang until the connection times out, waiting for stream to generate enough data.
|
||||
* For streaming large items, assign a generator to resp.stream that yields data in reasonable chunk sizes (what's a reasonable size?). Falcon will then leave off the Content-Length header, and hopefully your WSGI server will do the right thing, assuming you've told it to enable keep-alive (PEP-333 prohibits apps from setting hop-by-hop headers itself, such as Transfer-Encoding).
|
||||
* If you know the size of the stream in advance, set stream\_len and Falcon will use it to set Content-Length, avoiding the whole chunked encoding issue altogether.
|
||||
|
||||
|
||||
|
||||
@@ -57,22 +57,10 @@ class Api:
|
||||
|
||||
# Set Content-Length when given a fully-buffered body
|
||||
if resp.body is not None:
|
||||
resp.set_header('Content-Length', len(resp.body))
|
||||
resp.set_header('Content-Length', str(len(resp.body)))
|
||||
elif resp.stream is not None:
|
||||
# TODO: Transfer-Encoding: chunked
|
||||
# TODO: if resp.stream_len is not None, don't use chunked
|
||||
pass
|
||||
else:
|
||||
resp.set_header('Content-Length', 0)
|
||||
|
||||
|
||||
# Enable Keep-Alive when appropriate
|
||||
if env['SERVER_PROTOCOL'] != 'HTTP/1.0':
|
||||
if req.get_header('Connection') == 'close':
|
||||
connection = 'close'
|
||||
else:
|
||||
connection = 'Keep-Alive'
|
||||
|
||||
resp.set_header('Connection', connection)
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,5 @@ class Response:
|
||||
def set_header(self, name, value):
|
||||
self._headers[name] = str(value)
|
||||
|
||||
#TODO: Add some helper functions and test them
|
||||
|
||||
def _wsgi_headers(self):
|
||||
return [t for t in self._headers.items()]
|
||||
return self._headers.items()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
def application(environ, start_response):
|
||||
start_response("200 OK", [('Content-Type', 'text/plain')])
|
||||
start_response("200 OK", [
|
||||
('Content-Type', 'text/plain')])
|
||||
|
||||
body = '\n{\n'
|
||||
for key, value in environ.items():
|
||||
@@ -9,8 +10,9 @@ def application(environ, start_response):
|
||||
|
||||
body += '}\n\n'
|
||||
|
||||
return body
|
||||
return [body]
|
||||
|
||||
app = application
|
||||
|
||||
if __name__ == '__main__':
|
||||
from wsgiref.simple_server import make_server
|
||||
|
||||
@@ -56,50 +56,23 @@ def rand_string(min, max):
|
||||
return ''.join([c for c in RandChars(min, max)])
|
||||
|
||||
def create_environ(path='/', query_string='', protocol='HTTP/1.1', headers=None):
|
||||
|
||||
env = {
|
||||
'SERVER_SOFTWARE': 'WSGIServer/0.1 Python/2.7.3',
|
||||
'TERM_PROGRAM_VERSION': '309',
|
||||
'REQUEST_METHOD': 'GET',
|
||||
'SERVER_PROTOCOL': protocol,
|
||||
'HOME': '/Users/kurt',
|
||||
'DISPLAY': '/tmp/launch-j5GrQm/org.macosforge.xquartz:0',
|
||||
'TERM_PROGRAM': 'Apple_Terminal',
|
||||
'LANG': 'en_US.UTF-8',
|
||||
'SHELL': '/bin/bash',
|
||||
'_': '/Library/Frameworks/Python.framework/Versions/2.7/bin/python',
|
||||
'SERVER_PORT': '8003',
|
||||
'HTTP_HOST': 'localhost:8003',
|
||||
'SCRIPT_NAME': '',
|
||||
'HTTP_ACCEPT': '*/*',
|
||||
'wsgi.version': '(1, 0)',
|
||||
'wsgi.run_once': 'False',
|
||||
'wsgi.multiprocess': 'False',
|
||||
'__CF_USER_TEXT_ENCODING': '0x1F5:0:0',
|
||||
'USER': 'kurt',
|
||||
'LOGNAME': 'kurt',
|
||||
'PATH_INFO': path,
|
||||
'QUERY_STRING': query_string,
|
||||
'HTTP_USER_AGENT': 'curl/7.24.0 (x86_64-apple-darwin12.0) '
|
||||
'libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5',
|
||||
'SERVER_NAME': 'WSGIRef',
|
||||
'REMOTE_ADDR': '127.0.0.1',
|
||||
'SHLVL': '1',
|
||||
'wsgi.url_scheme': 'http',
|
||||
'CONTENT_LENGTH': '',
|
||||
'TERM_SESSION_ID': '51EE7744-E45F-455C-AC2E-E232A521094D',
|
||||
'SSH_AUTH_SOCK': '/tmp/launch-0N2o9o/Listeners',
|
||||
'Apple_PubSub_Socket_Render': '/tmp/launch-2ZwqSM/Render',
|
||||
'wsgi.multithread': 'True',
|
||||
'TMPDIR': '/var/folders/4g/mzp3qmyn33v_xjn1h69y1d3h0000gn/T/',
|
||||
'LSCOLORS': 'ExCxFxDxBxegedabagaced',
|
||||
'GATEWAY_INTERFACE': 'CGI/1.1',
|
||||
'CLICOLOR': '1',
|
||||
'Apple_Ubiquity_Message': '/tmp/launch-NTB6Mp/Apple_Ubiquity_Message',
|
||||
'PWD': '/Users/kurt/Projects/rax/falcon',
|
||||
'CONTENT_TYPE': 'text/plain',
|
||||
'wsgi.file_wrapper': 'wsgiref.util.FileWrapper',
|
||||
'REMOTE_HOST': '1.0.0.127.in-addr.arpa',
|
||||
'COMMAND_MODE': 'unix2003'
|
||||
"SERVER_PROTOCOL": protocol,
|
||||
"SERVER_SOFTWARE": "gunicorn/0.17.0",
|
||||
"SCRIPT_NAME": "",
|
||||
"REQUEST_METHOD": "GET",
|
||||
"HTTP_HOST": "localhost:8000",
|
||||
"PATH_INFO": path,
|
||||
"QUERY_STRING": query_string,
|
||||
"HTTP_ACCEPT": "*/*",
|
||||
"HTTP_USER_AGENT": "curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5",
|
||||
"REMOTE_PORT": "65133",
|
||||
"RAW_URI": "/",
|
||||
"REMOTE_ADDR": "127.0.0.1",
|
||||
"wsgi.url_scheme": "http",
|
||||
"SERVER_NAME": "localhost",
|
||||
"SERVER_PORT": "8000",
|
||||
}
|
||||
|
||||
if headers is not None:
|
||||
|
||||
@@ -49,39 +49,3 @@ class TestHeaders(helpers.TestSuite):
|
||||
content_length = str(len(self.on_hello.sample_body))
|
||||
content_length_header = ('Content-Length', content_length)
|
||||
self.assertThat(headers, Contains(content_length_header))
|
||||
|
||||
def test_keep_alive_http_1_1(self):
|
||||
self._simulate_request(self.test_route, protocol='HTTP/1.1')
|
||||
headers = self.srmock.headers
|
||||
|
||||
# Test Keep-Alive assumed on by default (HTTP/1.1)
|
||||
connection_header = ('Connection', 'Keep-Alive')
|
||||
self.assertThat(headers, Contains(connection_header))
|
||||
|
||||
def test_no_keep_alive_http_1_1(self):
|
||||
req_headers = {'Connection': 'close'}
|
||||
self._simulate_request(self.test_route, protocol='HTTP/1.1',
|
||||
headers=req_headers)
|
||||
headers = self.srmock.headers
|
||||
|
||||
# Test Keep-Alive assumed on by default (HTTP/1.1)
|
||||
connection_header = ('Connection', 'Keep-Alive')
|
||||
self.assertThat(headers, Not(Contains(connection_header)))
|
||||
|
||||
def test_no_implicit_keep_alive_http_1_0(self):
|
||||
self._simulate_request(self.test_route, protocol='HTTP/1.0')
|
||||
headers = self.srmock.headers
|
||||
|
||||
# Test Keep-Alive assumed on by default (HTTP/1.1)
|
||||
connection_header = ('Connection', 'Keep-Alive')
|
||||
self.assertThat(headers, Not(Contains(connection_header)))
|
||||
|
||||
def test_no_explicit_keep_alive_http_1_0(self):
|
||||
req_headers = {'Connection': 'Keep-Alive'}
|
||||
self._simulate_request(self.test_route, protocol='HTTP/1.0',
|
||||
headers=req_headers)
|
||||
headers = self.srmock.headers
|
||||
|
||||
# Test Keep-Alive assumed on by default (HTTP/1.1)
|
||||
connection_header = ('Connection', 'Keep-Alive')
|
||||
self.assertThat(headers, Not(Contains(connection_header)))
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
Functionality
|
||||
|
||||
* Test sending/receiving various status codes
|
||||
* Chunked transfer encoding, streaming 0, some, and lots of bytes
|
||||
* Add tips to NOTES.md
|
||||
* Check that Transfer-Encoding is set unless stream\_len is specified. In the latter case, Content-Length must be tested and found to be equal to stream\_len
|
||||
transfer encoding
|
||||
* Ensure req dict has things the app needs, including path, user agent, various headers.
|
||||
* Test default http status code (200 OK?)
|
||||
* Test compiling routes, throwing on invalid routes (such as missing initial forward slash or non-ascii)
|
||||
@@ -30,3 +28,4 @@ Performance
|
||||
* Test inlining functions, maybe make a tool that does this automatically
|
||||
* Try using a closure to generate the WSGI request handler (vs. a class)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user