在openResty中,ngx.location.capture_multi是一个非常强大的功能。可以应用于并发多个相互之间没有依赖的请求。在现代的应用架构中经常使用微服务,提供低粒度的接口;但在客户端(例如:app、网页服务)经常需要请求多个微服务接口,才能完整显示页面内容。
例如:打开一个商品详情页,需要请求:
- banner广告接口;
- 商品详情;
- 商品评论等。
那么ngx.location.capture_multi就派上大用场了,当然使用ngx.location.capture_multi不是唯一的办法,呵呵~。下面就来看看这个东东的用法;
先介绍一下下面这几个应用之间的差别;
- ngx.exec:nginx跳转;跳转到其他的location中执行。但仅限nginx内部的location。
- ngx.redirect:和nginx.exec相似,但支持外部跳转。
- ngx.location.capture_multi:并发请求;但仅限nginx内部的location。
- http包中multi方法:概念上与ngx.location.capture_multi相似,但支持外部接口。
一、ngx.location.capture
语法: res = ngx.location.capture(uri, options?)
作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*
1.1 uri
直接看栗子:
location ~ /comment/([0-9]+) { internal; set $goodsId $1; content_by_lua_block{ local args = ngx.req.get_uri_args() ngx.say("comments for goodsId :", ngx.var.goodsId) ngx.say("comments for goods:", args.offset) } } location ~ /goods/detail/([0-9]+) { set $goodsId $1; default_type plain/text; content_by_lua_block{ local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?offset=0") ngx.say(res.status) ngx.say(res.body) } }
返回结果:
200 comments for goodsId :123123 comments for goods:0
1.2 options
method: 请求方法,默认为ngx.HTTP_GET body: 请求内容,仅限于string 或 nil args: 请求参数,支持string 或 table vars: 变量,仅限于table ctx: 可参考中ngx.ctx的用法: openResty中ngx_lua模块提供的API copy_all_vars: 复制变量 share_all_vars: 共享变量 always_forward_body: 当设置为true时,父请求中的body转发到子请求。 默认是false,仅转发put和post请求方式中的body。如果设置body选项,则该设置失效。
1.2.1 always_forward_body
请看栗子:
栗子 01:
location ~ /comment/([0-9]+) {
internal;
set $goodsId $1;
content_by_lua_block{
ngx.req.read_body();
local args = ngx.req.get_uri_args()
local data = ngx.req.get_body_data()
ngx.say("comments for goodsId :", ngx.var.goodsId)
ngx.say("comments for rank:", args.rank)
ngx.say("comments for data :", data)
}
}
location ~ /goods/detail/([0-9]+) {
set $goodsId $1;
default_type plain/text;
content_by_lua_block{
ngx.req.read_body();
local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{
method = ngx.HTTP_GET,
always_forward_body = false,
})
ngx.say(res.status)
ngx.say(res.body)
}
}
请求raw: uid=37A059714A2B4B4280794DCA5C150DF0,请看如下输出
200 comments for goodsId :123123 comments for rank:5 comments for data :nil
将 栗子 01 中的:method = ngx.HTTP_GET ,更改成 method = ngx.HTTP_PUT或 method = ngx.HTTP_POST,请看如下输出:
200 comments for goodsId :123123 comments for rank:5 comments for data :uid=37A059714A2B4B4280794DCA5C150DF0
重新将 栗子 01 中的 always_forward_body = false 更改成 always_forward_body = true,其他不变,请看如下输出:
200 comments for goodsId :123123 comments for rank:5 comments for data :uid=37A059714A2B4B4280794DCA5C150DF0
结论 01:
always_forward_body:当设置为true时,父请求中的body转发到子请求。设置为false,仅转发put和post 请求方式中的body.
继续更改 栗子 01 :
local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{ method = ngx.HTTP_GET, body = 'hello, world', always_forward_body = false, --也可以设置为true })
查看输出结果:
200 comments for goodsId :123123 comments for rank:5 comments for data :hello, world
结论 02:
当选项中设置 body (只能为string)时,always_forward_body 选项失效。
1.2.2 args 和 vars
这一组的用法比较相似,放在一块讲了。看栗子吧。
栗子 02:
location ~ /comment/([0-9]+) {
internal;
set $goodsId $1;
content_by_lua_block{
local args = ngx.req.get_uri_args()
ngx.say("comments for goodsId :", ngx.var.goodsId)
ngx.say("comments for rank:", args.rank)
ngx.say("comments for args.a:", args.a)
ngx.say("comments for args.b:", args.b)
ngx.say("comments for vars.a:", ngx.var.a)
ngx.say("comments for vars.b:", ngx.var.b)
}
}
location ~ /goods/detail/([0-9]+) {
set $goodsId $1;
set $a '';
set $b '';
default_type plain/text;
content_by_lua_block{
local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{
method = ngx.HTTP_GET,
args = {a = "aa", b = "bb"},
vars = {a = "aa", b = "bb"},
})
ngx.say(res.status)
ngx.say(res.body)
}
}
输出结果:
200 comments for goodsId :123123 comments for rank:5 comments for args.a:aa comments for args.b:bb comments for vars.a:aa comments for vars.b:bb
从栗子 02 中可以看出,args 和 vars的区别。
结论 03 :
在发送参数到子请求中,一般参数使用 args;如特殊参数可以使用 vars,但也可以使用 args 代替。
1.23 ctx
请看栗子 03
栗子 03:
location ~ /comment/([0-9]+) {
internal;
set $goodsId $1;
content_by_lua_block{
ngx.ctx.foo = "bar"
}
}
location ~ /goods/detail/([0-9]+) {
set $goodsId $1;
default_type plain/text;
content_by_lua_block{
local c = {}
local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{
method = ngx.HTTP_GET,
ctx = c,
})
ngx.say(c.foo)
ngx.say(ngx.ctx.foo)
}
}
输出结果:
bar nil
1.24 copy_all_vars、share_all_vars
请看栗子04
栗子 04 :
location ~ /comment/([0-9]+) {
internal;
set $goodsId $1;
set $dog "$dog world";
echo "$uri dog: $dog";
}
location ~ /goods/detail/([0-9]+) {
set $goodsId $1;
default_type plain/text;
set $dog 'hello';
content_by_lua_block{
local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{
method = ngx.HTTP_GET,
share_all_vars = true,
})
ngx.print(res.body)
ngx.say(ngx.var.uri, ": ", ngx.var.dog)
}
}
输出结果:
/comment/123123 dog: hello world /goods/detail/123123/view: hello world
更改栗子 04 :更改 share_all_vars = true 成 copy_all_vars = true
查看输出结果:
/comment/123123 dog: hello world /goods/detail/123123/view: hello
结论 04 :
share_all_vars 可能会污染全局变量,不推荐使用。
二、ngx.location.capture_multi
和 ngx.location.capture 的用法相似,但可以同时并发多个请求。
查看栗子 0 5
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
local reqs = {}
table.insert(reqs, { "/mysql" })
table.insert(reqs, { "/postgres" })
table.insert(reqs, { "/redis" })
table.insert(reqs, { "/memcached" })
-- issue all the requests at once and wait until they all return
local resps = { ngx.location.capture_multi(reqs) }
-- loop over the responses table
for i, resp in ipairs(resps) do
-- process the response table "resp"
end
ngx.location.capture = function (uri, args)
return ngx.location.capture_multi({ {uri, args} })
end