Flask 学习-69.捕获异常钩子函数errorhandler

  • Post author:
  • Post category:其他




前言

flask 运行请求出现异常时,会先触发对应的异常钩子,比如出现404时,会根据NotFound 异常类返回404状态码。

我们也可以根据捕获异常钩子errorhandler 来自定义异常的输出。



404 NotFound

以404 NotFound 为例,在werkzeug.exceptions中可以找到

class NotFound(HTTPException):
    """*404* `Not Found`

    Raise if a resource does not exist and never existed.
    """

    code = 404
    description = (
        "The requested URL was not found on the server. If you entered"
        " the URL manually please check your spelling and try again."
    )

NotFound 类继承了一个基类HTTPException

class HTTPException(Exception):
    """The base class for all HTTP exceptions. This exception can be called as a WSGI
    application to render a default error page or you can catch the subclasses
    of it independently and render nicer error messages.
    """

    code: t.Optional[int] = None
    description: t.Optional[str] = None

我们只需要继承HTTPException类,自定义code和description就可以指定不同状态码返回了。

当我们访问一个不存在的地址,先抛出NotFound异常,然后触发异常钩子,返回对应的code和description

于是我们可以自定义这个404 页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>404</title>
</head>
<body>
   <h1>404 page object not found !</h1>
</body>
</html>



errorhandler 使用

errorhandler 相关源码

    @setupmethod
    def errorhandler(
        self, code_or_exception: t.Union[t.Type[Exception], int]
    ) -> t.Callable[[ErrorHandlerCallable], ErrorHandlerCallable]:
        """Register a function to handle errors by code or exception class.

        A decorator that is used to register a function given an
        error code.  Example::

            @app.errorhandler(404)
            def page_not_found(error):
                return 'This page does not exist', 404

        You can also register handlers for arbitrary exceptions::

            @app.errorhandler(DatabaseError)
            def special_exception_handler(error):
                return 'Database connection failed', 500

通过使用 errorhandler() 装饰函数来注册或者使用 register_error_handler() 来注册

@app.errorhandler(werkzeug.exceptions.BadRequest)
def handle_bad_request(e):
    return 'bad request!', 400

# or, without the decorator
app.register_error_handler(400, handle_bad_request)

使用

@app.errorhandler()

装饰器返回自定义的页面

from flask import Flask, request, g, abort, Response, render_template
app = Flask(__name__)


@app.errorhandler(404)
def error_404(error):
    return render_template('404.html'), 404


@app.route("/demo", methods=["GET"])
def demo():

    return {'msg': 'ok'}


if __name__ == '__main__':
    app.run()

使用的时候需注意三点

1.errorhandler()括号里面传对应状态码或者一个异常类

2.函数error_404(error) 括号里面必须传一个位置参数接收异常

3.return 返回的时候需带上状态码(404),没带上状态码默认返回200



errorhandler 传异常类示例



werkzeug.exceptions

导入异常类

from werkzeug.exceptions import NotFound


@app.errorhandler(NotFound)
def error_404(error):
    return render_template('404.html'), 404

效果和上面传404 参数一样



自定义400 bad request

请求参数不合法时,我们一般会返回400 bad request, 默认返回的是一个html页面

在开发接口的时候,我们希望统一返回json 格式

@app.errorhandler(400)
def error_400(error):
    return {'msg': '请求参数不合法', 'data': f'{error}'}, 400


@app.route("/demo", methods=["GET"])
def demo():
    if request.args.get('username'):
        abort(400)
    return {'msg': 'ok'}

当访问一个不存在的地址时,就会返回400的json格式

HTTP/1.0 400 BAD REQUEST
Content-Type: application/json
Content-Length: 145
Server: Werkzeug/2.0.1 Python/3.8.5
Date: Sun, 11 Sep 2022 14:19:53 GMT

{
  "data": "400 Bad Request: The browser (or proxy) sent a request that this server could not understand.", 
  "msg": "请求参数不合法"
}



处理

在处理请求时,当 Flask 捕捉到一个异常时,它首先根据代码检索。如果该代码没 有注册处理器,它会根据类的继承来查找,确定最合适的注册处理器。如果找不到已 注册的处理器,那么 HTTPException 子类会显示 一个关于代码的通用消息。没有代码的异常会被转化为一个通用的 500 内部服务器 错误。

例如,如果一个 ConnectionRefusedError 的实例被抛出,并且一个出错处 理器注册到 ConnectionError 和 ConnectionRefusedError ,那么 会使用更合适的 ConnectionRefusedError 来处理异常实例,生成响应。

当一个蓝图在处理抛出异常的请求时,在蓝图中注册的出错处理器优先于在应用中全 局注册的出错处理器。

但是,蓝图无法处理 404 路由错误,因为 404 发生的路由级 别还不能检测到蓝图。



通用异常处理器

可以为非常通用的基类注册异常处理器,例如 HTTPException 基类或者甚至 Exception 基类。但是,请注意,这样会捕捉到超出你预期的异常。

基于 HTTPException 的异常处理器对于把缺省的 HTML 出错页面转换为 JSON 非常有用,但是这个处理器会触发不由你直接产生的东西,

如路由过程中产生的 404 和 405 错误。请仔细制作你的处理器,确保不会丢失关于 HTTP 错误的信息。

from flask import json
from werkzeug.exceptions import HTTPException

@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({
        "code": e.code,
        "name": e.name,
        "description": e.description,
    })
    response.content_type = "application/json"
    return response

基于 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

异常处理器仍然遵循异常烦类的继承层次。如果同时基于 HTTPException 和 Exception 注册了异常处理器, Exception 处理器不会处理 HTTPException 子类,因为 HTTPException 更有针对性。



未处理的异常 500

当一个异常发生时,如果没有对应的异常处理器,那么就会返回一个 500 内部服务错误。关于此行为的更多内容参见 flask.Flask.handle_exception() 。

如果针为 InternalServerError 注册了异常处理器,那么出现内部服务错误时就 会调用这个处理器。自 Flask 1.1.0 开始,总是会传递一个 InternalServerError 实例给这个异常处理器,而不是以前的未处理异常。原始 的异常可以通过 e.original_error 访问。在 Werkzeug 1.0.0 以前,这个属性 只有未处理异常有。建议使用 getattr 访问这个属性,以保证兼容性。

@app.errorhandler(InternalServerError)
def handle_500(e):
    original = getattr(e, "original_exception", None)

    if original is None:
        # direct 500 error, such as abort(500)
        return render_template("500.html"), 500

    # wrapped unhandled error
    return render_template("500_unhandled.html", e=original), 500



版权声明:本文为qq_27371025原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。