From: Alek Storm Date: Sun, 1 Jul 2012 03:40:14 +0000 (-0400) Subject: Add ability to get/set HTTP response reason phrase in RequestHandler and HTTPResponse X-Git-Tag: v3.0.0~274 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=481c24404b9aefe2e8d222225c871de65bf9f0df;p=thirdparty%2Ftornado.git Add ability to get/set HTTP response reason phrase in RequestHandler and HTTPResponse --- diff --git a/tornado/httpclient.py b/tornado/httpclient.py index 0fcc943f9..7426ea52d 100644 --- a/tornado/httpclient.py +++ b/tornado/httpclient.py @@ -331,11 +331,13 @@ class HTTPResponse(object): * code: numeric HTTP status code, e.g. 200 or 404 + * reason: human-readable reason phrase describing the status code + * headers: httputil.HTTPHeaders object * buffer: cStringIO object for response body - * body: respose body as string (created on demand from self.buffer) + * body: response body as string (created on demand from self.buffer) * error: Exception object, if any @@ -347,11 +349,12 @@ class HTTPResponse(object): plus 'queue', which is the delay (if any) introduced by waiting for a slot under AsyncHTTPClient's max_clients setting. """ - def __init__(self, request, code, headers=None, buffer=None, + def __init__(self, request, code, reason=None, headers=None, buffer=None, effective_url=None, error=None, request_time=None, time_info=None): self.request = request self.code = code + self.reason = reason if headers is not None: self.headers = headers else: diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py index c6e8a3a7c..ab70b519e 100644 --- a/tornado/simple_httpclient.py +++ b/tornado/simple_httpclient.py @@ -333,9 +333,10 @@ class _HTTPConnection(object): def _on_headers(self, data): data = native_str(data.decode("latin1")) first_line, _, header_data = data.partition("\n") - match = re.match("HTTP/1.[01] ([0-9]+)", first_line) + match = re.match("HTTP/1.[01] ([0-9]+) ([^\r]*)", first_line) assert match self.code = int(match.group(1)) + self.reason = match.group(2) self.headers = HTTPHeaders.parse(header_data) if "Content-Length" in self.headers: @@ -426,7 +427,8 @@ class _HTTPConnection(object): else: buffer = BytesIO(data) # TODO: don't require one big string? response = HTTPResponse(original_request, - self.code, headers=self.headers, + self.code, reason=self.reason, + headers=self.headers, request_time=time.time() - self.start_time, buffer=buffer, effective_url=self.request.url) diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index ad4173365..e0f3e7a0f 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -447,6 +447,12 @@ class HeaderInjectionHandler(RequestHandler): raise +class StatusHandler(RequestHandler): + def get(self): + reason = self.request.arguments.get('reason', []) + self.set_status(int(self.get_argument('code')), reason=reason[0] if reason else None) + + # This test is shared with wsgi_test.py class WSGISafeWebTest(AsyncHTTPTestCase, LogTrapTestCase): COOKIE_SECRET = "WebTest.COOKIE_SECRET" @@ -483,6 +489,7 @@ class WSGISafeWebTest(AsyncHTTPTestCase, LogTrapTestCase): url("/multi_header", MultiHeaderHandler), url("/redirect", RedirectHandler), url("/header_injection", HeaderInjectionHandler), + url("/status", StatusHandler), ] return urls @@ -587,6 +594,19 @@ js_embed() response = self.fetch("/header_injection") self.assertEqual(response.body, b("ok")) + def test_status(self): + response = self.fetch("/status?code=304") + self.assertEqual(response.code, 304) + self.assertEqual(response.reason, "Not Modified") + response = self.fetch("/status?code=304&reason=Foo") + self.assertEqual(response.code, 304) + self.assertEqual(response.reason, "Foo") + response = self.fetch("/status?code=682&reason=Bar") + self.assertEqual(response.code, 682) + self.assertEqual(response.reason, "Bar") + response = self.fetch("/status?code=682") + self.assertEqual(response.code, 500) + class NonWSGIWebTests(AsyncHTTPTestCase, LogTrapTestCase): def get_app(self): diff --git a/tornado/web.py b/tornado/web.py index a9bc5046f..090613d67 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -227,6 +227,7 @@ class RequestHandler(object): self.set_header("Connection", "Keep-Alive") self._write_buffer = [] self._status_code = 200 + self._reason = None def set_default_headers(self): """Override this to set HTTP headers at the beginning of the request. @@ -238,10 +239,17 @@ class RequestHandler(object): """ pass - def set_status(self, status_code): - """Sets the status code for our response.""" - assert status_code in httplib.responses + def set_status(self, status_code, reason=None): + """Sets the status code for our response. + + :arg int status_code: Response status code. If `reason` is ``None``, + it must be present in `httplib.responses`. + :arg string reason: Human-readable reason phrase describing the status + code. If ``None``, it will be filled in from `httplib.responses`. + """ + assert reason is not None or status_code in httplib.responses self._status_code = status_code + self._reason = reason def get_status(self): """Returns the status code for our response.""" @@ -1025,9 +1033,12 @@ class RequestHandler(object): self._handle_request_exception(e) def _generate_headers(self): + reason = self._reason + if reason is None: + reason = httplib.responses[self._status_code] lines = [utf8(self.request.version + " " + str(self._status_code) + - " " + httplib.responses[self._status_code])] + " " + reason)] lines.extend([(utf8(n) + b(": ") + utf8(v)) for n, v in itertools.chain(self._headers.iteritems(), self._list_headers)]) if hasattr(self, "_new_cookie"): diff --git a/tornado/wsgi.py b/tornado/wsgi.py index ca34ff3e8..bba4d92cb 100644 --- a/tornado/wsgi.py +++ b/tornado/wsgi.py @@ -114,8 +114,10 @@ class WSGIApplication(web.Application): def __call__(self, environ, start_response): handler = web.Application.__call__(self, HTTPRequest(environ)) assert handler._finished - status = str(handler._status_code) + " " + \ - httplib.responses[handler._status_code] + reason = handler._reason + if reason is None: + reason = httplib.responses[handler._status_code] + status = str(handler._status_code) + " " + reason headers = handler._headers.items() + handler._list_headers if hasattr(handler, "_new_cookie"): for cookie in handler._new_cookie.values():