Flask之请求钩子和异常捕获


请求钩子[hook]

在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:

  • 在项目运行开始时,建立数据库连接;
  • 在客户端请求开始时,根据需求进行权限校验;
  • 在请求结束视图返回数据时,指定数据的交互格式;

为了让每个视图函数避免编写重复功能的代码,Flask提供了通用设置的功能,即请求钩子。

请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子(注意:名字是固定的):

  • before_first_request
    • 在处理第一个请求前执行[项目初始化时的钩子]
  • before_request
    • 在每一次请求前执行
    • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用
  • after_request
    • 如果没有抛出错误,在每次请求后执行
    • 接受一个参数:视图函数作出的响应
    • 在此函数中可以对响应值在返回之前做最后一步修改处理
    • 需要将参数中的响应在此参数中进行返回
  • teardown_request
    • 在每一次请求后执行
    • 接受一个参数:错误信息,如果有相关错误抛出
    • 需要设置flask的配置DEBUG=False,teardown_request才会接受到异常对象。

app.py代码

from flask import Flask, request

# 初始化
app = Flask(import_name=__name__)


@app.before_first_request
def before_first_request():
    """
    这个钩子会在项目启动后第一次被用户访问时执行
    可以编写一些初始化项目的代码,例如,数据库初始化,加载一些可以延后引入的全局配置
    """
    print("----before_first_request----")
    print("系统初始化的时候,执行这个钩子方法")
    print("会在接收到第一个客户端请求时,执行这里的代码")


@app.before_request
def before_request():
    """
    这个钩子会在每次客户端访问视图的时候执行
    # 可以在请求之前进行用户的身份识别,以及对于本次访问的用户权限等进行判断。..
    """
    print("----before_request----")
    print("每一次接收到客户端请求时,执行这个钩子方法")
    print("一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据")


@app.after_request
def after_request(response):
    print("----after_request----")
    print("在处理请求以后,执行这个钩子方法")
    print("一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作")
    response.headers["Content-Type"] = "application/json"
    response.headers["Company"] = "python oldboy..."
    # 必须返回response参数
    return response


@app.teardown_request
def teardown_request(exc):
    print("----teardown_request----")
    print("在每一次请求以后,执行这个钩子方法")
    print("如果有异常错误,则会传递错误异常对象到当前方法的参数中")
    # 在项目关闭了DEBUG模式以后,则异常信息就会被传递到exc中,我们可以记录异常信息到日志文件中
    print(exc)


# 编写路由视图
@app.route(rule='/')
def index():
    print("-----------视图函数执行了---------------")
    return "hello world!"


if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0", port=5000)

第一次请求

----before_first_request----
系统初始化的时候,执行这个钩子方法
会在接收到第一个客户端请求时,执行这里的代码
----before_request----
每一次接收到客户端请求时,执行这个钩子方法
一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
-----------视图函数执行了---------------
----after_request----
在处理请求以后,执行这个钩子方法
一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
----teardown_request----
在每一次请求以后,执行这个钩子方法
如果有异常错误,则会传递错误异常对象到当前方法的参数中
None

第二次请求打印

----before_request----
每一次接收到客户端请求时,执行这个钩子方法
一般可以用来判断权限,或者转换路由参数或者预处理客户端请求的数据
-----------视图函数执行了---------------
----after_request----
在处理请求以后,执行这个钩子方法
一般可以用于记录会员/管理员的操作历史,浏览历史,清理收尾的工作
----teardown_request----
在每一次请求以后,执行这个钩子方法
如果有异常错误,则会传递错误异常对象到当前方法的参数中
None

异常捕获

主动抛出HTTP异常

  • abort 方法
    • 抛出一个给定状态代码的 HTTPException 或者 指定响应,例如想要用一个页面未找到异常来终止请求,你可以调用 abort(404)
  • 参数:
    • code – HTTP的错误状态码
from flask import Flask, request, abort

app = Flask(import_name=__name__)


@app.route('/')
def index():
    # 主动抛出HTTP异常
    if not request.args.get("uname"):
        abort(400)
    return "hello world!"


if __name__ == '__main__':
    # 运行flask
    app.run(host="0.0.0.0", port=5000, debug=True)

注意点:

  • 抛出状态码的话,只能抛出 HTTP 协议的错误状态码

  • abort在工作中基本不会被使用,工作中的异常抛出往往在业务错误的时候使用raise进行抛出错误类型,而不是抛出http异常。

  • abort一般用于权限等页面上错误的展示提示。

捕获错误

  • errorhandler 装饰器
    • 注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法
  • 参数:
    • code_or_exception – HTTP的错误状态码或指定异常
  1. errorhandler 可以捕获指定http状态码的异常
from flask import Flask, request, abort

app = Flask(import_name=__name__)


# errorhandler 可以捕获指定http状态码的异常
@app.errorhandler(400)
def internal_server_error(e):
    print(e) # 400 Bad Request: The browser (or proxy) sent a request that this server could not understand.
    return '对不起,参数有误!'


@app.route("/")
def index():
    # 主动抛出HTTP异常
    if not request.args.get("uname"):
        abort(400)

    return "ok"


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)

  1. errorhandler捕获自定义的异常
from flask import Flask, request, abort

app = Flask(import_name=__name__)


# errorhandler 也可以捕获指定的内置异常或者自定义的异常
class APIError(Exception):
    pass


# 捕获自定义异常
@app.errorhandler(APIError)
def api_error(e):
    print(e)
    return "api请求有误"


@app.route("/")
def index():
    # 抛出自定义异常并进行处理
    raise APIError("api接口发生异常!!!!")

    return "ok"


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)

  1. 捕获标准异常[python内置声明的]
from flask import Flask, request, abort

app = Flask(import_name=__name__)


# 捕获标准异常[python内置声明的]
@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
    print(e)
    return '除数不能为0'


@app.route("/")
def index():
    1 / 0
    return "ok"


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5000, debug=True)