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

Question:

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?

Asked By: S-K'

||

Answers:

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>")
Answered By: VisioN

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,
                })
Answered By: Rod Hyde
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.

Answered By: Somesh

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):
...
Answered By: TIO

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>")
Answered By: punkrockpolly

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.”

Answered By: scharfmn

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)
Answered By: Gosha null
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.