From 596038c28478b9ba85bcf0e34322d663bc641db4 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Thu, 19 Jun 2014 09:28:45 -0400 Subject: [PATCH] Add new exception tornado.web.Finish to quietly end a request. This allows error pages to be generated inline with the main code instead of in write_error and is friendlier to generating error pages from library code. Closes #1064. --- docs/web.rst | 1 + tornado/test/web_test.py | 19 ++++++++++++++++++- tornado/web.py | 27 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/docs/web.rst b/docs/web.rst index 889b4299..277126d6 100644 --- a/docs/web.rst +++ b/docs/web.rst @@ -235,6 +235,7 @@ Everything else --------------- .. autoexception:: HTTPError + .. autoexception:: Finish .. autoexception:: MissingArgumentError .. autoclass:: UIModule :members: diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index cbb62b9b..b32a0326 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -10,7 +10,7 @@ from tornado.template import DictLoader from tornado.testing import AsyncHTTPTestCase, ExpectLog, gen_test from tornado.test.util import unittest from tornado.util import u, bytes_type, ObjectDict, unicode_type -from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature_v1, create_signed_value, decode_signed_value, ErrorHandler, UIModule, MissingArgumentError, stream_request_body +from tornado.web import RequestHandler, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler, _create_signature_v1, create_signed_value, decode_signed_value, ErrorHandler, UIModule, MissingArgumentError, stream_request_body, Finish import binascii import contextlib @@ -2307,3 +2307,20 @@ class XSRFTest(SimpleHandlerTestCase): body=urllib_parse.urlencode(dict(_xsrf=body_token)), headers=self.cookie_headers(cookie_token)) self.assertEqual(response.code, 200) + + +@wsgi_safe +class FinishExceptionTest(SimpleHandlerTestCase): + class Handler(RequestHandler): + def get(self): + self.set_status(401) + self.set_header('WWW-Authenticate', 'Basic realm="something"') + self.write('authentication required') + raise Finish() + + def test_finish_exception(self): + response = self.fetch('/') + self.assertEqual(response.code, 401) + self.assertEqual('Basic realm="something"', + response.headers.get('WWW-Authenticate')) + self.assertEqual(b'authentication required', response.body) diff --git a/tornado/web.py b/tornado/web.py index 209b7ecd..d28531e2 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1384,6 +1384,11 @@ class RequestHandler(object): " (" + self.request.remote_ip + ")" def _handle_request_exception(self, e): + if isinstance(e, Finish): + # Not an error; just finish the request without logging. + if not self._finished: + self.finish() + return self.log_exception(*sys.exc_info()) if self._finished: # Extra errors after the request has been finished should @@ -1938,6 +1943,9 @@ class HTTPError(Exception): `RequestHandler.send_error` since it automatically ends the current function. + To customize the response sent with an `HTTPError`, override + `RequestHandler.write_error`. + :arg int status_code: HTTP status code. Must be listed in `httplib.responses ` unless the ``reason`` keyword argument is given. @@ -1966,6 +1974,25 @@ class HTTPError(Exception): return message +class Finish(Exception): + """An exception that ends the request without producing an error response. + + When `Finish` is raised in a `RequestHandler`, the request will end + (calling `RequestHandler.finish` if it hasn't already been called), + but the outgoing response will not be modified and the error-handling + methods (including `RequestHandler.write_error`) will not be called. + + This can be a more convenient way to implement custom error pages + than overriding ``write_error`` (especially in library code):: + + if self.current_user is None: + self.set_status(401) + self.set_header('WWW-Authenticate', 'Basic realm="something"') + raise Finish() + """ + pass + + class MissingArgumentError(HTTPError): """Exception raised by `RequestHandler.get_argument`.