不太RESTurl的接口设计

本文总阅读量

前记

又是一篇水文- -.

在还没开始实习的时候,除了在弄一些练习项目或者刷面试题时,经常碰到RESTurl,知道这是一个非常重要的前后端交互的接口设计模式,也是很少人/公司能把RESTurl完全的应用到自己的业务逻辑里面.

实习时,一开始就用RESTurl方式去写API,写了一段时间的API后发现,其实没必要完全去追求RESTurl,虽然RESTurl的设计是很好的,但是从理论到落地总是会有一些偏差.

要永远记得,一切要以实际业务为准.

** 注:文章已经更新,目前已经使用starlette代替sanic作为web框架**

1.HTTP Method方法缺少云服务商的支持

在RESTurl中,最明显的就是对于同一个url,把url当做资源,再用http method方法去进行操作,比如用get去获取数据,post添加数据,put更新数据,delete删除数据,从设计上面来看这个设计的没问题的,但是在现实中却会带来一些bug.之前在API运用RESTurl的设计后运行了一段时间,结果在某一天套上了阿里云的CDN后发现接口不可用了,经过一段时间的排查后发现是阿里云的CDN只支持GET/POST方法…
在这次事故之后,所有API的HTTP Method改为GET/POST, get一般用于获取数据且数据一定能被CDN缓存的,其他都使用POST方法.在做了此次更改后,无论接了什么CDN,都再也没有出现云服务商无法支持的问题了.

2.接口设计与抛错设计的

经常能发现很多人在一些编程论坛上吵着,全RESTurl,或者只要接口没问题,那就返回HTTP STATUS CODE 200,并自己在json里面写上业务的code,交由客户端判断.在经历过几次项目的设计后,我还是偏爱于后者的.先看看设计

2.1接口返回设计

由于前端的部分库会设计在非200的错误码就抛错或者业务需求,正常情况下,只要web框架能响应,就返回HTTP Status Code:200,只有在下列情况下才会出现非200的情况:

  • 1.服务端当服务端鉴权失败时返回 HTTP Status Code 403(如果返回401要记得在 Response 的头里面去掉 [WWW-Authenticate: Basic realm=”Realm”],不然浏览器遇到 401 会弹出那个浏览器自带的输入帐号密码的东西)
  • 2.中间件遇到问题返回的标准错误码,例如 Nginx 返回的 5** 错误码

当接口返回HTTP Status Code:200时,对应的响应内容如下:

1
2
3
4
5
{
"code": 0,
"result": {},
"msg": ""
}
  • code
    表示为业务状态码,以1位数字表示大类的错误,比如0为正常,1为数据库错误,2为限流等.以2位数的表示业务类的错误,如10为用户相关错误,11为功能块B相关错误等.,4位数为详细错误,如用户登录出错,用户密码错误,用户token过期等,能描述出来且能把该错误快速定位出来.
    其中3位数留空是因为有些前端人员会在出现问题后只报出错误码,并不会去区分 HTTP Status Code或者业务状态码,所以3位数的业务状态码留空可以减少不必要的交流成本.
  • result
    表示返回的数据,一般只有code为0才会有数据,没数据时result为null
  • msg
    表示错误的信息,只有code不为0时才有数据,为0时留空.

2.2 如何编写抛错设计的代码

一般基于上面设计时,都会自己封装一个response(code, msg, result=None)的代码,然后根据业务逻辑填写对应的值,久而久之就会发现,代码里太多if…else,同时在存在多层嵌套时,也需要多层if…else.

解决这个问题很简单,只要利用好框架中的中间件功能即可,框架中的中间件功能除了处理请求前与请求后的数据外,还能处理请求时的异常,所以可以利用这一点来编写接口返回中的异常返回.同时还能利用继承来完成我们上面所说的code分类.
先以如下个代码结构为例子:

1
2
3
# 这里示例代码用的是starlette框架
├── custom.py # 存放中间件以及错误定义相关
└── server.py # 主要入口代码

在没引入错误设计前的HelloWord的代码如下:

1
2
3
4
5
6
7
8
9
10
11
from starlette.applications import Starlette
from starlette.routing import Route, Router
from starlette.responses import JSONResponse


APP = Starlette()


@APP.route('/')
async def homepage(request):
return JSONResponse({'hello': 'world'})

2.2.1 添加custom.py文件

最主要的是针对中间件的封装以及错误定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 继承starlette框架的错误中间件接口,starlette会捕获handle.py中的错误,并发向api_exception,再由我们自定义的response返回给用户
# 这里的response也就是的HTTPException
class APIException(HTTPException):
API_CODE = 1
DETAIL = "API Error"

def __init__(
self,
extend_detail: Optional[str] = None,
api_code: Optional[int] = None,
content: Any = None,
background_task: Optional[BackgroundTask] = None
) -> None:
super().__init__(200, self.DETAIL) # 这里的200是http状态码
if api_code:
self.api_code = api_code
else:
self.api_code = self.API_CODE
self.content = content
self.background_task = background_task

# 第二类错误且他属于第一类中状态码1错误的子类,所以直接继承APIException
class ClientException(APIException):
API_CODE = 11
DETAIL = "Request Error"

# 使用starlette框架的错误中间件接口,starlette会捕获handle.py中的错误,并发向api_exception,再由我们自定义的response返回给用户
async def api_exception(request: Request, exc: APIException) -> Response:
return response(request, exc.status_code, exc.api_code, content=exc.content, error_type=exc.DETAIL)

可以看到我们主要是为自己的抛错定义一个类,当有错误时,会把错误通过api_exception发送给我们的response,这里的response也就是返回给用户一个响应,http状态码为exc.status_code,返回的json内容为

1
2
3
4
5
{
"code": exc.api_code,
"result": {},
"msg": exc.content
}

2.2.1 修改server.py文件

先通过APP.add_exception_handler(APIException, api_exception)把我们的api_exception引入到starlette框架里面,成为他的异常中间件.

之后修改api接口为:

1
2
3
@APP.route('/')
async def homepage(request):
raise APIException()

修改后重启程序,重新调用就会看到网页显示

1
2
3
4
5
{
"code": 1,
"result": {},
"msg": "API Error"
}

3.总结

RESTurl的设计非常不错,需要学习他的思想,但由于以下原因,无法在项目上完整的应用.

  • 1.比较难与自由业务的融合(最重要的一点,比较公司需要的是你的产出,无法与业务融合,那再怎么高大上也没什么用)
  • 2.云服务商缺少支持
  • 3.客户端/前端人员的知识
  • 4.客户端/前端框架的支持
查看评论