一个简单API Server框架

在前面的文章中,我们讨论REST相关的概念,以及REST在OpenResty中的应用。接下来,我们尝试使用OpenResty中搭建一个简单的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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
worker_processes  1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 80;

# 加法
location /addition {
content_by_lua_block {
local args = ngx.req.get_uri_args()
ngx.say(args.x + args.y)
}
}

# 减法
location /subtraction {
content_by_lua_block {
local args = ngx.req.get_uri_args()
ngx.say(args.x - args.y)
}
}

# 乘法
location /multiplication {
content_by_lua_block {
local args = ngx.req.get_uri_args()
ngx.say(args.x * args.y)
}
}

# 除法
location /division {
content_by_lua_block {
local args = ngx.req.get_uri_args()
ngx.say(args.x / args.y)
}
}
}
}

可以看到,整个配置文件看起来非常复杂,下面我们来优化一下

三、优化

首先,入口要统一,接口的实现要独立,保持简洁:

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
worker_processes  1;
error_log logs/error.log;
events {
worker_connections 1024;
}

http {
# 设置默认 lua 搜索路径
# 此处写相对路径时,需要注意启动 nginx 的路径,因为它会影响自定义模块的导入
# 绝对路径当然也没问题,但是不可移植,因此应使用变量 $prefix 或 ${prefix}
lua_package_path '$prefix/lua/?.lua;/blah/?.lua;;';

# 这里设置为 off,是为了避免每次修改之后都要重新 reload 的麻烦。
# 在生产环境上务必确保 lua_code_cache 设置成 on。
lua_code_cache off;

server {
listen 80;

# 在代码路径中使用nginx变量
# 注意: nginx var 的变量一定要谨慎,否则将会带来非常大的风险
location ~ ^/api/([-_a-zA-Z0-9/]+) {
content_by_lua_file lua/$1.lua;
}
}
}

${prefix}/lua目录下的文件分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- ========== {$prefix}/lua/addition.lua
local args = ngx.req.get_uri_args()
ngx.say(args.x + args.y)

-- ========== {$prefix}/lua/subtraction.lua
local args = ngx.req.get_uri_args()
ngx.say(args.x - args.y)

-- ========== {$prefix}/lua/multiplication.lua
local args = ngx.req.get_uri_args()
ngx.say(args.x * args.y)

-- ========== {$prefix}/lua/division.lua
local args = ngx.req.get_uri_args()
ngx.say(args.x / args.y)

四、迭代

对于一个后端程序来说,怎么可以容忍输入参数不检查呢?万一客户端传入的不是数字或者为空怎么办?所以这些都要过滤掉。我们会发现,这些接口的参数检查过滤方法应该是统一的,那么如何在这几个 API 中如何共享这个方法呢?这时候就需要 Lua 模块来完成了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
worker_processes  1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
server {
listen 80;

location ~ ^/api/([-_a-zA-Z0-9/]+) {
# 准入阶段完成参数验证
access_by_lua_file lua/access_check.lua;

content_by_lua_file lua/$1.lua;
}
}
}

此处,我们将验证逻辑的执行时机放到了准入阶段,${prefix}/lua/access_check.lua的内容如下:

1
2
3
4
5
6
7
local param= require("comm.param")
local args = ngx.req.get_uri_args()

if not args.x or not args.y or not param.is_number(args.x, args.y) then
ngx.exit(ngx.HTTP_BAD_REQUEST)
return
end

模块comm.param的路径为{$prefix}/lua/comm/param.lua,其内如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local _M = {}

-- 对输入参数逐个进行校验,只要有一个不是数字类型,则返回 false
function _M.is_number(...)
local arg = {...}

local num
for _,v in ipairs(arg) do
num = tonumber(v)
if nil == num then
return false
end
end

return true
end

return _M

五、小结

通过如上几个步骤,一个简单的API Server就搭建完成了,最终整体的目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.
├── conf
│ ├── nginx.conf
├── logs
│ ├── error.log
│ └── nginx.pid
├── lua
│ ├── access_check.lua
│ ├── addition.lua
│ ├── subtraction.lua
│ ├── multiplication.lua
│ ├── division.lua
│ └── comm
│ └── param.lua
└── sbin
└── nginx

六、参考

简单API Server框架


一个简单API Server框架
https://kuberxy.github.io/2020/12/09/一个简单API-Server框架/
作者
Mr.x
发布于
2020年12月9日
许可协议