OpenResty中的ngx.location.capture和ngx.location.capture_multi使用

  |   0 评论   |   0 浏览

Nginx Location 配置总结

语法:location [=||*|^~] /uri/ { … } 配置块:server

  • = 表示将 URI 作为字符串,以便于参数中的 URI 做完全匹配。
  • ~ 表示匹配 URI 时,字母大小写是敏感的。
  • ~*表示匹配 URI 时,忽略字母大小写。
  • ^~ 表示匹配 URI 时,只需要前半部分与 uri 参数匹配即可。
  • @表示用于 nginx 服务器内部请求之间的重定向,带有@的 location 不直接处理用户请求。
  • uri 参数可以使用正则表达式。
  • 使用 locaiton / {}可以匹配所有的请求。
  • 首先匹配 =,其次匹配 ^~, 其次是按文件中顺序的正则匹配,最后是交给 / 通用匹配。当有匹配成功时候,停止匹配,按当前匹配规则处理请求。
  • = 精确匹配会第一个被处理。如果发现精确匹配,nginx 停止搜索其他匹配。
  • 普通字符匹配,正则表达式规则和长的块规则将被优先和查询匹配,也就是说如果该项匹配还需去看有没有正则表达式匹配和更长的匹配。
  • ^~ 则只匹配该规则,nginx 停止搜索其他匹配,否则 nginx 会继续处理其他 location 指令。
  • 最后匹配理带有””和”*”的指令,如果找到相应的匹配,则 nginx 停止搜索其他匹配;当没有正则表达式或者没有正则表达式被匹配的情况下,那么匹配程度最高的逐字匹配指令会被使用。


ngx.location.capture 只支持相对路径,不能用绝对路径的解决办法

ngx.location.capture 是非阻塞的,ngx.location.capture 也可以用来完成 http 请求,但是它只能请求到相对于当前 nginx 服务器的路径,不能使用之前的绝对路径进行访问,但是我们可以配合 nginx upstream 实现我们想要的功能。在 nginx.cong 中的 http 部分添加如下 upstream 配置

upstream backend {
    server s.taobao.com;
    keepalive 100;
}

# 在example.conf配置如下location
location ~ /proxy/(.*) {
    internal;
    proxy_pass http://backend/$1$is_args$args;
}

# lua 请求可以这么写
local resp = ngx.location.capture("/proxy/search", {
    method = ngx.HTTP_GET,
    args = {q = "hello"}
})

if not resp then
    ngx.say("request error :", err)
    return
end

ngx.log(ngx.ERR, tostring(resp.status))

# 获取状态码
ngx.status = resp.status

# 获取响应头
for k, v in pairs(resp.header) do
    if k ~= "Transfer-Encoding" and k ~= "Connection" then
        ngx.header[k] = v
    end
end

# 响应体
if resp.body then
    ngx.say(resp.body)
end

ngx.location.capture, ngx.location.capture_multi介绍

在 openResty 中,ngx.location.capture_multi是一个非常强大的功能。可以应用于并发多个相互之间没有依赖的请求。在现代的应用架构中经常使用微服务,提供低粒度的接口;但在客户端(例如:app、网页服务)经常需要请求多个微服务接口,才能完整显示页面内容。例如:打开一个商品详情页,需要请求:

  1. banner 广告接口;
  2. 商品详情;
  3. 商品评论等。

那么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*

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

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选项,则该设置失效。

always_forward_body

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

将 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

重新将 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.

继续更改:

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 选项失效。

args 和 vars

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

结论 03 :

在发送参数到子请求中,一般参数使用 args;如特殊参数可以使用 vars,但也可以使用 args 代替。

ctx

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

copy_all_vars、share_all_vars

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

更改 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 的用法相似,但可以同时并发多个请求。

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


参见:http://www.hangdaowangluo.com/archives/2712