flask: 內置HttpException的rest风格改进

在api的设计中, 无论异常还是正常数据均需要服务器以json的格式返回, 为了对异常的统一管理, 同时为了后续更加方便的返回和验证数据, 我们自定义异常返回类.
设计异常数据的返回格式为:

{
    "error_code": 999,
    "msg": "sorry, we make a mistake",
    "request": "POST /v1/client/register"
}

异常值分别代表:

999  未知错误
1006  客户端错误
1007  服务器错误
1000 参数错误
...

其中json中的request包含了请求的方法和路径.


一. 定义一个最基本的类

二. 参数错误和客户端错误类

三. 所有的异常


一. 定义一个最基本的类

为了使代码简洁, 首先定义一个最基本的类, 供其它类继承, 这个自定义的APIException继承HTTPException.
1. 为了返回特定的body信息, 需要重写get_body;
2. 为了指定返回类型, 需要重写get_headers.
3. 为了接收自定义的参数, 重写了__init__;
4. 同时定义了类变量作为几个默认参数.(code500和error_code:999 均表示未知错误, error_code表示自定义异常code)

from flask import request, json
from werkzeug.exceptions import HTTPException


class ApiException(HTTPException):
    code = 500
    msg = 'sorry, we make a mistake'
    error_code = 999

    def __init__(self, code=None, msg=None, error_code=None, header=None):
        if code:
            self.code = code
        if msg:
            self.msg = msg
        if error_code:
            self.error_code = error_code
        super(ApiException, self).__init__(msg, None)

    def get_body(self, environ=None):
        body = dict(
            msg=self.msg,
            error_code=self.error_code,
            # 形如request="POST v1/client/register"
            request=request.method + ' ' + self.get_url_no_param()
        )
        text = json.dumps(body)
        return text

    def get_headers(self, environ=None):
        return [('Content-Type', 'application/json')]

    @staticmethod
    def get_url_no_param():
        full_url = str(request.full_path)
        main_path = full_url.split('?')
        return main_path[0]

二. 参数错误和客户端错误类

这两个类继承ApiException, 只是重写了三个默认的类变量.


class ClientTypeError(ApiException): code = 400 msg = 'client is invalid' error_code = 1006 # 参数错误 class ParameterException(ApiException): code = 400 msg = 'invalid parameter' error_code = 1000

一切定义完毕!
那么该怎么如何抛出异常呢?

# 抛出参数异常
raise ParameterException(msg=self.errors)

当然也可以只捕捉异常.

三. 所有的异常

至此我们无法将一些特殊的异常返回为json格式, 比如客户端提交的数据不符合规范等 在程序中, 我们会对所有的已知异常跑出一个ApiException的异常.但是无法对未知的异常琢一处理, 因此我们可以使用AOP的思想, 在程序的出口统一处理, 直接在入口函数中编写一个函数

# 为了捕捉所有的异常, 我们需要绑定异常的基类 Exception, Flask>1.0
@app.errorhandler(Exception)
def framework_error(e):
    # ApiExcetion
    # HttpException
    # Exception
    if isinstance(e, ApiException):
        return e
    if isinstance(e, HTTPException):
        code = e.code
        msg = e.description
        error_code = 1007
        return ApiException(code=code, msg=msg, error_code=error_code)
    else:
        if not app.config['DEBUG']:
            return ApiException()
        raise e

此时便可以捕捉所有异常了.

当然在一些正常的返回中也可以使用这个ApiException. 比如:

class Success(ApiException):
    code = 200
    msg = 'success'
    error_code = 0

@api.route('/register', methods=['POST'])
def create_client():
    #...
    return Success()