Flask: Registering a handler for HTTPException has no effect

Created on 3 Jan 2014  路  20Comments  路  Source: pallets/flask

When registering a handler for werkzeug.exceptions.HTTPException, it has no effect when an HTTP error is raised.

Assume the following handler:

@app.errorhandler(HTTPException)
def http_err_handler(error):
    response = jsonify({
        "success": False, 
        "message": error.name
    })
    response.status_code = error.code
    return response

When requesting a page for which no route exists, a JSON response should be returned by the error handler, but instead, the usual Flask-generated HTTP error page is returned.

On the other hand, if the error handler is defined to handle a specific error code (by passing the error code to the app.errorhandler decorator), the exception is trapped and the JSON message returned.

As wekzeug.exceptions.HTTPException is the class raised internally by the abort() function, why isn't it possible to create a "catch-all" handler like this? Am I missing something?

blocker bug

Most helpful comment

For those who simply want to override the default behavior of the _default_ HTTPException subclasses, you can do something like this:

import flask
from werkzeug.exceptions import default_exceptions

def create_app():
    app = flask.Flask(__name__)
    ....
    for code, ex in default_exceptions:
        app.errorhandler(code)(_handle_http_exception)

def _handle_http_exception(error):
    if flask.request.is_json:
        return jsonify({
            'status_code': error.code,
            'message': str(error),
            'description': error.description
        }), error.code
    raise error.get_response()

All 20 comments

Long story short: It is a bug, it's a flaw in the design, it's easy to work around as a user, so it's rather low priority to fix. :S

Do you know where the bug exactly lies? If this is an already known bug, with some informations about it maybe I could help by trying to fix it by myself and then submit a pull-request.

I think it would require rewriting most of the error handling system for Flask, the code for this is in flask/app.py

952 is a possible solution to this.

Fixed by #839

839 doesn't fix the issue OP posted -- it only handles subclasses of HTTPException.

Could you provide a testcase that fails?

import unittest

from werkzeug.exceptions import HTTPException, NotFound

import flask


class ErrorHandlerTest(unittest.TestCase):

    def setUp(self):
        app = flask.Flask(__name__)

        @app.errorhandler(HTTPException)
        def http_exception(e):
            return 'generic', 500

        @app.errorhandler(NotFound)
        def notfound_exception(e):
            return 'not found', 404

        @app.route('/error')
        def error():
            flask.abort(500)

        self.app = app

    def test_notfound_handler(self):
        rv = self.app.test_client().get('/')
        self.assertEqual(rv.status_code, 404)
        self.assertEqual(rv.data, 'not found')

    def test_http_handler(self):
        rv = self.app.test_client().get('/error')
        self.assertEqual(rv.status_code, 500)
        self.assertEqual(rv.data, 'generic')


if __name__ == '__main__':
    unittest.main()
$ venv/bin/python app.py
F.
======================================================================
FAIL: test_http_handler (__main__.ErrorHandlerTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "app.py", line 35, in test_http_handler
    self.assertEqual(rv.data, 'generic')
AssertionError: '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n<title>500 ...' != 'generic'

----------------------------------------------------------------------
Ran 2 tests in 0.021s

FAILED (failures=1)

Code 500 is an InternalServerError (subclass of HTTPException). Since there is no specific handler for it (unlike NotFound), one would hope it would be handled by @app.errorhandler(HTTPException)

Ahh, I think this is because HTTPException doesn't have a HTTP code, but the error handling logic tries to find an errorhandler _with the HTTP code_.

cc @flying-sheep

Has anybody reviewed/tested #1383? I was not very sure by looking at the changes.

Yeah this slipped through the cracks!

I wonder whether we shouldn't remove the code/no-code distinction because of this bug though.

right, i didn鈥檛 think of this, sorry!

so the legal classes to register handlers for: HTTPException, the finite set of all subclasses of it (synonymous with error codes), and subclasses of subclasses (e.g. class ForbiddenBecauseNotRegistered(Forbidden): ...

and the legal instances of exceptions to raise are instances of subclasses of HTTPException but not direct instances of HTTPException.


this means the following assumptions hold:

if isinstance(e_instance, HTTPException):
    assert e_instance.code is not None
if issubclass(e_class, HTTPException):
    assert e_class is HTTPException or e_class.code is not None

we should add a way to add a handler for HTTPExceptions without allowing users to raise an instance of HTTPException.

@Bekt that PR should be closed, it was written against the old broken behavior that I replaced.

For those who simply want to override the default behavior of the _default_ HTTPException subclasses, you can do something like this:

import flask
from werkzeug.exceptions import default_exceptions

def create_app():
    app = flask.Flask(__name__)
    ....
    for code, ex in default_exceptions:
        app.errorhandler(code)(_handle_http_exception)

def _handle_http_exception(error):
    if flask.request.is_json:
        return jsonify({
            'status_code': error.code,
            'message': str(error),
            'description': error.description
        }), error.code
    raise error.get_response()

2144 seems to solve this issue

@Bekt I'm a bit late to the party, hope you're around... I'm trying to get flask to return JSON errors when there are exceptions. Your code works great except for raise error.get_response(). According to werkzeug docs:

If you call get_response() on an exception you will get back a regular BaseResponse object, even if you are using a custom subclass.

This results in raise error.get_response() returning TypeError: exceptions must derive from BaseException. Did I miss something? Is there a better way of doing this in 2019 as opposed to 2015.

@antgel i would be lying if i said i remember even remotely what this thread is about... i miss my flask days.

I mean, the error message is pretty clear: error.get_response returns a Response, not an Exception, so you probably just have to replace raise with return

Long story short: It is a bug, it's a flaw in the design, it's easy to work around as a user, so it's rather low priority to fix. :S

Hi

So late, but it is NOT a bug, this is a design decision. HTTPException is the base type for all http exceptions in werkzeug.
and werkzeug or flask themselve, never raise an instance of HTTPException, instead they raise different subclasses of it.
so this is implemented this way to enforce that HTTPException is the base type, and should not be raised directly. but you
could register an error handler for HTTPException and handle all subclasses of it without any problem. the only thing
that you have to do is NOT to raise a direct instance of HTTPException, instead raise its subclasses.

Was this page helpful?
0 / 5 - 0 ratings