1. 日志输出
vim /usr/local/openresty/nginx/conf/nginx.conf
默认配置如下
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;#pid logs/nginx.pid;http {#log_format main '$remote_addr - $remote_user [$time_local] "$request" '# '$status $body_bytes_sent "$http_referer" '# '"$http_user_agent" "$http_x_forwarded_for"';#access_log logs/access.log main;
我们访问error.log,内容如下:
access.log,内容如下
# $remote_addr - 表示客户端发起请求的IP地址
# $remote_user - 表示发起请求的经过身份验证的用户的用户名(如果有)
# $time_local - 表示请求发起的本地时间
# $request - 表示客户端发起的HTTP请求,包括方法、URL和HTTP版本
# $status - 表示服务器返回的请求的HTTP状态码
# $body_bytes_sent - 表示服务器在响应体中发送的字节数
# $http_referer - 表示HTTP Referer头,指示引导客户端访问当前页面的页面的URL
# $http_user_agent - 表示客户端的Web浏览器或其他HTTP客户端的用户代理字符串
# $http_x_forwarded_for - 表示X-Forwarded-For头,通常用于跟踪客户端通过代理或负载均衡器连接到Web服务器时的原始IP地址
127.0.0.1 - - [07/Jan/2024:06:13:31 +0800] "GET login.cgi HTTP/1.1" 400 163 "-" "-"
如果你的日志需要归集,并且对时效性要求比较高那么用 lua-resty-logger-socket
lua-resty-logger-socket的目标是替代 Nginx 标准的 ngx_http_log_module 以非阻塞 IO 方式推送 access log 到远程服务器上。对远程服务器的要求是支持 syslog-ng 的日志服务
2. 子查询
Nginx 子请求是一种非常强有力的方式,它可以发起非阻塞的内部请求访问目标 location
需要注意的是,子请求只是模拟 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 进行流式处理
下面是一个简单例子:
res = ngx.location.capture(uri)
- 返回一个包含四个元素的 Lua 表 (res.status, res.header, res.body, 和 res.truncated)。
- res.status (状态) 保存子请求的响应状态码。
- res.header (头) 用一个标准 Lua 表储子请求响应的所有头信息。如果是“多值”响应头,这些值将使用 Lua (数组) 表顺序存储。例如,如果子请求响应头包含下面的行,则 res.header[“Set-Cookie”] 将存储 Lua 表 {“a=3”, “foo=bar”, “baz=blah”}。
Set-Cookie: a=3Set-Cookie: foo=barSet-Cookie: baz=blah
- res.body (体) 保存子请求的响应体数据,它可能被截断。用户需要检测 res.truncated (截断) 布尔值标记来判断 res.body 是否包含截断的数据。这种数据截断的原因只可能是因为子请求发生了不可恢复的错误,例如远端在发送响应体时过早中断了连接,或子请求在接收远端响应体时超时
3. 不同阶段共享变量
在 OpenResty 的体系中,可以通过共享内存的方式完成不同工作进程的数据共享,可以通过 Lua 模块方式完成单个进程内不同请求的数据共享
如何完成单个请求内不同阶段的数据共享呢?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)}}
首先 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 = 32ngx.say("sub post: ", ngx.ctx.blah)}}location /main {content_by_lua_block {ngx.ctx.blah = 73ngx.say("main pre: ", ngx.ctx.blah)local res = ngx.location.capture("/sub")ngx.print(res.body)ngx.say("main post: ", ngx.ctx.blah)}}
访问 GET /main 输出
main pre: 73sub pre: nilsub post: 32main post: 73
4. 防止SQL注入
对于 MySQL ,可以调用 ndk.set_var.set_quote_sql_str ,进行一次过滤即可,如果恰巧你使用的是 PostgreSQL ,调用 ndk.set_var.set_quote_pgsql_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 thenngx.say("bad result: ", err, ": ", errno, ": ", sqlstate, ".")return
end
5. 发起新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" -- ①local httpc = http.new()local res, err = httpc:request_uri( -- ②"http://127.0.0.1:81/spe_md5",{method = "POST",body = args.data,})if 200 ~= res.status thenngx.exit(res.status)endif args.key == res.body thenngx.say("valid request")elsengx.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"))}}}
}
重点解释:
① 引用 resty.http 库资源,它来自 github https://github.com/pintsized/lua-resty-http, 下载tar.gz解压lua文件至openresty/lualib/resty/usr/local/openresty/lualib/resty
② 参考 resty-http 官方 wiki 说明,我们可以知道 request_uri 函数完成了连接池、HTTP 请求等一系列动作