简体   繁体   中英

How do I return HTTP error code without default template in Tornado?

I am currently using the following to raise a HTTP bad request:

raise tornado.web.HTTPError(400)

which returns a html output:

<html><title>400: Bad Request</title><body>400: Bad Request</body></html>

Is it possible to return just the HTTP response code with a custom body?

You may simulate RequestHandler.send_error method:

class MyHandler(tornado.web.RequestHandler):
    def get(self):
        self.clear()
        self.set_status(400)
        self.finish("<html><body>My custom body</body></html>")

Tornado calls RequestHandler.write_error to output errors, so an alternative to VisioN's approach would be override it as suggested by the Tornado docs . The advantage to this approach is that it will allow you to raise HTTPError as before.

The source for RequestHandler.write_error is here . Below you can see an example of a simple modification of write_error that will change the set the status code and change the output if you provide a reason in kwargs.

def write_error(self, status_code, **kwargs):
    if self.settings.get("serve_traceback") and "exc_info" in kwargs:
        # in debug mode, try to send a traceback
        self.set_header('Content-Type', 'text/plain')
        for line in traceback.format_exception(*kwargs["exc_info"]):
            self.write(line)
        self.finish()
    else:
        self.set_status(status_code)
        if kwargs['reason']:
            self.finish(kwargs['reason'])
        else: 
            self.finish("<html><title>%(code)d: %(message)s</title>"
                "<body>%(code)d: %(message)s</body></html>" % {
                    "code": status_code,
                    "message": self._reason,
                })

It's better to use the standard interface and define your custom message on the HTTPError .

raise tornado.web.HTTPError(status_code=code, log_message=custom_msg)

You can then parse the error in your RequestHandler and check for the message:

class CustomHandler(tornado.web.RequestHandler):
    def write_error(self, status_code, **kwargs):
        err_cls, err, traceback = kwargs['exc_info']
        if err.log_message and err.log_message.startswith(custom_msg):
            self.write("<html><body><h1>Here be dragons</h1></body></html>")
def write_error(self, status_code, **kwargs):
    #Function to display custom error page defined in the handler.
    #Over written from base handler.
    data = {}
    data['code'] = status_code
    data['message'] = httplib.responses[status_code]
    # your other conditions here to create data dict
    self.write(TEMPLATES.load('error.html').generate(data=data))

when ever self.send_error() call is initiated write_error() function is called by the request handler. So you can create your custom error data dict here and render it to your custom error page.

http.responses[status_code] returns the error code text like "page not found" based on the status code.

Also you can override get_error_html method in your handler. For example:

import tornado.web
class CustomHandler(tornado.web.RequestHandler):
    def get_error_html(self, status_code, **kwargs);
        self.write("<html><body><h1>404!</h1></body></html>")
...
def get(self):
...

This exchange clarifies some of the approaches suggested here, and discounts the reason keyword (which I was thinking about trying).

Q: (by mrtn)

"I want to use raise tornado.web.HTTPError(400, reason='invalid request') to pass a custom reason to the error response, and I hope to do this by overriding the write_error (self, status_code, **kwargs) method.

"But it seems that I can only access self._reason inside write_error , which is not what I want. I also tried kwargs['reason'] but that does not exist."

A: (by Tornado lead developer @bendarnell)

"The exception that exposed the error is available to write_error as an exc_info triple in the keyword arguments. You can access the reason field with something like this:

if "exc_info" in kwargs:
    e = kwargs["exc_info"][1]
    if isinstance(e, tornado.web.HTTPError):
        reason = e.reason

"But note that the reason field is essentially deprecated (it is not present in HTTP/2), so it's probably not the best way to do whatever you're trying to do here ( HTTPError 's log_message field is a little better, but still not ideal). Just raise your own exception instead of using HTTPError ; your write_error override can use self.set_status(400) when it sees the right kind of exception."

For json error response i use follow template:

Request handler:

import json
from tornado.web import RequestHandler
from src.lib.errors import HTTPBadRequest


class JsonHandler(RequestHandler):

    def prepare(self):
        content_type = ''
        if "Content-Type" in self.request.headers:
            content_type = self.request.headers['Content-Type']

        if content_type == 'application/json':
            try:
                self.request.body = json.loads(self.request.body.decode('utf-8'))
            except ValueError:
                raise HTTPBadRequest

    def write_error(self, *args, **kwargs):
        err_cls, err, traceback = kwargs['exc_info']
        self.set_status(err.status_code)
        if err.description:
            self.write_json(err.description)
        self.finish()

    def set_default_headers(self):
        self.set_header('Content-Type', 'application/json')

    def write_json(self, response):
        self.write(json.dumps(response))

Errors handler:

from typing import Any
from tornado import httputil


class BaseHTTPError(Exception):
    def __init__(
        self, status_code: int = 500, description=None, *args: Any, **kwargs: Any
    ) -> None:
        if description is None:
            description = {}
        self.status_code = status_code
        self.description = description
        self.args = args
        self.kwargs = kwargs

    def __str__(self) -> str:
        message = "HTTP %d: %s" % (
            self.status_code,
            httputil.responses.get(self.status_code, "Unknown"),
        )
        return message


class HTTPBadRequest(BaseHTTPError):
    def __init__(self, *args, **kwargs):
        super().__init__(status_code=400, description={"error": "Bad Request"}, *args, **kwargs)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM