异常分为客户端异常和服务端异常,flask 中的异常处理分为三步走:异常注册、异常触发、异常处理。
关于客户端/服务端异常,先看段样例代码:
from flask import *
from paddlenlp import Taskflow
from werkzeug.exceptions import HTTPException
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False
@app.errorhandler(Exception)
def handle_500_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
@app.errorhandler(HTTPException)
def handle_exception(e):
response = e.get_response()
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response
class RequestParamException(HTTPException):
code = 400
def handle_param_exception(e):
response = e.get_response()
response.data = json.dumps({
"code": e.code,
"name": "子类:RequestParamException 处理的异常。",
"description": e.description,
})
response.content_type = "application/json"
return response
@app.route('/add_user', methods=['POST'])
def add_documents():
data = request.get_json()
user = data['user']
if user is None or len(user) <= 0:
raise RequestParamException(description="user 不能为 None 或 ''。")
return {"user": user}, 201
if __name__ == '__main__':
app.register_error_handler(RequestParamException, handle_param_exception)
app.run(debug=True)
常用的异常注册的模式包括两种:
@app.errorhandler
app.register_error_handler()
使用装饰器模式注册异常时,针对Exception异常(服务端异常)和HttpException异常(客户端异常),注册的时候可以指定具体处理的异常类型,也可以统一处理。
服务端异常注册:
@app.errorhandler(Exception)
def handle_error(e):
"""
Handle errors, formatting them as JSON if requested
"""
......
客户端异常注册:
@app.errorhandler(HTTPException)
def handle_error(e):
"""
Handle errors, formatting them as JSON if requested
"""
......
服务端异常注册:
app.register_error_handler(Exception, handle_error)
客户端异常注册:
app.register_error_handler(HTTPException, handle_error)
异常触发的方式通常有以下几种:
assert语句又称作断言,指的是程序期望满足指定的条件。定义的约束条件不满足的时候,它会触发AssertionError异常,所以assert语句可以当作条件式的raise语句。
assert 逻辑表达式, data
data是可选的,通常是一个字符串,当表达式的结果为False时,作为异常类型的描述信息使用。
assert语句等同于:
if not 逻辑表达式:
raise AssertionError(data)
raise [exceptionName [(reason)]]
其中,用 [] 括起来的为可选参数,其作用是指定抛出的异常名称,以及异常信息的相关描述。如果可选参数全部省略,则 raise 会把当前错误原样抛出;如果之前没有触发异常,触发RuntimeError;如果仅省略 (reason),则在抛出异常时,将不附带任何的异常描述信息。
在视图函数执行过程中,我们可以使用abort函数立即终止视图函数的执行。通过abort函数,可以返回一个app.aborter中存在的错误状态码,表示出现的错误信息。类似于python中raise。
abort(404)
在上述客户端全局异常捕获过程中,异常的处理方法为 handle_exception(e)
。
客户端的所有请求都囊括了,404、405、400 等等,一旦这些异常触发,都会执行到 handle_exception(e) 方法中,最后将异常信息返回给客户端。handle_exception(e) 方法里面的具体内容如下:
@app.errorhandler(HTTPException)
def handle_exception(e):
response = e.get_response()
response.data = json.dumps({
"code": e.code,
"name": e.name,
"description": e.description,
})
response.content_type = "application/json"
return response
请求内容:
curl --request POST \
--url http://127.0.0.1:5000/add_user \
--header 'Content-Type: application/json' \
--data '{"user":"diego"}'
返回结果:
{
"user": "diego"
}
(1)触发 404 异常
请求内容:
curl --request POST \
--url http://127.0.0.1:5000/add_user2 \
--header 'Content-Type: application/json' \
--data '{"user":"diego"}' | jq
返回结果:
{
"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.",
"name": "Not Found"
}
(2) 触发 405 异常
请求内容:
curl --request GET \
--url http://127.0.0.1:5000/add_user \
--header 'Content-Type: application/json' \
--data '{"user":"diego"}' | jq
返回结果:
{
"code": 405,
"description": "The method is not allowed for the requested URL.",
"name": "Method Not Allowed"
}
我们可以在 HTTPException 基础上自定义异常信息,比如在参数不符合要求,抛出异常。当子类异常与父类异常同时存在时,优先让子类异常捕获。
注意:不要直接触发HttpException异常,这样会导致错误不能被正常捕获并处理。
在Flask的异常处理流程中,首先会单独判断一下是否HTTPException异常,如果是的话转入handle_http_exeption处理器进行处理。然后handle_http_exeption处理逻辑中,会默认访问code属性。但是直接触发HTTPException时是无法设置code属性的。因此这时会引发新的异常,导致错误处理不会走我们注册的处理程序,造成莫名其妙的错误。
这里,建议我们要么触发类似NotFound这些基于HTTPException扩展的异常,要么可以基于HTTPException继承自定义的异常。
(1)定义异常
## 定义异常类
class RequestParamException(HTTPException):
code = 400
## 定义异常处理方法
def handle_param_exception(e):
response = e.get_response()
response.data = json.dumps({
"code": e.code,
"name": "子类:RequestParamException 处理的异常。",
"description": e.description,
})
response.content_type = "application/json"
return response
(2)注册异常
这里,在app.py文件的main函数中,使用工厂模式注册RequestParamException异常:
if __name__ == '__main__':
app.register_error_handler(RequestParamException, handle_param_exception)
app.run()
(3)异常触发
请求内容:
curl --request POST \
--url http://127.0.0.1:5000/add_user \
--header 'Content-Type: application/json' \
--data '{"user":""}' | jq
返回结果:
{
"code": 400,
"description": "user 不能为 None 或 ''。",
"name": "子类:RequestParamException 处理的异常。"
}
@app.errorhandler(Exception)
def handle_500_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 是捕获不了的,只能单独处理。