]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add ability to get/set HTTP response reason phrase in RequestHandler and HTTPResponse 555/head
authorAlek Storm <alek.storm@gmail.com>
Sun, 1 Jul 2012 03:40:14 +0000 (23:40 -0400)
committerAlek Storm <alek.storm@gmail.com>
Sun, 1 Jul 2012 03:40:14 +0000 (23:40 -0400)
tornado/httpclient.py
tornado/simple_httpclient.py
tornado/test/web_test.py
tornado/web.py
tornado/wsgi.py

index 0fcc943f9d0712f7c9bd9d4585f867aabb1ec6c8..7426ea52d0e59352301eb7e1050810362d7ab363 100644 (file)
@@ -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:
index c6e8a3a7ce0e0594d725cf5a09571651ab69b779..ab70b519e63b06f92fff7df58de9cf160a5eba07 100644 (file)
@@ -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)
index ad417336535696403ef931cbf38fb2afb140d19d..e0f3e7a0f9ed79cea3b2f80482a940f933f28c65 100644 (file)
@@ -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):
index a9bc5046fff0a96a7a3ac6a230f26ec1cd6ecbef..090613d671f0271196fccad498b6d86563ed12ff 100644 (file)
@@ -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"):
index ca34ff3e8829a787af888b6584d33b7806aff0de..bba4d92cb4f71095a092d8813e9217ab207c3c02 100644 (file)
@@ -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():