API的设计

OpenResty最擅长的应用场景之一就是API Server。一提到API Server我们的脑海里一定会冒出许多与REST相关的概念,下面我们来看看在OpenResty中如何实现REST风格的API Server。

一、为什么要使用REST

首先,我们先来看看不采用REST的API Server长什么样,如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
server {
listen 80;
server_name localhost;

location /app/set {
content_by_lua_block {
ngx.say('set data')
}
}

location /app/get {
content_by_lua_block {
ngx.say('get data')
}
}

location /app/del {
content_by_lua_block {
ngx.say('del data')
}
}

...
}

可以看到,如果我们的API Server提供了很多的接口服务,那么上面的nginx配置文件就会变得非常冗杂。可读性、可维护性都会降低。因此,我们更推荐采用REST风格来开发API Server。

二、什么是REST

REST是Representational State Transfer的简称,即表述性状态转移,是2000年Roy Fielding博士在他的博士论文中提出来的一种软件架构风格。它是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可伸缩性。

从资源的角度来看整个网络,分布在各处的资源都由URI来标识的,而客户端应用则是通过URI来获取资源的表现形式。所谓表述性状态转移是指,客户端获取资源的表现形式致使其状态发生转变。随着客户端不断地获取资源的表现形式,其状态也会不断地进行转变。以上观点并不是凭空臆造的,而是通过对Web运行方式的观察而抽象出来的。Roy Fielding博士认为:

设计良好的网络应用表现为一系列的网页,这些网页可以看作是虚拟的状态机。用户选择一个链接会导致下一个网页传输到用户端并展现给用户,而这正代表了状态的转变。

REST是设计风格而不是标准。REST通常基于HTTP、URI,XML和HTML等这些现有的、广泛流行的协议和标准:

  • 资源由URI来标识
  • 对资源的操作包括获取、创建、修改和删除,这些操作正好对应HTTP协议提供GET、POST、PUT、DELETE方法
  • 通过操作资源的表现形式来操作资源。
  • 资源的表现形式是HTML还是XML,取决于获取资源的是机器还是人,是消费Web服务端的客户端软件还是Web浏览器。

REST要求:

  • 客户端和服务器(C/S)结构
  • 连接协议具有无状态性
  • 能够利用Cache机制提高性能
  • 层次化的系统

三、OpenResty & REST

按照 REST 的风格引导,有关数据的 API Server 就可以变成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server {
listen 80;
server_name localhost;

location /app/task01 {
content_by_lua_block {
ngx.say(ngx.req.get_method() .. ' task01')
}
}
location /app/task02 {
content_by_lua_block {
ngx.say(ngx.req.get_method() .. ' task02')
}
}
location /app/task03 {
content_by_lua_block {
ngx.say(ngx.req.get_method() .. ' task03')
}
}
}

对于 /app/task01 接口(资源),这时候我们可以用下面的方法,完成对应的方法调用(操作):

1
2
3
curl -X GET http://127.0.0.1/app/task01
curl -X PUT http://127.0.0.1/app/task01
curl -X DELETE http://127.0.0.1/app/task01

如果 task 类型(接口)非常多,那么后面这个配置依然会随着业务的调整而调整,随之也会引来一系列的问题。那么,能否在简洁一些呢?这里引用一下 HttpLuaModule 官方示例代码:

1
2
3
4
5
6
7
# use nginx var in code path
# WARNING: contents in nginx var must be carefully filtered,
# otherwise there'll be great security risk!
location ~ ^/app/([-_a-zA-Z0-9/]+) {
set $path $1;
content_by_lua_file /path/to/lua/app/root/$path.lua;
}

可以看到,我们再也不用去修改 Nginx 主配置了。另外,强制了入口文件命名规则。对于后期检查维护更容易。

四、REST的缺点

REST 推崇使用 HTTP 返回码来区分返回结果, 但最大的问题在于 HTTP 的错误返回码 (4xx 系列为主) 不够多,而且订得很随意。比如,用 API 创建一个用户,那么错误可能有:

  • 调用格式错误(一般返回 400, 405)
  • 授权错误(一般返回 403)
  • "运行期"错误
    • 用户名不合法
    • 用户名冲突
    • email 不合法
    • email 冲突

此外,REST具有一定的学习成本。想要理解REST,必须具备HTTP、URI、HTML和XML等相关知识。

五、小结


API的设计
https://kuberxy.github.io/2020/12/09/API的设计/
作者
Mr.x
发布于
2020年12月9日
许可协议