diff --git a/NOTES.md b/NOTES.md index b37b2b5..35ecf15 100644 --- a/NOTES.md +++ b/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. diff --git a/falcon/api.py b/falcon/api.py index 3f9a335..1d94cd0 100644 --- a/falcon/api.py +++ b/falcon/api.py @@ -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) - - diff --git a/falcon/response.py b/falcon/response.py index 0ac83df..32d95e7 100644 --- a/falcon/response.py +++ b/falcon/response.py @@ -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() diff --git a/test/dump_wsgi.py b/test/dump_wsgi.py index 748ccf7..e384b84 100644 --- a/test/dump_wsgi.py +++ b/test/dump_wsgi.py @@ -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 diff --git a/test/helpers.py b/test/helpers.py index 16f4785..94b5a51 100644 --- a/test/helpers.py +++ b/test/helpers.py @@ -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: diff --git a/test/test_headers.py b/test/test_headers.py index 68e6d3a..d7bcf69 100644 --- a/test/test_headers.py +++ b/test/test_headers.py @@ -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))) diff --git a/test/todo.md b/test/todo.md index f451e5e..d9f43bc 100644 --- a/test/todo.md +++ b/test/todo.md @@ -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) @@ -29,4 +27,5 @@ 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) +