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

内部子请求

子请求在内部执行,非常高效。子请求会缓冲所有的响应体,如果响应体很大,建议用 cosockets 来操作。

子请求默认情况下会继承所有父请求的请求头。

ngx.location.capture

1
syntax: res = ngx.location.capture(uri, options?)

基本用法

1
res = ngx.location.capture(uri)

res是一个表格,包含4个成员

res.status 子请求响应的状态码
res.header 表格的形式存放子请求的响应头,如果有同名多个响应头,那么值也为表格,比如假设响应头有3个 Set-Cookie。

1
2
3
Set-Cookie: a=3
Set-Cookie: foo=bar
Set-Cookie: baz=blah

那么 res.header["Set-Cookie"] 里的值为

1
{"a=3", "foo=bar", "baz=blah"}

res.body 存放响应体内容,必须通过检查 res.truncated 返回值来确认响应体是否完整,如果为 true,那么证明响应体被裁剪过了不完整,可能是由于一些错误导致的。

查询的参数也可以结合到uri比如

1
res = ngx.location.capture('/foo/bar?a=3&b=4')

这里不支持命名 location 比如 @foo,如果想使用只允许内部子请求的location,可以在 location 块里加上 internal 指令。

第二个可选参数表格:

method 指定请求方法比如 ngx.HTTP_POST。
body 指定的请求体,只支持字符串。
args 指定查询字符串,支持表格。
ctx 指定子请求里的 ngx.ctx 表格,如果当前请求里传入 ngx.ctx ,那么可以实现父子请求共用上下文。
vars 表格形式指定传递给子请求的变量,在子请求可以用 ngx.var.name 获取。
copy_all_vars 布尔值,是否复制父级所有nginx变量到子请求,那么在子请求改变变量值不会影响到福请求。
share_all_vars 布尔值,是否共享当前所有变量到子请求。
always_forward_body 布尔值,是否总是发送当前请求体。如果开启,那么如果没有指定 body 参数,会把当前通过 ngx.req.read_body() 收到的请求体直接发送给子请求而无需拷贝请求体数据,不管请求体数据在内存里还是在临时文件里。默认为false,如果没有指定 body 参数,只有子请求方法为 PUT 或者 POST 时才放松当前请求体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
--POST请求
res = ngx.location.capture(
'/foo/bar',
{ method = ngx.HTTP_POST, body = 'hello, world' }
)
--下面3个等同
ngx.location.capture('/foo?a=1',
{ args = { b = 3, c = ':' } }
)

ngx.location.capture('/foo?a=1&b=3&c=%3a')

ngx.location.capture('/foo?a=1',
{ args = 'b=3&c=%3a' } }
)

共享参数可能会造成一些负面影响,最好用 args、vars、copy_all_vars 代替。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location /other {
set $dog "$dog world";
echo "$uri dog: $dog";
}

location /lua {
set $dog 'hello';
content_by_lua_block {
res = ngx.location.capture("/other",
{ share_all_vars = true });

ngx.print(res.body)
ngx.say(ngx.var.uri, ": ", ngx.var.dog)
}
}
[root@192 ~]# curl localhost/lua
/other dog: hello world
/lua: hello world

如果同时指定,copy_all_vars 和 share_all_vars,那么 share_all_vars 优先级更高。

ctx 选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
location /sub {
content_by_lua_block {
ngx.ctx.foo = "bar";
}
}
location /lua {
content_by_lua_block {
local ctx = {}
res = ngx.location.capture("/sub", { ctx = ctx })

ngx.say(ctx.foo);
ngx.say(ngx.ctx.foo);
}
}
bar
nil

当然也可以通过 ctx 选项来共享请求上下文。

1
2
3
4
5
6
7
8
9
10
11
12
location /sub {
content_by_lua_block {
ngx.ctx.foo = "bar";
}
}
location /lua {
content_by_lua_block {
res = ngx.location.capture("/sub", { ctx = ngx.ctx })
ngx.say(ngx.ctx.foo);
}
}
bar

ngx.location.capture_multi

同上,只是它支持多个子请求平行运行,按相同的顺序返回,所花费的时间为多个请求里时间最长的那个。

1
2
3
4
5
6
7
8
9
10
11
12
13
res1, res2, res3 = ngx.location.capture_multi{
{ "/foo", { args = "a=3&b=4" } },
{ "/bar" },
{ "/baz", { method = ngx.HTTP_POST, body = "hello" } },
}

if res1.status == ngx.HTTP_OK then
...
end

if res2.body == "BLAH" then
...
end

上面两个指令的关系类似于下面

1
2
3
4
ngx.location.capture =
function (uri, args)
return ngx.location.capture_multi({ {uri, args} })
end

内部重定向 - ngx.exec

1
syntax: ngx.exec(uri, args?)

执行内部重定向到 uri,并附加上 args 参数。

1
2
3
4
5
6
7
8
ngx.exec('/some-location')
ngx.exec('/some-location', 'a=3&b=5&c=6')
ngx.exec('/some-location?a=3&b=5', 'c=6')

ngx.exec("/foo", "a=3&b=hello+world")

--表格
ngx.exec("/foo", { a = 3, b = "hello world" })

通过表格的形式传递参数,类似于 ngx.encode_args 方法。

ngx.exec 支持命名 location,但是 args 参数将会被忽略,不过可以通过 uri 传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
location /foo {
content_by_lua_block {
return ngx.exec("@bar", "a=goodbye");
}
}

location @bar {
content_by_lua_block {
local args = ngx.req.get_uri_args()
for key, val in pairs(args) do
if key == "a" then
ngx.say(val)
end
end
}
}
[root@192 ~]# curl localhost/foo?a=hello
hello

注意:重定向前必须没有任何输出,也就是必须在 ngx.send_headers 或者是 ngx.print、ngx.say之前调用。建议配合 return 来强调重定向后会终止请求。

外部重定向 - ngx.redirect

1
syntax: ngx.redirect(uri, status?)

支持的状态码有:301、302(默认)、303、307、308。

1
2
3
4
5
6
7
8
9
return ngx.redirect("/foo")
--等价
return ngx.redirect("/foo", ngx.HTTP_MOVED_TEMPORARILY)
return ngx.redirect("http://www.google.com")

return ngx.redirect("/foo", 301)

--可以指定参数
return ngx.redirect('/foo?a=3&b=4')

该重定向类似于 ngx_http_rewrite_module 里,rewrite 指令配合 redirect 修饰符。

1
rewrite ^ /foo? redirect; #nginx 配置

等同于

1
return ngx.redirect('/foo');  -- Lua code

下面也等同

1
2
rewrite ^ /foo? permanent; #nginx 配置
return ngx.redirect('/foo', ngx.HTTP_MOVED_PERMANENTLY) -- lua代码

注意:重定向前必须没有任何输出,也就是必须在 ngx.send_headers 或者是 ngx.print、ngx.say之前调用。建议配合 return 来强调重定向后会终止请求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
location /foo {
content_by_lua_block {
return ngx.redirect("/bar");
}
}

location /bar {
content_by_lua_block {
ngx.print("bar")
}
}
[root@192 ~]# curl localhost/foo -v
...
>
< HTTP/1.1 302 Moved Temporarily
< Server: openresty/1.13.6.2
< Date: Wed, 01 Aug 2018 09:30:45 GMT
< Content-Type: text/html
< Content-Length: 167
< Connection: keep-alive
< Location: /bar