配置环境变量
创建工作目录, 启动 nginx
创建独立的nginx
工作目录 your/path
在 conf
目录下创建一个文本文件作为配置文件,命名为 nginx.conf
,
文件内容如下:
worker_processes 1; #nginx worker 数量
error_log logs/error.log; #指定错误日志文件路径
events {
worker_connections 1024;
}
http {
server {
#监听端口,若你的6699端口已经被占用,则需要修改
listen 6699;
location / {
default_type text/html;
content_by_lua_block {
ngx.say("HelloWorld")
}
}
}
}
启动 nginx
start nginx
或者指定 自己的 nginx目录
start nginx -p 'your/path'
访问 http://localhost:6699/
HelloWorld
关闭
nginx -s stop
# 优雅 退出
nginx -s quit
重启
命令在不停止服务器的情况下重新加载 nginx 配置
nginx -s reload
指示 nginx 重新打开所有 日志文件
nginx -s reopen
resty
script 调试
resty -e "ngx.say('Hello, OpenResty!')"
Hello, OpenResty!
location
配合
nginx
世界的 location
是异常强大的,毕竟 nginx
的主要应用场景是在负载均衡
、API server
,
在不同 server
、location
之间跳转更是家常便饭。
利用不同 location
的功能组合,我们可以完成内部调用
、流水线方式跳转
、外部重定向
内部调用 internal
对数据库
、内部公共函数
的统一接口,可以把它们放到统一的 location
中
通常情况下,为了保护这些内部接口,都会把这些接口设置为 internal
这么做的最主要好处就是可以让这个内部接口
相对独立,不受外界干扰
location = /sum {
# 只允许内部调用
internal;
# 这里做了一个求和运算只是一个例子,可以在这里完成一些数据库、
# 缓存服务器的操作,达到基础模块和业务逻辑分离目的
content_by_lua_block {
local args = ngx.req.get_uri_args()
ngx.say(tonumber(args.a) + tonumber(args.b))
}
}
location = /app/test {
content_by_lua_block {
local res = ngx.location.capture(
"/sum", {args={a=3, b=8}}
)
ngx.say("status:", res.status, " response:", res.body)
}
}
http://localhost:6699/sum 外部不能访问
404 Not Found
openresty/1.19.9.1
http://localhost:6699/app/test
status:200 response:11
并行请求,串行请求实例
ngx.location.capture_multi
vs ngx.location.capture
ngx.location.capture_multi( {
{"/sum", {args={a=3, b=8}}},
{"/subduction", {args={a=3, b=8}}}
})
ngx.location.capture(
"/sum", {args={a=3, b=8}}
)
location = /sum {
internal;
content_by_lua_block {
ngx.sleep(0.1)
local args = ngx.req.get_uri_args()
ngx.print(tonumber(args.a) + tonumber(args.b))
}
}
location = /subduction {
internal;
content_by_lua_block {
ngx.sleep(0.1)
local args = ngx.req.get_uri_args()
ngx.print(tonumber(args.a) - tonumber(args.b))
}
}
location = /app/test_parallels {
content_by_lua_block {
local start_time = ngx.now()
local res1, res2 = ngx.location.capture_multi( {
{"/sum", {args={a=3, b=8}}},
{"/subduction", {args={a=3, b=8}}}
})
ngx.say("status:", res1.status, " response:", res1.body)
ngx.say("status:", res2.status, " response:", res2.body)
ngx.say("time used:", ngx.now() - start_time)
}
}
location = /app/test_queue {
content_by_lua_block {
local start_time = ngx.now()
local res1 = ngx.location.capture(
"/sum", {args={a=3, b=8}}
)
local res2 = ngx.location.capture(
"/subduction", {args={a=3, b=8}}
)
ngx.say("status:", res1.status, " response:", res1.body)
ngx.say("status:", res2.status, " response:", res2.body)
ngx.say("time used:", ngx.now() - start_time)
}
}
➜ ~ curl 127.0.0.1/app/test_parallels
status:200 response:11
status:200 response:-5
time used:0.10099983215332
➜ ~ curl 127.0.0.1/app/test_queue
status:200 response:11
status:200 response:-5
time used:0.20199990272522
利用 ngx.location.capture_multi
函数,直接完成了两个子请求并行执行。
当两个请求没有相互依赖,这种方法可以极大提高查询效率。
流水线方式跳转 ngx.exec
仿工厂的流水线模式
,逐层过滤、处理
location ~ ^/static/([-_a-zA-Z0-9/]+).jpg {
set $image_name $1;
content_by_lua_block {
ngx.exec("/download_internal/images/"
.. ngx.var.image_name .. ".jpg");
};
}
location /download_internal {
internal;
# 这里还可以有其他统一的 download 下载设置,例如限速等
alias ../download;
}
这里的两个 location 更像是流水线上工人之间的协作关系。
第一环节的工人对完成自己处理部分后,直接交给第二环节处理人(实际上可以有更多环节),它们之间的数据流是定向的
ngx.exec
方法与 ngx.redirect
完全不同,ngx.exec
是纯粹的内部跳转
并且没有引入任何额外 HTTP 信号
。
外部重定向 ngx.redirect
location = /foo {
content_by_lua_block {
ngx.say([[I am foo]])
}
}
location = / {
rewrite_by_lua_block {
return ngx.redirect('/foo');
}
}
使用浏览器
访问页面 http://127.0.0.1
就可以发现浏览器会自动跳转到 http://127.0.0.1/foo
外部重定向
是可以跨域名
的。
从 A 网站跳转到 B 网站是绝对允许的。
在 CDN 场景的大量下载应用中,一般分为调度
、存储
两个重要环节。
调度就是通过根据请求方 IP 、下载文件等信息寻找最近、最快节点,应答跳转给请求方完成下载。
获取请求参数 url参数
如何正确获取、设置 uri 参数
获取请求 uri 参数
ngx.req.get_uri_args ( uri 请求参数)
+ ngx.req.get_post_args( post 请求内容)
ngx.req.read_body() -- 解析 body 参数之前一定要先读取 body
local arg = ngx.req.get_post_args()
server {
listen 80;
server_name localhost;
location /print_param {
content_by_lua_block {
local arg = ngx.req.get_uri_args()
for k,v in pairs(arg) do
ngx.say("[GET ] key:", k, " v:", v)
end
ngx.req.read_body() -- 解析 body 参数之前一定要先读取 body
local arg = ngx.req.get_post_args()
for k,v in pairs(arg) do
ngx.say("[POST] key:", k, " v:", v)
end
}
}
}
➜ ~ curl '127.0.0.1/print_param?a=1&b=2%26' -d 'c=3&d=4%26'
[GET ] key:b v:2&
[GET ] key:a v:1
[POST] key:d v:4&
[POST] key:c v:3
传递请求 uri 参数
URI 内容传递过程中 调用 ngx.encode_args
进行规则转义
location /test {
content_by_lua_block {
local res = ngx.location.capture(
'/print_param',
{
method = ngx.HTTP_POST,
args = ngx.encode_args({a = 1, b = '2&'}),
body = ngx.encode_args({c = 3, d = '4&'})
}
)
ngx.say(res.body)
}
}
# 不调用 ngx.encode_args
local res = ngx.location.capture('/print_param',
{
method = ngx.HTTP_POST,
args = 'a=1&b=2%26', -- 注意这里的 %26 ,代表的是 & 字符
body = 'c=3&d=4%26'
}
)
ngx.say(res.body)
获取请求body get_body_data
全局设置 lua_need_request_body
或者 调用 ngx.req.read_body()
在 Nginx 的典型应用场景
,几乎都是只读取 HTTP 头即可,例如负载均衡
、正反向代理
等场景。
但是对于 API Server
或者 Web Application
,对 body
可以说就比较敏感了
Nginx
诞生之初主要是为了解决负载均衡
情况,而这种情况,是不需要读取 body
就可以决定负载策略
的
默认是不读取 body
所以需要 设置lua_need_request_body
OpenResty 基于 Nginx ,所以天然的对请求 body 的读取细节与其他成熟 Web 框架有些不同
http {
server {
listen 80;
# 默认读取 body
lua_need_request_body on;
location /test {
content_by_lua_block {
local data = ngx.req.get_body_data()
ngx.say("hello ", data)
}
}
}
}
# 或者
http {
server {
listen 80;
location /test {
content_by_lua_block {
ngx.req.read_body()
local data = ngx.req.get_body_data()
ngx.say("hello ", data)
}
}
}
}
输出响应体
HTTP响应报文
分为三个部分:
响应行
响应头
响应体
对于 HTTP 响应体
的输出,在 OpenResty
中调用 ngx.say
或 ngx.print
即可
ngx.say 与 ngx.print 均为异步输出
优雅处理响应体过大的输出
-
输出内容
本身体积很大
例如超过 2G 的文件下载
利用HTTP 1.1
特性 CHUNKED 编码
, 把一个大的响应体拆分成多个小的应答体
,分批、有节制的响应给请求方
location /test {
content_by_lua_block {
-- ngx.var.limit_rate = 1024*1024
local file, err = io.open(ngx.config.prefix() .. "data.db","r")
if not file then
ngx.log(ngx.ERR, "open file error:", err)
ngx.exit(ngx.HTTP_SERVICE_UNAVAILABLE)
end
local data
while true do
data = file:read(1024)
if nil == data then
break
end
ngx.print(data)
ngx.flush(true)
end
file:close()
}
}
-
输出内容本身是由各种碎片拼凑的,
碎片数量庞大
例如应答数据是某地区所有人的姓名
利用 ngx.print
的特性,它的输入参数
可以是单个或多个字符串参数,也可以是 table 对象
local table = {
"hello, ",
{"world: ", true, " or ", false,
{": ", nil}}
}
ngx.print(table)
也就是说当有非常多碎片数据时,没有必要一定连接成字符串后再进行输出。
完全可以直接存放在 table 中,用数组的方式把这些碎片数据统一起来,
直接调用 ngx.print(table) 即可。这种方式效率更高,并且更容易被优化
日志输出
标准日志输出
OpenResty
的标准日志输出原句为 ngx.log(log_level, ...)
,几乎可以在任何 ngx_lua 阶段
进行日志的输出。
OpenResty
里面的 print
语句是 INFO 级别
#user nobody;
worker_processes 1;
error_log logs/error.log error; # 日志级别
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
server {
listen 80;
location / {
content_by_lua_block {
local num = 55
local str = "string"
local obj
ngx.log(ngx.ERR, "num:", num)
ngx.log(ngx.INFO, " string:", str) -- 不会打印 (日志级别为 error)
print([[i am print]]) -- 不会打印 (日志级别为 error)
ngx.log(ngx.ERR, " object:", obj)
}
}
}
}
Nginx 的日志级别
,请看下表:
ngx.STDERR -- 标准输出
ngx.EMERG -- 紧急报错
ngx.ALERT -- 报警
ngx.CRIT -- 严重,系统故障,触发运维告警系统
ngx.ERR -- 错误,业务不可恢复性错误
ngx.WARN -- 告警,业务中可忽略错误
ngx.NOTICE -- 提醒,业务比较重要信息
ngx.INFO -- 信息,业务琐碎日志信息,包含不同情况判断等
ngx.DEBUG -- 调试
对于应用开发,一般使用 ngx.INFO 到 ngx.CRIT 就够了
生产中错误日志开启到 error 级别就够了
网络日志输出
如果日志需要归集, 对时效性要求比较高 lua-resty-logger-socket
的目标是替代 Nginx 标准的 ngx_http_log_module
以非阻塞 IO 方式
推送 access log
到远程服务器上
lua_package_path "/path/to/lua-resty-logger-socket/lib/?.lua;;";
server {
location / {
log_by_lua_block {
local logger = require "resty.logger.socket"
if not logger.initted() then
local ok, err = logger.init{
host = 'xxx',
port = 1234,
flush_limit = 1234,
drop_limit = 5678,
}
if not ok then
ngx.log(ngx.ERR, "failed to initialize the logger: ",
err)
return
end
end
-- construct the custom access log message in
-- the Lua variable "msg"
local bytes, err = logger.log(msg)
if err then
ngx.log(ngx.ERR, "failed to log message: ", err)
return
end
}
}
}
lua-resty-logger-socket
几个好处:
- 基于
cosocket
非阻塞 IO 实现 - 日志累计到一定量,集体提交,增加网络传输利用率
- 短时间的网络抖动,自动容错
- 日志累计到一定量,如果没有传输完毕,直接丢弃
- 日志传输过程完全不落地,没有任何磁盘 IO 消耗
简单的API server框架
worker_processes 1; #nginx worker 数量
error_log logs/error.log; #指定错误日志文件路径
events {
worker_connections 1024;
}
http {
# 设置默认 lua 搜索路径,添加 lua 路径
# 此处写相对路径时,对启动 nginx 的路径有要求,必须在 nginx 目录下启动,require 找不到
# comm.param 绝对路径当然也没问题,但是不可移植,因此应使用变量 $prefix 或
# ${prefix},OR 会替换为 nginx 的 prefix path。
# lua_package_path 'lua/?.lua;/blah/?.lua;;';
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/]+) {
# 准入阶段完成参数验证
access_by_lua_file lua/access_check.lua;
#内容生成阶段
content_by_lua_file lua/$1.lua;
}
}
}
其他文件内容:
--========== {$prefix}/lua/addition.lua
local args = ngx.req.get_uri_args()
ngx.say(args.a + args.b)
--========== {$prefix}/lua/subtraction.lua
local args = ngx.req.get_uri_args()
ngx.say(args.a - args.b)
--========== {$prefix}/lua/multiplication.lua
local args = ngx.req.get_uri_args()
ngx.say(args.a * args.b)
--========== {$prefix}/lua/division.lua
local args = ngx.req.get_uri_args()
ngx.say(args.a / args.b)
--========== {$prefix}/lua/comm/param.lua
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
--========== {$prefix}/lua/access_check.lua
local param= require("comm.param")
local args = ngx.req.get_uri_args()
if not args.a or not args.b or not param.is_number(args.a, args.b) then
ngx.exit(ngx.HTTP_BAD_REQUEST)
return
end
整体目录关系:
.
├── 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
使用Nginx内置绑定变量
在OpenResty
中引用 Nginx内置绑定变量 ngx.var.VARIABLE
名称 | 说明 |
---|---|
$arg_name |
请求中的name参数 |
$args |
请求中的参数 |
$binary_remote_addr |
远程地址的二进制表示 |
$body_bytes_sent |
已发送的消息体字节数 |
$content_length |
HTTP请求信息里的”Content-Length” |
$content_type |
请求信息里的”Content-Type” |
$document_root |
针对当前请求的根路径设置值 |
$document_uri |
与$uri相同; 比如 /test2/test.php |
$host |
请求信息中的”Host”,如果请求中没有Host行,则等于设置的服务器名 |
$hostname |
机器名使用 gethostname系统调用的值 |
$http_cookie |
cookie 信息 |
$http_referer |
引用地址 |
$http_user_agent |
客户端代理信息 |
$http_via |
最后一个访问服务器的Ip地址。 |
$http_x_forwarded_for |
相当于网络访问路径 |
$is_args |
如果请求行带有参数,返回“?”,否则返回空字符串 |
$limit_rate |
对连接速率的限制 |
$nginx_version |
当前运行的nginx版本号 |
$pid |
worker进程的PID |
$query_string |
与$args相同 |
$realpath_root |
按root指令或alias指令算出的当前请求的绝对路径。其中的符号链接都会解析成真是文件路径 |
$remote_addr |
客户端IP地址 |
$remote_port |
客户端端口号 |
$remote_user |
客户端用户名,认证用 |
$request |
用户请求 |
$request_body |
这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义 |
$request_body_file |
客户端请求主体信息的临时文件名 |
$request_completion |
如果请求成功,设为”OK”;如果请求未完成或者不是一系列请求中最后一部分则设为空 |
$request_filename |
当前请求的文件路径名,比如/opt/nginx/www/test.php |
$request_method |
请求的方法,比如”GET”、”POST”等 |
$request_uri |
请求的URI,带参数 |
$scheme |
所用的协议,比如http或者是https |
$server_addr |
服务器地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费) |
$server_name |
请求到达的服务器名 |
$server_port |
请求到达的服务器端口号 |
$server_protocol |
请求的协议版本,”HTTP/1.0”或”HTTP/1.1” |
$uri |
请求的URI,可能和最初的值有不同,比如经过重定向之类的 |
location /sum {
# 使用access阶段完成准入阶段处理
access_by_lua_block {
local black_ips = {["127.0.0.1"]=true}
local ip = ngx.var.remote_addr
if true == black_ips[ip] then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
}
#处理业务
content_by_lua_block {
local a = tonumber(ngx.var.arg_a) or 0
local b = tonumber(ngx.var.arg_b) or 0
ngx.say("sum: ", a + b )
}
}
大多数内容都是不允许写入修改的
对于可写的变量中的limit_rate
,值得一提,它能完成传输速率限制,并且它的影响是单个请求级别
location /download {
access_by_lua_block {
ngx.var.limit_rate = 1000
};
}
子查询(子请求)
Nginx 子请求
是一种非常强有力的方式,它可以发起非阻塞
的内部请求访问目标 location
。
目标 location
可以是配置文件中其他文件目录
,或 任何 其他 nginx C 模块
,包括 ngx_proxy、ngx_fastcgi、ngx_memc、ngx_postgres、ngx_drizzle,甚至 ngx_lua 自身
等等 。
子请求
只是模拟 HTTP 接口的形式
, 没有 额外的 HTTP/TCP 流量
,也 没有 IPC (进程间通信) 调用
。
所有工作在 内部高效地在 C 语言级别完成
。
注意:
子请求
与 HTTP 301/302
重定向指令 (通过 ngx.redirect
) 完全不同
与内部重定向
((通过 ngx.exec
) 完全不同
在发起子请求前,用户程序应总是读取完整的 HTTP 请求体 (通过调用 ngx.req.read_body
或设置 lua_need_request_body
指令为 on
).
该 API 方法(ngx.location.capture_multi
也一样)总是缓冲整个请求体
到内存中。
因此,当需要处理一个大的子请求响应,用户程序应使用 cosockets
进行流式处理
,
注:
ngx.location.capture
和 ngx.location.capture_multi
指令无法抓取包含以下指令的 location:
add_before_body, add_after_body, auth_request, echo_location, echo_location_async, echo_subrequest, 或 echo_subrequest_async 。
location /foo {
content_by_lua_block {
res = ngx.location.capture("/bar")
}
}
location /bar {
echo_location /blah;
}
location /blah {
echo "Success!";
}
curl -i http://example.com/foo
不会按照预期工作。
不同阶段共享变量
在 OpenResty
的体系中
可以通过共享内存的方式
完成不同工作进程的数据共享
可以通过 Lua 模块方式
完成单个进程内不同阶段的数据共享
典型的例子,就是在 log 阶段记录一些请求的特殊变量
ngx.ctx
表 就是为了解决这类问题而设计的
location /test {
rewrite_by_lua_block {
ngx.ctx.foo = 76
}
access_by_lua_block {
ngx.ctx.foo = ngx.ctx.foo + 3
}
content_by_lua_block {
ngx.say(ngx.ctx.foo)
}
}
http://localhost/test # 79
单个进程内不同请求(不同阶段 rewrite_by_lua_block, access_by_lua_block, content_by_lua_block) 的数据共享
首先 ngx.ctx
是一个表
,所以我们可以对他添加、修改
。用来存储基于请求的 Lua 环境数据
,其生存周期
与当前请求相同 (类似 Nginx 变量)
。
一个最重要的特性:单个请求内的 rewrite (重写),access (访问),和 content (内容) 等各处理阶段是保持一致的
每个请求,包括子请求,都有一份自己的 ngx.ctx 表
location /sub {
content_by_lua_block {
ngx.say("sub pre: ", ngx.ctx.blah)
ngx.ctx.blah = 32
ngx.say("sub post: ", ngx.ctx.blah)
}
}
location /main {
content_by_lua_block {
ngx.ctx.blah = 73
ngx.say("main pre: ", ngx.ctx.blah)
local res = ngx.location.capture("/sub")
ngx.print(res.body)
ngx.say("main post: ", ngx.ctx.blah)
}
}
http://localhost/sub
sub pre: nil
sub post: 32
http://localhost/main
main pre: 73
sub pre: nil
sub post: 32
main post: 73
由于 ngx.ctx
保存的是指定请求资源,所以这个变量是不能直接共享给其他请求使用的
注意:
ngx.ctx 表查询
需要相对昂贵的元方法调用
,这比通过用户自己的函数参数直接传递基于请求的数据要慢得多
。
不要为了节约用户函数参数而滥用此 API,因为它可能对性能有明显影响。
防止SQL注入
SQL 注入
,就是通过把 SQL 命令
插入到 Web 表单提交或输入域名或页面请求的查询字符串
,最终达到欺骗服务器执行恶意的 SQL 命令
利用现有应用程序,将(恶意)的 SQL 命令注入到后台数据库引擎执行的能力,
它可以通过在 Web 表单中输入(恶意)SQL 语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行 SQL 语句
比如先前的很多影视网站泄露 VIP 会员密码大多就是通过 Web 表单递交查询字符暴出的,
这类表单特别容易受到 SQL 注入式攻击
SQL 注入例子
location /test {
content_by_lua_block {
local mysql = require "resty.mysql"
local db, err = mysql:new()
if not db then
ngx.say("failed to instantiate mysql: ", err)
return
end
db:set_timeout(1000) -- 1 sec
local ok, err, errno, sqlstate = db:connect{
host = "127.0.0.1",
port = 3306,
database = "ngx_test",
user = "ngx_test",
password = "ngx_test",
max_packet_size = 1024 * 1024 }
if not ok then
ngx.say("failed to connect: ", err, ": ", errno, " ", sqlstate)
return
end
ngx.say("connected to mysql.")
local res, err, errno, sqlstate =
db:query("drop table if exists cats")
if not res then
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
return
end
res, err, errno, sqlstate =
db:query("create table cats "
.. "(id serial primary key, "
.. "name varchar(5))")
if not res then
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
return
end
ngx.say("table cats created.")
res, err, errno, sqlstate =
db:query("insert into cats (name) "
.. "values (\'Bob\'),(\'\'),(null)")
if not res then
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
return
end
ngx.say(res.affected_rows, " rows inserted into table cats ",
"(last insert id: ", res.insert_id, ")")
-- 这里有 SQL 注入(后面的 drop 操作)
local req_id = [[1'; drop table cats;--]]
res, err, errno, sqlstate =
db:query(string.format([[select * from cats where id = '%s']], req_id))
if not res then
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
return
end
local cjson = require "cjson"
ngx.say("result: ", cjson.encode(res))
-- 再次查询,table 被删
res, err, errno, sqlstate =
db:query([[select * from cats where id = 1]])
if not res then
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
return
end
db:set_keepalive(10000, 100)
}
}
connected to mysql.
table cats created.
3 rows inserted into table cats (last insert id: 1)
result: [{"name":"Bob","id":"1"}]
bad result: failed to send query: cannot send query in the current context: 2: nil: nil.
规避 SQL注入
在 OpenResty
中,需要对输入参数进行一层过滤
即可
ndk.set_var.set_quote_sql_str
-- for MySQL
local req_id = [[1'; drop table cats;--]]
res, err, errno, sqlstate =
db:query(string.format([[select * from cats where id = %s]],
ndk.set_var.set_quote_sql_str(req_id)))
if not res then
ngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")
return
end
发起新的HTTP请求
OpenResty
最主要的应用场景之一是 API Server
, 需要高效的与其他 HTTP Server 调用
(HTTP 接口调用方法)
利用 proxy_pass
ngx.location.capture+proxy_pass
http {
upstream md5_server{
server 127.0.0.1:81; # ① 上游访问地址清单(可以按需配置不同的权重规则)
keepalive 20; # ② 上游访问长连接,是否开启长连接,对整体性能影响比较大
}
# # 业务系统
server {
listen 80;
location /test {
content_by_lua_block {
ngx.req.read_body()
local args, err = ngx.req.get_uri_args()
-- ③ 接口访问通过 ngx.location.capture 的子查询方式发起
local res = ngx.location.capture('/spe_md5',
{
method = ngx.HTTP_POST,
body = args.data
}
)
if 200 ~= res.status then
ngx.exit(res.status)
end
if args.key == res.body then
ngx.say("valid request")
else
ngx.say("invalid request")
end
}
}
# 调用
location /spe_md5 {
proxy_pass http://md5_server; # ④ 由于 ngx.location.capture 方式只能是 nginx 自身的子查询,需要借助 proxy_pass 发出 HTTP 连接信号
#For HTTP, the proxy_http_version directive should be set to “1.1” and the “Connection”
#header field should be cleared.(from:http://nginx.org/en/docs/http/ngx_http_upstream_module.html#keepalive)
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
# “盐” md5 计算 服务
server {
listen 81; # ⑤ 公共 API 输出服务
location /spe_md5 {
content_by_lua_block {
ngx.req.read_body()
local data = ngx.req.get_body_data()
ngx.print(ngx.md5(data .. "*&^%$#$^&kjtrKUYG"))
}
}
}
}
利用 cosocket
[推荐]
resty.http
http {
server {
listen 80;
location /test {
content_by_lua_block {
ngx.req.read_body()
local args, err = ngx.req.get_uri_args()
local http = require "resty.http" -- ① 引用 resty.http 库资源 [引用 resty.http 库资源 https://github.com/ledgetech/lua-resty-http]
local httpc = http.new()
local res, err = httpc:request_uri( -- ② 参考 resty-http 官方 wiki 说明,我们可以知道 request_uri 函数完成了连接池、HTTP 请求等一系列动作
"http://127.0.0.1:81/spe_md5",
{
method = "POST",
body = args.data,
}
)
if 200 ~= res.status then
ngx.exit(res.status)
end
if args.key == res.body then
ngx.say("valid request")
else
ngx.say("invalid request")
end
}
}
}
server {
listen 81;
location /spe_md5 {
content_by_lua_block {
ngx.req.read_body()
local data = ngx.req.get_body_data()
ngx.print(ngx.md5(data .. "*&^%$#$^&kjtrKUYG"))
}
}
}
}
如果你的内部请求比较少
,使用 ngx.location.capture+proxy_pass
的方式还没什么问题。
如果你的请求数量比较多
,或者需要频繁的修改上游地址
,那么 resty.http
就更适合你