任何异常的全局错误处理程序

59
有没有一种方法可以添加全局的错误处理程序,在其中我可以将响应更改为通用的JSON响应?
我不能使用got_request_exception信号,因为它不允许修改响应(http://flask.pocoo.org/docs/0.10/signals/)。
相比之下,所有的信号处理程序都是以未定义的顺序执行,并且不修改任何数据。
我宁愿不包装app.handle_exception函数,因为那感觉像是内部API。我猜我想要的是这样的东西:
 @app.errorhandler()
 def handle_global_error(e):
     return "Global error"

请注意,errorhandler不需要任何参数,这意味着它会捕获所有未附加特定错误处理程序的异常/状态代码。我知道我可以使用errorhandler(500)errorhandler(Exception)来捕获异常,但如果我例如使用abort(409),它仍将返回HTML响应。
7个回答

74

您可以使用@app.errorhandler(Exception):

Demo(HTTPException检查确保状态代码被保留):

from flask import Flask, abort, jsonify
from werkzeug.exceptions import HTTPException

app = Flask('test')

@app.errorhandler(Exception)
def handle_error(e):
    code = 500
    if isinstance(e, HTTPException):
        code = e.code
    return jsonify(error=str(e)), code

@app.route('/')
def index():
    abort(409)

app.run(port=1234)

输出:

$ http get http://127.0.0.1:1234/
HTTP/1.0 409 CONFLICT
Content-Length: 31
Content-Type: application/json
Date: Sun, 29 Mar 2015 17:06:54 GMT
Server: Werkzeug/0.10.1 Python/3.4.3

{
    "error": "409: Conflict"
}

$ http get http://127.0.0.1:1234/notfound
HTTP/1.0 404 NOT FOUND
Content-Length: 32
Content-Type: application/json
Date: Sun, 29 Mar 2015 17:06:58 GMT
Server: Werkzeug/0.10.1 Python/3.4.3

{
    "error": "404: Not Found"
}

如果你想覆盖 Flask 默认的 HTML 异常(使它们也返回 JSON),请在 app.run 之前添加以下内容:

from werkzeug.exceptions import default_exceptions
for ex in default_exceptions:
    app.register_error_handler(ex, handle_error)

对于旧版本的 Flask(小于等于0.10.1,即当前任何非Git/Master版本),请将以下代码添加到您的应用程序中以显式注册HTTP错误:

对于旧版Flask (<=0.10.1,即当前任何非git/master版本),可以通过在应用程序中加入下面的代码来明确地注册HTTP错误:

from werkzeug import HTTP_STATUS_CODES
for code in HTTP_STATUS_CODES:
    app.register_error_handler(code, handle_error)

https://github.com/mitsuhiko/flask/blob/0.10.1/flask/app.py#L1086 - 显然,它只在旧版本中使用异常代码。 - ThiefMaster
3
谢谢,我将把这个问题标记为已回答,因为在下一个Flask版本中它将可以工作。也许你应该编辑你的回答说它在当前版本中不能工作,这样找到这个问题的人就不会感到困惑了。 - joscarsson
9
在0.11.1版本中,这个方法不起作用,它无法捕获405错误。我必须显式地编写@app.errorhandler(405)。 - Abhinav Chauhan
5
确实,Exception不再起作用了。这是一个巨大的退步。他们为什么要改变它? - Houman
1
@Houman 我认为发生的情况是 Flask 为最常见的 HTTP 错误提供了默认的错误处理程序;因此,如果您的应用程序引发其中一个 HTTP 错误,则会选择“最具体”的错误处理程序(而不是非常通用的 Exception)。我能够通过使用 JSON 处理程序替换所有默认错误处理程序来解决这个问题(请参见编辑)。 - FGreg
显示剩余8条评论

14

这是兼容Flask 0.12的,而且是解决问题的很好方案(它允许以JSON或任何其他格式呈现错误)。

from functools import wraps
from flask import Flask, redirect, jsonify
app = Flask(__name__)

def get_http_exception_handler(app):
    """Overrides the default http exception handler to return JSON."""
    handle_http_exception = app.handle_http_exception
    @wraps(handle_http_exception)
    def ret_val(exception):
        exc = handle_http_exception(exception)    
        return jsonify({'code':exc.code, 'message':exc.description}), exc.code
    return ret_val

# Override the HTTP exception handler.
app.handle_http_exception = get_http_exception_handler(app)

https://github.com/pallets/flask/issues/671#issuecomment-12746738


9

虽然不太优雅,但以下代码可以将所有HTTPException的子类都绑定到一个错误处理程序:

from flask import jsonify
from werkzeug.exceptions import HTTPException

def handle_error(error):
    code = 500
    if isinstance(error, HTTPException):
        code = error.code
    return jsonify(error='error', code=code)

for cls in HTTPException.__subclasses__():
    app.register_error_handler(cls, handle_error)

这段代码运行得很完美,但有一个缺点,可能会出现HTTP状态码为200的响应,但返回的JSON代码是500和错误“X”。 - Noki
我通过将返回行更改为json.dumps({'success': False, 'error': str(error)}), 500来进行了更正。 - Noki

3

可以为非常通用的基类(例如HTTPException或甚至Exception)注册错误处理程序。然而,请注意,这些处理程序将捕获比您预期的更多内容。

例如,HTTPException的错误处理程序可能对将默认的HTML错误页面转换为JSON很有用。但是,此处理程序会触发一些您没有直接引起的问题,例如路由过程中的404和405错误。请务必仔细编写处理程序,以免丢失有关HTTP错误的信息。

from flask import Flask, abort, jsonify, json
from werkzeug.exceptions import HTTPException


app = Flask('test')
app.config['JSON_SORT_KEYS'] = False


@app.errorhandler(HTTPException)
def handle_exception(e):
    """Return JSON instead of HTML for HTTP errors."""
    # start with the correct headers and status code from the error
    response = e.get_response()
    # replace the body with JSON
    response.data = json.dumps({
        "error": {
            "code": e.code,
            "name": e.name,
            "description": e.description,
        }
    })
    print(response.data) 
    response.content_type = "application/json"
    return response


@app.route('/')
def index():
    abort(409)


@app.route('/aloha')
def aloha():
    abort(400, "I'm not in the mood to talk!")


app.run(port=1234)

输出:

输入图像描述

输入图像描述

输入图像描述

对于Exception的错误处理程序可能会很有用,以更改所有错误(包括未处理的错误)呈现给用户的方式。然而,这类似于在Python中执行except Exception:它将捕获所有未处理的错误,包括所有HTTP状态代码。

在大多数情况下,注册更具体的异常处理程序将更安全。由于HTTPException实例是有效的WSGI响应,您也可以直接通过它们。

from werkzeug.exceptions import HTTPException

@app.errorhandler(Exception)
def handle_exception(e):
    # pass through HTTP errors
    if isinstance(e, HTTPException):
        return e

    # now you're handling non-HTTP exceptions only
    return render_template("500_generic.html", e=e), 500

错误处理程序仍然遵循异常类层次结构。如果您为 HTTPExceptionException 注册处理程序,则 Exception 处理程序不会处理 HTTPException 的子类,因为 HTTPException 处理程序更加具体。


2
在 Flask >=0.12 中更加简洁的实现方法是显式地为每个 Werkzeug 异常注册处理程序:
from flask import jsonify
from werkzeug.exceptions import HTTPException, default_exceptions

app = Flask('test')

def handle_error(error):
    code = 500
    if isinstance(error, HTTPException):
        code = error.code
    return jsonify(error='error', code=code)

for exc in default_exceptions:
    app.register_error_handler(exc, handle_error)

0
基于REST API中的纯文本(非HTML)错误页面 我想返回JSON而不改变我的任何代码,所以我只需在代码顶部添加以下内容。
@app.errorhandler(500)
def error_500(exception):
    return jsonify({"error": str(exception)}), 500, {'Content-Type': 'application/json'}

@app.errorhandler(400)
def error_400(exception):
    return jsonify({"error": str(exception)}), 400, {'Content-Type': 'application/json'}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接