最近在做SRDP项目的后端开发,但是狗书上面对API相关内容介绍比较少,示例代码中的API部分也不涉及用户登录注册相关内容。不过幸运的是在Github上找到了一个问卷调查项目,通过阅读项目代码发现了在进行API开发时处理异常的一些技巧。

问卷项目地址:https://github.com/yuzhanglong/EasyQuestionnaire-backend

Flask异常

程序执行遇到异常是很正常的现象,在普通的程序中可以用try except捕获异常并抛出来保证程序的正常执行。但是在web项目中,如果遇到了异常只是在服务器终端中抛出是不行的,有时必须要让用户了解到错误信息。

Flask作为一个web开发框架在设计之初就想到了这点,对异常处理也进行了封装,最典型的例子就是404异常。在默认情况下,如果访问了无效的路由,会看到一个错误页面,这个错误页面就是由Flask内部封装好的异常处理机制发出的。

如果是普通的Flask项目,在程序执行时遇到异常时可以直接把错误渲染在页面上,但是在进行API开发时,返回的数据都应该是json类型,当出现错误时只要把错误类型返回,具体的渲染工作交给前端负责即可。那么应该怎么实现呢,一个很实用的方法是自己定义异常处理类。

这里有一篇文章,详细的介绍了重写异常处理类的思路和方法:Flask开发技巧之异常处理。接下来简单介绍实现思路。

自定义异常

Flask内部的异常都是通过继承HTTPException类来实现的,当抛出这个类时会向发送方发送响应,内部有几个重要的方法:

  1. get_headers方法定义返回的响应头
  2. get_body方法定义返回的响应体,默认情况下是一段html内容

因为在前后端分离开发时返回类型都是json,所以我们可以通过继承HTTPException这个类来定义我们自己的异常处理类。主要修改的内容就是响应头和响应主体两个部分,将响应头中的类型改为json,并将响应主体也改为json类型。下面是我自己实现的代码:

class ApiException(HTTPException):
    code = 404
    errorCode = 2000
    information = "未知错误"

    def __init__(self, code=None, error_code=None, information=None, payload=None):
        Exception.__init__(self)
        if code:
            self.code = code
        if error_code:
            self.errorCode = error_code
        if information:
            self.information = information
        self.payload = payload
        super(ApiException, self).__init__(information, None)

    # 重写get_body
    def get_body(self, environ=None):
        body = dict(self.payload or ())
        body['errorCode'] = self.errorCode
        body['information'] = self.information
        return json.dumps(body)

    # 设置返回的响应头
    def get_headers(self, environ=None):
        return [('Content-Type', 'application/json')]

当抛出上面的异常类时,就会返回包含错误码和错误信息的json格式的响应。http状态码会自动添加到响应当中,也需要注意修改。

在此基础上,我们可以定义自己的各种错误类,并且在合适的地方抛出,比如:

class Success(ApiException):
    code = 200
    errorCode = 0


class ParameterException(ApiException):
    code = 403
    errorCode = "validate error"
    information = "验证失败"

需要注意的一点是,虽然定义的是错误类,但实际上也可以返回成功的响应,比如上面代码中的Success

最后一点技巧是全局捕获异常,推荐去看看我上面放的文章。

from flask import Blueprint
from werkzeug.exceptions import HTTPException

from .errorHandler import ApiException

# 全局错误AOP处理
commonException = Blueprint('common', __name__)


@commonException.app_errorhandler(Exception)
def framework_error(error):
    if isinstance(error, ApiException):
        return error                    # 自定义错误直接返回
    if isinstance(error, HTTPException):
        code = error.code
        information = error.description
        # error_code = 1007
        return ApiException(code=code, information=information)     # flask内置错误封装为ApiException返回
    else:
        return error    # 比较合适的方法是在生产环境中返回服务器内部错误 不返回具体信息