Python中HTTPException(基于werkzeug.exceptions包)
当我们在开发HTTP服务时(接口服务),由于很多从内部引发的 Python 异常,会触发标准 HTTP 非 200 响应的视图。为了让前端有着更好的视图体验(如果因为内部异常,会返回给前端/调用方更好的一个页面/返回)。对于我们来说,给予调用方一个固定的返回格式时非常重要的(因此通过HTTPException来固定当发生异常、成功返回时等场景下发送给请求方的格式)。
本文使用的werkzeug版本为3.0.x,官方文档:HTTP 异常 — 工具文档 (3.0.x)
1、HTTPException详解
根据源码中类描述:所有 HTTP 异常的基类。这个异常可以被称为 WSGI应用程序呈现默认错误页面,或者您可以捕获子类它独立并呈现更好的错误消息。我们可以认知到这个类其实是一种模板模式(定义一个操作中的算法的骨架,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤),HTTPException类中定义好了大部分返回形式以及结构,作为其子类只用重写其中的某些关键方法,就可以完成定制化输出。
1.1、类属性
HTTPExceptuon的类属性有两个:
- code:状态码
-
description:状态描述
这两个类属性用于告知 前端/调用方 当前服务的状态以及描述,其中code将会被设置为HTTP返回状态码(因此最好遵循HTTP状态码规则)。而description则是会放置在返回的html页面的描述中。
1.2、类方法
前面有说到HTTPException其实类似于模板模式,其中它已经写好了大部分方法用于更好的返回给前端页面,我们将解释其中的一些方法:
get_description():
用于将类属性description转换为<p></p>标签包裹结构(也就是HTML结构)用于被其他方法调用
get_body():
返回给调用方的实际展示。在源码中这是一段HTML页面视图,如下图
我们从图中可以看到该方法默认返回的是一个HTML页面,从代码中可以看到返回的内容包括了状态码以及状态码的描述(这里的self.name是根据code获取得到的http包中的描述),以及一些其他的信息。这个方法就是子类实现中需要重点重写的方法,因为get_body()方法的返回值就是调用方接收到的主要信息。
get_headers():
用于设置返回请求的返回头,下图是源码实现:
从源码中可以看到其实这个方法就是在设置返回头中的header内容使用[(key,value),]的方式,给返回头中注入内容。由于源码中get_body()方法返回的是一个html页面,因此这里返回头中设置的也是"text/html",如果我们的get_body()返回的是一个json格式,那么我们就需要同时重写这个方法,将返回值设定为[('Content-Type', 'application/json; charset=utf-8')]。
2、自定义异常类型
从上一节中我们了解到,其实我们只需要重写一个HTTPException的子类,并重写重要的方法(主要是get_body(),get_headers()),即可得到一个我们自己的自定义异常类基类(其实这不仅仅只作为异常类,如果我们的code设置为200,也可以看成是一种格式化出参的包装类)。
下面我们以返回一个json对象作为异常值的返回,来构建一个自定义异常类:
import json from werkzeug.exceptions import HTTPException from typing_extensions import override class APIException(HTTPException): """这个类由Flask监听并返回响应的错误代码 1. 为了返回特定的body信息, 需要重写get_body; 2. 为了指定返回类型, 需要重写get_headers. 3. 为了接收自定义的参数, 重写了__init__; data: 放在返回值中的结构 4. 同时定义了类变量作为几个默认参数. code: HTTP状态码, 需要遵守http常规约定, error_code: 表示自定义异常code, 用于自己排除错 """ data = None # 默认为None, 子类可以不用设置 # 自定义需要返回的信息,在初始化完成并交给父类 def __init__(self, message=None, code=None, error_code=None, data=None): if code: self.code = code if error_code: self.error_code = error_code if data: self.data = data if message: self.message = message super(APIException, self).__init__(message, None) @override def get_body(self, environ=None, scope=None): """ 返回的内容将会返回给前端. """ body = dict( message=self.message, error_code=self.error_code, data=self.data ) text = json.dumps(body) return text @override def get_headers(self, environ=None, scope=None): """Get a list of headers. 返回给前端表示数据接收方式为json """ return [('Content-Type', 'application/json; charset=utf-8')]
我们自己创建了一个APIException类,其中data是放在返回的json中,当抛出这个异常时,我们会以json格式返回(内容看get_body()方法)。
接下来我们就可以基于这个APIException类设置更多的不同异常类,比如我下面创建了三个类:
class ValidateFailed(APIException): """验证出现错误 """ code = 500 class ServiceFailed(APIException): """调用其他服务器时, 服务器返回错误错误 error_code=9901: 表示xxx服务器出现异常 error_code=9902: 表示xxx服务器出现异常 """ code = 500 class Success(APIException): code = 200 message = 'success' error_code = 0
我们这里创建了三个基于APIException(我们自己创建的异常基类),我们在类上就已经定义好了code(HTTP状态码),因此在抛出此异常时我们只用传递data,message等参数即可(Success什么都不用传)。下面是使用样例:
@app.route('/success', methods=["POST"]) def receive_quality_info(): return Success(data=["a", "s"]) @app.route('/validateFailed', methods=["POST"]) def receive_failure_info(): raise ValidateFailed(error_code=9901, message='测试')
3、自带错误类
werkzeug.exceptions包中的HTTPException文件中,其实已经为我们封装好了一些常见常用的异常类。包括有:
- BadRequest(400错误类):服务无法处理
-
Unauthorized(401错误类):资源无权访问
-
Forbidden(403错误类): 没有所请求资源的权限但已通过身份验证
-
MethodNotAllowed(405错误类):请求方法错误
- 等
上述的一些常见错误类都可以直接使用import导入使用。实际上从源码中看这些类的编写模式与上述说的也几乎一样,基本是重新定义类属性code,description,并重写get_headers方法等,这样在运行时就可以返回指定的错误类型以及描述。