摘自:http://www.daileinote.com/computer/openresty/08

ngx.req.is_internal

1
syntax: is_internal = ngx.req.is_internal()

判断是否为内部请求。

ngx.get_phase

1
syntax: str = ngx.get_phase()

获取当前阶段名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
init         #init_by_lua*.
init_worker #init_worker_by_lua*.
ssl_cert #ssl_certificate_by_lua*.
ssl_session_fetch #ssl_session_fetch_by_lua*.
ssl_session_store #ssl_session_store_by_lua*.
set #set_by_lua*.
rewrite #rewrite_by_lua*.
balancer #balancer_by_lua*.
access #access_by_lua*.
content #content_by_lua*.
header_filter #header_filter_by_lua*.
body_filter #body_filter_by_lua*.
log #log_by_lua*.
timer #ngx.timer.*.

ngx.req.start_time

1
syntax: secs = ngx.req.start_time()

返回请求创建的时间戳,格式为 1533118835.492。

ngx.req.http_version

1
syntax: num = ngx.req.http_version()

返回 http 协议版本比如 2.0、1.0、1.1、0.9,识别不了返回 nil 。

ngx.req.raw_header

1
syntax: str = ngx.req.raw_header(no_request_line?)

默认返回请求行 + 请求头,传入 true 代表去掉请求行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
location /test {
default_type text/html;
content_by_lua_block {
ngx.print(ngx.req.raw_header())
ngx.print(ngx.req.raw_header(true))
}
}
[root@192 ~]# curl localhost/test
GET /test HTTP/1.1
User-Agent: curl/7.29.0
Host: localhost
Accept: */*

User-Agent: curl/7.29.0
Host: localhost
Accept: */*

ngx.req.get_method

1
syntax: method_name = ngx.req.get_method()

获取请求方法比如 "GET" "POST" 等。

ngx.req.set_uri

1
syntax: ngx.req.set_uri(uri, jump?)

重写url,jump设置为 true 会触发 location 重新匹配,默认为 false。

1
rewrite ^ /foo last;  #配置

等同于

1
ngx.req.set_uri("/foo", true)  --lua代码
1
rewrite ^ /foo break;

等同于

1
2
ngx.req.set_uri("/foo", false)
ngx.req.set_uri("/foo")

如果 jump 设置为 true 执行 location 重新匹配,只能在 rewrite_by_lu* 里调用,在其他地方将会出错。

1
2
3
4
5
6
7
location /test {
rewrite_by_lua_block {
local uri = ngx.re.sub(ngx.var.uri, "^/test/(.*)", "/$1", "o")
ngx.req.set_uri(uri)
}
proxy_pass http://my_backend;
}

等同于

1
2
3
4
location /test {
rewrite ^/test/(.*) /$1 break;
proxy_pass http://my_backend;
}

ngx.req.set_method

1
syntax: ngx.req.set_method(method_id)

利用常量id设置请求头比如 ngx.HTTP_POST,如果当前是子请求,那么子请求方法会被覆盖。

ngx.req.set_uri_args

1
syntax: ngx.req.set_uri_args(args)

设置重写的uri参数,args可以是字符串或者是表格。

1
2
3
4
5
ngx.req.set_uri_args("a=3&b=hello+world")
ngx.req.set_uri_args({ a = 3, b = "hello world" })

-- a=3&b=5&b=6
ngx.req.set_uri_args({ a = 3, b = {5, 6} })

下面等同

1
rewrite ^ /foo?a=3? last;

等同于

1
2
3
4
5
ngx.req.set_uri_args("a=3")
ngx.req.set_uri("/foo", true)
--或者
ngx.req.set_uri_args({a = 3})
ngx.req.set_uri("/foo", true)

ngx.req.get_uri_args

1
syntax: args, err = ngx.req.get_uri_args(max_args?)

获取请求参数到表格,默认最大参数为 100个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
location = /test {
content_by_lua_block {
local args, err = ngx.req.get_uri_args(100)

if err == "truncated" then
-- 超过了最大参数个数会到此
end

for key, val in pairs(args) do
if type(val) == "table" then
ngx.say(key, ": ", table.concat(val, ", "))
else
ngx.say(key, ": ", val)
end
end
}
}
[root@192 ~]# curl 'localhost/test?name=freecls&name=222&age=22&has_equal=&no_equal'
no_equal: true
name: freecls, 222
has_equal:
age: 22

支持动态修改 $args 变量。

1
2
3
4
ngx.var.args = "a=3&b=42"
local args, err = ngx.req.get_uri_args()

-- {a = 3, b = 42}

max_args 设置为 0 代表不限制参数个数,但是极度不推荐。

1
local args, err = ngx.req.get_uri_args(0)

ngx.req.get_post_args

1
syntax: args, err = ngx.req.get_post_args(max_args?)

解析请求体数据为表格(mime 类型必须为 application/x-www-form-urlencoded)。注意:必须先读取请求体比如 ngx.req.read_body() 或者 lua_need_request_body on。其他的同上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
location = /test {
content_by_lua_block {
ngx.req.read_body()
local args, err = ngx.req.get_post_args()

if err == "truncated" then
-- 超过了最大参数个数会到此
end
for key, val in pairs(args) do
if type(val) == "table" then
ngx.say(key, ": ", table.concat(val, ", "))
else
ngx.say(key, ": ", val)
end
end
}
}
[root@192 ~]# curl 'localhost/test' -d 'name=freecls&name=222&age=22&has_equal=&no_equal'
err:nil
no_equal: true
name: freecls, 222
has_equal:
age: 22

ngx.req.get_headers

1
syntax: headers, err = ngx.req.get_headers(max_headers?, raw?)

获取请求头到表格,如果只是过去单个请求头,最好使用 nginx 内部变量。nginx 内部变量的请求头名都是经过转化的,连字符转为下划线并且全部转化为小写,多个同名请求头只取第一个。

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
location = /test {
content_by_lua_block {
local h, err = ngx.req.get_headers()

if err == "truncated" then
-- 超过最大请求头限制
end
for key, val in pairs(h) do
if type(val) == "table" then
ngx.say(key, ": ", table.concat(val, ", "))
else
ngx.say(key, ": ", val)
end
end

--通过 nginx 内部变量获取
-- 注意连字符 - 将会被转化为 _
ngx.say(ngx.var["http_head_cus"])
}
}
[root@192 ~]# curl localhost/test -H "Head-Cus: freecls" -H "name: 1" -H "name: 2" -H "head-cus: cus2"
host: localhost
user-agent: curl/7.29.0
accept: */*
name: 1, 2
head-cus: freecls, cus2
freecls

第二个参数默认为 false,__index 元方法将会加入到结果表格该方法将请求头名称将会转化为小写,并且将会把下划线 _ 转化为连字符 - 。

下面几个都能获取到请求头。

1
2
3
ngx.say(headers.head_cus)
ngx.say(headers["Head-Cus"])
ngx.say(headers["head-cus"])

如果第二个参数设为 true,那么就不会转化任何请求头,那么下面两个请求头要分别获取。

1
2
3
ngx.say(headers["head-cus"])

ngx.say(headers["Head-Cus"])

ngx.req.set_header

1
syntax: ngx.req.set_header(header_name, header_value)

设置或覆盖请求头,主要用于子请求,因为子请求会继承当前请求头。

1
2
3
4
5
6
7
8
ngx.req.set_header("Content-Type", "text/css")

-- 设置多个同名
ngx.req.set_header("Foo", {"a", "abc"})
-- 移除请求头
ngx.req.set_header("X-Foo", nil)

ngx.req.clear_header("X-Foo")

ngx.req.clear_header

移除请求头

ngx.header.HEADER

1
syntax: ngx.header.HEADER = VALUE

获取、新增、修改、清除响应头,下划线 _ 将会被替换成连字符 - 。可以通过 lua_transform_underscores_in_response_headers 配置指令来关闭。而且是大小写不敏感的。获取时不存在则返回 nil。

1
2
3
4
ngx.header["Content-Type"] = 'text/plain'
ngx.header.content_type = 'text/plain';

ngx.header["X-My-Header"] = 'blah blah';

支持多个值

1
2
3
4
ngx.header['Set-Cookie'] = {'a=32; path=/', 'b=4; path=/'}

-- Set-Cookie: a=32; path=/
-- Set-Cookie: b=4; path=/

多个值但是该响应头只支持一个值,那么取最后那个。

1
2
3
ngx.header.content_type = {'a', 'b'}
-- 等同于
ngx.header.content_type = 'b'

移除请求头

1
2
3
ngx.header["X-My-Header"] = nil;

ngx.header["X-My-Header"] = {};

一般放在 header_filter_by_lua* 阶段来处理

1
2
3
4
5
6
7
8
9
10
11
12
13
location /test {
set $footer '';

proxy_pass http://some-backend;

header_filter_by_lua_block {
if ngx.header["X-My-Header"] == "blah" then
ngx.var.footer = "some value"
end
}

echo_after_body $footer;
}

ngx.header 不是常规的表格,所以不能遍历。

ngx.resp.get_headers

1
syntax: headers, err = ngx.resp.get_headers(max_headers?, raw?)

返回所有响应头存放在表格里,用法请参照 ngx.req.get_headers。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
location = /test {
content_by_lua_block {
local h, err = ngx.resp.get_headers()

if err == "truncated" then
-- 超过最大请求头限制
end
for key, val in pairs(h) do
if type(val) == "table" then
ngx.say(key, ": ", table.concat(val, ", "))
else
ngx.say(key, ": ", val)
end
end

}
}
[root@192 ~]# curl localhost/test
connection: keep-alive
content-type: application/octet-stream

ngx.req.read_body

1
syntax: ngx.req.read_body()

读取请求体并保存,如果超过了 client_body_buffer_size 指定的大小将会存入临时文件。所有需要用到请求体的 API 都必须先读取请求体。或者设置 lua_need_request_body on。

ngx.req.discard_body

1
syntax: ngx.req.discard_body()

显性的接收请求体并丢弃,立马返回。一般用于客户端带上了请求体,但是服务端用不到。

ngx.req.get_body_data

1
syntax: data = ngx.req.get_body_data()

以字符串形式返回读取到内存里的请求体数据。如下情形之一的会返回 nil:

1.没有读取请求体比如没调用 ngx.req.read_body()
2.请求体存入了临时文件
3.请求体大小为0

用它来获取请求体比用变量 $request_body 快,因为少一次内存分配。

ngx.req.get_body_file

1
syntax: file_name = ngx.req.get_body_file()

返回存取请求体的临时文件名,没有读取请求体或者请求体存在内存里返回 nil。

返回的文件名只读,nginx 会帮助我们删除。

ngx.req.set_body_data

1
syntax: ngx.req.set_body_data(data)

设置请求体,也要先读取请求体。

ngx.req.set_body_file

1
syntax: ngx.req.set_body_file(file_name, auto_clean?)

设置读取请求体的文件名,也要先读取请求体。auto_clean 默认为 false,请求结束后不会移除文件。

如果设置 auto_clean 为true,那么当请求结束或者在当前请求调用 ngx.req.set_body_data 将会移除文件,必须保证改文件存在并且 nginx 拥有适当的权限操作该文件。

ngx.req.init_body

1
syntax: ngx.req.init_body(buffer_size?)

创建新的块来存储请求体,默认为 client_body_buffer_size 。超过该大小会存入临时文件,跟正常请求处理逻辑一样。

一般在 rewrite_by_lua* 或者 access_by_lua* 阶段配合 ngx.req.append_body,ngx.req.finish_body,ngx.req.socket 实现高效输入过滤,同样可以配合 upstream 模块比如 ngx_http_proxy_module 和 ngx_http_fastcgi_module。

ngx.req.append_body

1
syntax: ngx.req.append_body(data_chunk)

附加新的块到 ngx.req.init_body 创建的缓冲区。

ngx.req.finish_body

1
syntax: ngx.req.finish_body()

标记结束。

1
2
3
4
5
ngx.req.init_body(128 * 1024)  -- buffer is 128KB
for chunk in next_data_chunk() do
ngx.req.append_body(chunk) -- 一次读取 4KB
end
ngx.req.finish_body()

ngx.req.socket

1
syntax: tcpsock, err = ngx.req.socket(raw)

返回只读的 cosocket 对象,可以通过 receive 和 receiveuntil 来接收数据,一般是用来以流的形式接收大量的请求体。注意不要开启 lua_need_request_body。

如果指定了 raw 参数为true,那么就是原始的 tcp 连接,可以使用 receive 和 receiveuntil 和 send 收发数据,但是之前不能有准备发送的数据比如之前调用了 ngx.say()、ngx.print、ngx.send_headers等,如果有,那么要先调用 ngx.flush(true) 发送出去。

详细用法请参照 openresty - lua API(7),这篇文章讲的是与上游服务器的tcp或者是udp连接,也就是跟当前 http 请求连接是无关的。

而 ngx.req.socket 创建的是用来处理当前 http 连接的,可以接收当前 http 的请求体,也可以发送响应给浏览器客户端。

ngx.send_headers

1
syntax: ok, err = ngx.send_headers()

主动发送响应头,成功返回 1,失败返回 nil 加错误信息。一般无需调用,ngx.say、ngx.print 或者 content_by_lua* 正常结束都会自动发送响应头。

ngx.headers_sent

1
syntax: value = ngx.headers_sent

返回 true 代表响应头已经发送。

ngx.is_subrequest

1
syntax: value = ngx.is_subrequest

返回true,如果是子请求。

ngx.on_abort

1
syntax: ok, err = ngx.on_abort(callback)

注册回调函数,当客户端非正常关闭连接时调用,必须设置 lua_check_client_abort on。

1
2
3
4
5
6
7
8
9
10
11
12
local function my_cleanup()
-- 清理工作可以在这里执行

-- now abort all the "light threads" running in the current request handler
ngx.exit(499)
end

local ok, err = ngx.on_abort(my_cleanup)
if not ok then
ngx.log(ngx.ERR, "failed to register the on_abort callback: ", err)
ngx.exit(500)
end

时间相关

ngx.today

1
syntax: str = ngx.today()

从 nginx 缓存里获取 yyyy-mm-dd 格式的时间,没有额外的系统调用。

ngx.time

1
syntax: secs = ngx.time()

从 nginx 缓存里获取时间戳。

ngx.now

1
syntax: secs = ngx.now()

从 nginx 缓存里获取时间戳,小数位为毫秒,格式为 1533134658.555。

ngx.update_time

1
syntax: ngx.update_time()

要 nginx 更新缓存时间,会造成系统调用。

ngx.localtime

1
syntax: str = ngx.localtime()

从 nginx 缓存里获取本地时间格式为 yyyy-mm-dd hh:mm:ss。

ngx.utctime

1
syntax: str = ngx.utctime()

从 nginx 缓存里获取 UTC 时间格式为 yyyy-mm-dd hh:mm:ss。

1
syntax: str = ngx.cookie_time(sec)

转化时间戳为 cookie 的时间格式。

ngx.http_time

1
syntax: str = ngx.http_time(sec)

转化时间戳为 http头使用的时间格式,比如 Last-Modified。

ngx.parse_http_time

1
syntax: sec = ngx.parse_http_time(str)

转化 http格式时间为时间戳。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
location = /test {
content_by_lua_block {

ngx.say(ngx.today())
ngx.say(ngx.time())
ngx.say(ngx.now())
ngx.say(ngx.localtime())
ngx.say(ngx.utctime())
ngx.say(ngx.cookie_time(ngx.time()))
ngx.say(ngx.http_time(ngx.time()))
ngx.say(ngx.parse_http_time("Thu, 18 Nov 2010 11:27:35 GMT"))

}
}
[root@192 ~]# curl 'localhost/test'
2018-08-01
1533134658
1533134658.555
2018-08-01 22:44:18
2018-08-01 14:44:18
Wed, 01-Aug-18 14:44:18 GMT
Wed, 01 Aug 2018 14:44:18 GMT
1290079655