From 34903f9e1a99441b2729bbe6f1d65d46cf352ea7 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sun, 14 Feb 2016 19:04:27 -0500 Subject: [PATCH] Fix HTTP1Connection for responses without content-length Fixes #1598 --- tornado/http1connection.py | 4 +- tornado/test/http1connection_test.py | 61 ++++++++++++++++++++++++++++ tornado/test/runtests.py | 1 + 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tornado/test/http1connection_test.py diff --git a/tornado/http1connection.py b/tornado/http1connection.py index 71f0790d45..7632214544 100644 --- a/tornado/http1connection.py +++ b/tornado/http1connection.py @@ -481,7 +481,9 @@ def _can_keep_alive(self, start_line, headers): return connection_header != "close" elif ("Content-Length" in headers or headers.get("Transfer-Encoding", "").lower() == "chunked" - or start_line.method in ("HEAD", "GET")): + or getattr(start_line, 'method', None) in ("HEAD", "GET")): + # start_line may be a request or reponse start line; only + # the former has a method attribute. return connection_header == "keep-alive" return False diff --git a/tornado/test/http1connection_test.py b/tornado/test/http1connection_test.py new file mode 100644 index 0000000000..815051b91d --- /dev/null +++ b/tornado/test/http1connection_test.py @@ -0,0 +1,61 @@ +from __future__ import absolute_import, division, print_function, with_statement + +import socket + +from tornado.http1connection import HTTP1Connection +from tornado.httputil import HTTPMessageDelegate +from tornado.iostream import IOStream +from tornado.locks import Event +from tornado.netutil import add_accept_handler +from tornado.testing import AsyncTestCase, bind_unused_port, gen_test + + +class HTTP1ConnectionTest(AsyncTestCase): + def setUp(self): + super(HTTP1ConnectionTest, self).setUp() + self.asyncSetUp() + + @gen_test + def asyncSetUp(self): + listener, port = bind_unused_port() + event = Event() + + def accept_callback(conn, addr): + self.server_stream = IOStream(conn) + self.addCleanup(self.server_stream.close) + event.set() + + add_accept_handler(listener, accept_callback) + self.client_stream = IOStream(socket.socket()) + self.addCleanup(self.client_stream.close) + yield [self.client_stream.connect(('127.0.0.1', port)), + event.wait()] + self.io_loop.remove_handler(listener) + listener.close() + + @gen_test + def test_http10_no_content_length(self): + # Regression test for a bug in which can_keep_alive would crash + # for an HTTP/1.0 (not 1.1) response with no content-length. + conn = HTTP1Connection(self.client_stream, True) + self.server_stream.write(b"HTTP/1.0 200 Not Modified\r\n\r\nhello") + self.server_stream.close() + + event = Event() + test = self + body = [] + + class Delegate(HTTPMessageDelegate): + def headers_received(self, start_line, headers): + test.code = start_line.code + + def data_received(self, data): + body.append(data) + + def finish(self): + event.set() + + yield conn.read_response(Delegate()) + yield event.wait() + self.assertEqual(self.code, 200) + self.assertEqual(b''.join(body), b'hello') diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py index ad9b0b8357..3b22d396eb 100644 --- a/tornado/test/runtests.py +++ b/tornado/test/runtests.py @@ -29,6 +29,7 @@ 'tornado.test.curl_httpclient_test', 'tornado.test.escape_test', 'tornado.test.gen_test', + 'tornado.test.http1connection_test', 'tornado.test.httpclient_test', 'tornado.test.httpserver_test', 'tornado.test.httputil_test',