OpenResty为什么可以实现API服务
OpenResty可以实现简单的API服务的原因如下:
-
Nginx基础:OpenResty是基于Nginx的扩展,而Nginx本身就是一个高性能的Web服务器和反向代理服务器。Nginx使用事件驱动的非阻塞I/O模型,可以处理大量并发连接,并具有良好的性能和可靠性。OpenResty建立在这个强大的基础上,通过扩展和定制化,使得它可以更加灵活地处理API服务的需求。
-
Lua语言集成:OpenResty将Nginx与Lua语言集成在一起,Lua是一种轻量级的、高性能的脚本语言。使用Lua可以方便地编写请求和响应处理逻辑、路由规则和业务逻辑。Lua语言具有简洁的语法和丰富的库,使得编写API服务变得简单、灵活和高效。
-
灵活的配置:OpenResty使用Nginx的配置文件语法,可以通过简单的配置实现路由、反向代理、负载均衡等功能。您可以根据需要定义不同的API路由,将请求转发到不同的处理逻辑或后端服务。这种灵活的配置使得实现简单的API服务变得非常容易。
-
强大的扩展性:OpenResty提供了丰富的Lua API和模块,使得开发人员能够扩展和定制化API服务。您可以使用Lua编写自定义的模块来处理请求和响应,访问数据库,调用其他API等。这种扩展性使得OpenResty可以满足各种复杂的API服务需求。
-
高性能和可扩展性:OpenResty的性能优化和可扩展性是基于Nginx的优势。Nginx的事件驱动非阻塞I/O模型可以处理大量并发连接,并且在高负载下仍然保持稳定的性能。这使得OpenResty能够处理高并发的API请求,并且可以通过水平扩展来应对更高的负载。
OpenResty结合了Nginx的高性能和灵活性以及Lua的简洁性和扩展性,使得它成为实现简单的API服务的理想选择。通过配置和编写Lua脚本,您可以轻松地定义API路由、处理请求和响应、访问后端服务等,从而构建高性能和定制化的API服务。
如何使用Lua实现API服务
OpenResty可以使用Lua实现简单的API服务的原因如下:
-
集成性:OpenResty是基于Nginx的扩展,它将Nginx与Lua语言集成在一起。Lua是一种轻量级、高性能的脚本语言,具有简洁的语法和易于学习的特点。通过与Lua的集成,OpenResty可以使用Lua作为编程语言来处理请求和响应,以及执行其他的业务逻辑。
-
灵活性:Lua是一种灵活的脚本语言,具有强大的表达能力和丰富的语法特性。它提供了丰富的标准库和扩展库,使得开发人员可以方便地处理各种任务,例如字符串操作、JSON解析、数据库访问等。使用Lua,您可以根据具体的需求编写自定义的处理逻辑,并与其他系统进行集成。
-
高性能:Lua脚本在OpenResty中的执行速度非常快。OpenResty使用了基于事件驱动和非阻塞I/O的Nginx架构,这意味着Lua脚本的执行不会阻塞请求和响应的处理过程。这使得OpenResty能够处理大量并发请求,并提供高吞吐量和低延迟的API服务。
-
轻量级和可扩展性:Lua是一种轻量级的脚本语言,它的运行时环境占用资源较少。这使得OpenResty在相同硬件条件下能够处理更多的并发请求,同时保持较低的服务器负载。此外,Lua脚本可以通过编写自定义模块来扩展OpenResty的功能,满足更复杂的业务需求。
-
广泛的生态系统:Lua拥有一个活跃的社区和丰富的第三方库。在OpenResty中,您可以利用这个生态系统,使用现有的Lua库来处理各种任务,例如身份验证、缓存控制、数据处理等。这使得开发API服务变得更加简单和高效。
综上所述,OpenResty使用Lua作为编程语言,使得实现简单的API服务变得灵活、高效和可扩展。Lua的简洁性和性能优势,以及与Nginx的集成,为开发人员提供了一个强大的工具来构建高性能和定制化的API服务。
OpenResty如何实现简单的API服务
使用OpenResty可以轻松地实现简单的API服务。以下是一个基本的步骤指南:
-
定义API路由:在OpenResty的配置文件中,使用
location
指令定义API的路由规则。例如,可以使用以下配置将请求路径为/api/users
的请求路由到指定的处理逻辑:location /api/users { content_by_lua_block { -- 处理逻辑 } }
-
解析请求参数:使用Lua脚本,可以解析请求的参数,例如查询字符串参数、POST请求的表单数据等。OpenResty提供了许多内置的Lua库和函数,用于解析和操作请求参数。例如,可以使用
ngx.req.get_uri_args()
函数获取查询字符串参数,使用ngx.req.get_post_args()
函数获取POST请求的表单数据。 -
处理请求逻辑:在Lua脚本中,可以编写逻辑来处理API请求。您可以根据具体的业务需求执行各种操作,例如查询数据库、生成响应数据、调用其他API等。OpenResty的Lua API提供了许多函数和库,使得编写请求处理逻辑变得简单和高效。
-
构造响应:使用Lua脚本构造API的响应。您可以设置响应的状态码、响应头、响应体等。OpenResty提供了
ngx.status
变量用于设置响应状态码,ngx.header
表用于设置响应头,ngx.say()
函数用于输出响应体。ngx.status = 200 ngx.header["Content-Type"] = "application/json" ngx.say('{"message": "Hello, world!"}')
-
处理异常情况:在编写API服务时,还需要考虑异常情况的处理。例如,处理请求错误、验证失败、数据库连接问题等。可以使用Lua的异常处理机制(如
pcall
函数)或OpenResty提供的错误处理函数(如ngx.exit
)来处理异常情况,并返回适当的错误响应。 -
重新加载或重启OpenResty:在保存配置文件后,使用以下命令重新加载或重启OpenResty,以使新的API服务配置生效:
nginx -s reload # 重新
实战
API服务概述
API
users 的 CRUD 操作- 使用
OpenResty
和lua
完成 API 的开发和部署 - 使用
lua-resty-mysql
库操作MySQL
数据库
启动openresty服务
docker run \
--name openresty \
-p 80:80 \
-d \
-v ${PWD}/data/conf.d:/etc/nginx/conf.d \
-v ${PWD}/data/users:/usr/local/openresty/lualib/users \
openresty/openresty:alpine
参数注释:
${PWD}/data/conf.d:/etc/nginx/conf.d
挂载 nginx 的 server 配置文件${PWD}/data/users:/usr/local/openresty/lualib/users
挂载 lua 实现的业务逻辑
API接口实现的关键模块
lua链接数据库:
local mysql = require "resty.mysql"
local db = mysql:new()
local ok, err, errcode, sqlstate = db:connect({
host = "192.168.1.2",
port = 3306,
database = "demo",
user = "root",
password = "root"
})
if not ok then
ngx.say("failed to connect: ", err, ": ", errcode, " ", sqlstate)
return
end
数据操作:
-- create语句
sql = string.format("INSERT INTO users (age,nickname)VALUES (%d,'%s')", user_payload.age, user_payload.nickname)“
-- update语句
sql = string.format("UPDATE users SET age=%d, nickname='%s' WHERE id=%d", user_payload.age, user_payload.nickname,
user_payload.id)
-- delete语句
sql = string.format("DELETE FROM users where id=%d", args.id)
-- 执行sql语句
res, err, errcode, sqlstate = db:query(sql)
if not res then
ngx.say("failed to connect: ", err, ": ", errcode, " ", sqlstate)
return
end
路由配置
users的Restful API如下:
- 创建用户,路由:
/users
;请求方法:POST
- 更新用户,路由:
/users
;请求方法:PUT
- 用户详情,路由:
/users/:id
;请求方法:GET
- 用户列表,路由:
/users
;请求方法:GET
- 删除用户,路由:
/users/:id
;请求方法:DELETE
OpenResty location配置:
location /users
:首先根据路径参数匹配创建用户、用户列表路由;然后根据请求方法不同调用不同子请求location ~ ^/users/([0-9]+)
:首先根据路径参数匹配用户详情、更新用户、删除用户路由;然后根据请求方法不同调用不同子请求
不同类型的参数子请求获取方法:
- 路径参数,比如用户详情、更新用户、删除用户接口的用户ID参数,使用方式如下:
server {
location /example {
#将id参数设置的`id`变量中
set $id $1;
#
rewrite_by_lua_block {
local args, err = ngx.req.get_uri_args()
if err == "truncated" then
-- one can choose to ignore or reject the current request here
end
-- 将ID参数放入到args中
args["id"] = ngx.var.id
-- 调用子请求,并将args参数传入子请求的
res = ngx.location.capture('/user_get', {
args = args
})
ngx.say(res.body)
}
}
# 用户详情
location /user_get {
internal;
default_type 'application/json';
content_by_lua_block {
-- 从uri_args参数读取用户ID
local args, err = ngx.req.get_uri_args()
if err == "truncated" then
-- one can choose to ignore or reject the current request here
end
ngx.say(args.id)
}
}
}
- 查询参数,直接调用
local args, err = ngx.req.get_uri_args()
指令,返回的args
包含所有的查询参数 - body参数
-- 父请求:第一步读取body
ngx.req.read_body()
-- 父请求:第二步,将always_forward_body设置为true
res = ngx.location.capture('/user_create', {
args = args,
always_forward_body = true
})
ngx.say(res.body)
-- 子请求:读取和解码请求body
local json_text = ngx.req.get_body_data()
user_payload = cjson.decode(json_text)
完整配置example
server {
listen 80;
server_name localhost;
#默认编码
charset utf-8;
# 许出站从非本地 IP 地址到代理服务器的连接(例如,来自客户端的真实 IP 地址)
proxy_bind $remote_addr transparent;
# 当后端web服务器上也配置多个虚拟主机时,需要用该header来区分反向代理哪个主机名。
proxy_set_header Host $host;
# 通过$remote_addr变量获取前端客户真实IP地址。
proxy_set_header X-Real-IP $remote_addr;
# 通过$remote_addr变量获取前端客户真实IP地址。
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 接口
location ~ ^/users/([0-9]+) {
default_type 'application/json';
set $id $1;
rewrite_by_lua_block {
local args, err = ngx.req.get_uri_args()
if err == "truncated" then
-- one can choose to ignore or reject the current request here
end
local method_name = ngx.req.get_method()
args["id"] = ngx.var.id
local method_name = ngx.req.get_method()
if method_name == 'GET' then
res = ngx.location.capture('/user_get', {
args = args
})
ngx.say(res.body)
elseif method_name == 'DELETE' then
res = ngx.location.capture('/user_delete', {
args = args
})
ngx.say(res.body)
elseif method_name == 'PUT' then
res = ngx.location.capture('/user_update', {
args = args
})
ngx.say(res.body)
end
}
}
location /users {
default_type 'application/json';
rewrite_by_lua_block {
local args, err = ngx.req.get_uri_args()
if err == "truncated" then
-- one can choose to ignore or reject the current request here
end
local method_name = ngx.req.get_method()
if method_name == 'GET' then
res = ngx.location.capture('/user_find', {
args = args,
always_forward_body = true
})
ngx.say(res.body)
elseif method_name == 'POST' then
ngx.req.read_body()
res = ngx.location.capture('/user_create', {
args = args,
always_forward_body = true
})
ngx.say(res.body)
end
}
}
# 创建用户
location /user_create {
internal;
default_type 'application/json';
content_by_lua_block {
local cjson = require "cjson"
local mysql = require "resty.mysql"
local db = mysql:new()
local ok, err, errcode, sqlstate = db:connect({
host = "192.168.1.2",
port = 3306,
database = "demo",
user = "root",
password = "root"
})
if not ok then
ngx.say(cjson.encode({
code = 1,
err = err,
errcode = errcode,
sqlstate = sqlstate
}))
return
end
local json_text = ngx.req.get_body_data()
user_payload = cjson.decode(json_text)
sql = string.format("INSERT INTO users (age,nickname)VALUES (%d,'%s')", user_payload.age, user_payload.nickname)
res, err, errcode, sqlstate = db:query(sql)
if not res then
ngx.say(cjson.encode({
code = 1,
err = err,
errcode = errcode,
sqlstate = sqlstate
}))
return
end
local resp = {
code = 0,
err_msg = "",
method = "user_crate",
sql = sql,
data = user_payload
}
ngx.say(cjson.encode(resp))
}
}
# 更新用户
location /user_update {
internal;
default_type 'application/json';
content_by_lua_block {
local cjson = require "cjson"
local mysql = require "resty.mysql"
local db = mysql:new()
local ok, err, errcode, sqlstate = db:connect({
host = "192.168.1.2",
port = 3306,
database = "demo",
user = "root",
password = "root"
})
if not ok then
ngx.say(cjson.encode({
code=1,
err=err,
errcode=errcode,
sqlstate=sqlstate
}))
return
end
local json_text = ngx.req.get_body_data()
user_payload = cjson.decode(json_text)
sql=string.format("UPDATE users SET age=%d, nickname='%s' WHERE id=%d", user_payload.age,user_payload.nickname,user_payload.id)
res, err, errcode, sqlstate =db:query(sql)
if not res then
ngx.say(cjson.encode({
code=1,
err=err,
errcode=errcode,
sqlstate=sqlstate
}))
return
end
local resp= {
code=0,
err_msg="",
method="user_update",
sql=sql,
data=user_payload
}
ngx.say(cjson.encode(resp))
}
}
# 删除用户
location /user_delete {
internal;
default_type 'application/json';
content_by_lua_block {
local resp= {
code=0,
err_msg="",
method="user_delete"
}
local cjson = require "cjson"
ngx.say(cjson.encode(resp))
}
}
# 用户详情
location /user_get {
internal;
default_type 'application/json';
content_by_lua_block {
local mysql = require "resty.mysql"
local cjson = require "cjson"
local args, err = ngx.req.get_uri_args()
local headers, err = ngx.req.get_headers()
local db = mysql:new()
local ok, err, errcode, sqlstate = db:connect({
host = "192.168.1.2",
port = 3306,
database = "demo",
user = "root",
password = "root"
})
if not ok then
ngx.say(cjson.encode({
code=1,
err=err,
errcode=errcode,
sqlstate=sqlstate
}))
return
end
sqlScript=string.format("select * from users where id=%d order by id asc",args.id)
list, err, errcode, sqlstate = db:query(sqlScript)
if not list then
ngx.say(cjson.encode({
code=1,
err=err,
errcode=errcode,
sqlstate=sqlstate
}))
return
end
ngx.say(cjson.encode({
code = 0,
data = list
}))
}
}
# 用户列表
location /user_find {
internal;
default_type 'application/json';
content_by_lua_block {
local mysql = require "resty.mysql"
local cjson = require "cjson"
local args, err = ngx.req.get_uri_args()
local headers, err = ngx.req.get_headers()
local db = mysql:new()
local ok, err, errcode, sqlstate = db:connect({
host = "192.168.1.2",
port = 3306,
database = "demo",
user = "root",
password = "root"
})
if not ok then
ngx.say(cjson.encode({
code=1,
err=err,
errcode=errcode,
sqlstate=sqlstate
}))
return
end
sqlScript=string.format("select * from users order by id asc")
list, err, errcode, sqlstate = db:query(sqlScript)
if not list then
ngx.say(cjson.encode({
code=1,
err=err,
errcode=errcode,
sqlstate=sqlstate
}))
return
end
ngx.say(cjson.encode({
code = 0,
data = list
}))
}}
}
注意:也可以将content_by_lua_block
替换为content_by_lua_file
实现。具体如下所示:
server {
location ~ ^/users/([0-9]+) {
default_type 'application/json';
set $id $1;
rewrite_by_lua_file /usr/local/openresty/lualib/users/users_detail_location.lua;
}
}
测试脚本
curl -XPOST -d '{"age":30,"nickname":"bob"}' localhost/users #创建user
curl -XPUT -d '{"id":1,"age":30,"nickname":"bob"}' localhost/users #更新user
curl -XDELETE localhost/users/6 #删除user
curl -XGET 'localhost/users/2' #详情
curl -XGET 'localhost/users' #列表