Skip to content

第十三章、子请求

xikder edited this page Dec 14, 2018 · 1 revision

Nginx一般分两种请求类型,一种是主请求;一种是子请求,即subrequest。主请求从Nginx的外部进行访问,而子请求则在Nginx内部进行访问。子请求不是HTTP请求,不会增加网络开销。它的主要作用是将一个主请求分解为多个子请求,用子请求去访问指定的location服务,最后汇总到一起完成主请求的任务。

Nginx的请求方法有很多种,如GET、POST、 PUT 、DELETE等,同样,子请求也支持这些请求方法。

13.1 请求方法

Lua API中提供了多个指令来实现子请求,Lua API常见的请求方法说明见表7-2。 表7-2 Lua API常见的请求方法说明

13.2 单一子请求


ngx.location.capture

语法:res = ngx.location.capture(uri, options?)

配置环境:rewrite_by_lua*,access_by_lua*,content_by_lua*

含义:发出同步但不阻塞Nginx的子请求。可以用来访问指定的location,但不支持访问命名location(如@abc 就是命名location)。location中可以有静态文件,如ngx_proxy、ngx_fastcgi、ngx_memc、ngx_postgres、ngx_drizzle,甚至是Ngx_Lua和Nginx的c模块。 子请求总是会把整个请求体缓存到内存中,如果要处理一个较大的子请求,使用cosockets是最好的选择(cosockets是与ngx.socket.tcp有关的API)。 子请求一般在内部进行访问,建议在被子请求访问的location上配置internal,即只允许内部访问。 子请求返回的结果res,它是一个table类型的数据,包含4个元素:res.status、res.header、res.body和res.truncated,res的元素名及其用途见表7-3。

表7-3 res的元素名及其用途

ngx.location.capture的第2个参数options是可选参数,也可以包含多个参数,示例如下: server { listen 80; server_name testnginx.com; default_type 'text/plain';

location = /main {
    set $m 'hello';
    content_by_lua_block {
        local ngx = require "ngx";
        --发起子请求,访问/test,请求方式是GET,请求体是test nginx,子请求的URL参数是a=1&b=2,并使用copy_all_vars将主请求的Nginx变量($m)全部复制到子请求中
          local res = ngx.location.capture(
          '/test',  { method = ngx.HTTP_GET , body = 'test nginx',
           args = { a = 1, b = 2 },copy_all_vars = true }
        )
        ngx.say(res.status)
        ngx.say(res.body)
        ngx.say(type(res.header))
        ngx.say(type(res.truncated))
    }
}
location = /test

{ #只能在Nginx内部进行访问 internal; content_by_lua_block { local ngx = require "ngx"; --获取请求体,在这里是获取主请求的请求体 ngx.req.read_body() local body_args = ngx.req.get_body_data() --输出请求的参数,获取主请求的m变量的值,并与world进行字符串拼接 ngx.print('request_body: ' ,body_args, ' capture_args: ', ngx.var.args, '--- copy_all_vars : ', ngx.var.m .. 'world! ') } } }

执行结果如下:

# curl 'http://testnginx.com/main'

200

request_body:test nginx capture_args:a=1&b=2--- copy_all_vars : helloworld!

table

boolean

从示例中可以看出:

1.ngx.location.capture的第2个参数options可以包含多个table类型的参数。

2.子请求的请求方法由参数method进行配置,示例中的请求方法为GET。

3.子请求通过参数body可以定义新的请求体。

4.子请求通过参数args可以配置新的URL的args,args是table类型的。

5.copy_all_vars = true的作用是将主请求的全部变量传递给子请求,如果没有此配置就不会传递过去。

6.从子请求的返回结果可以获取状态码、响应体、响应头、结果是否被截断。

根据上面的介绍可知,下面两种方式是等价的: local res = ngx.location.capture('/test?a=1&b=2') local res = ngx.location.capture('/test , args = { a = 1, b = '2' }') ngx.location.capture 还支持更丰富的参数操作,具体如下。

1.vars参数,table类型,可以设置子请求中的变量值,前提是该变量在Nginx中被声明过。如果配置copy_all_vars = true,且vars里有和主请求相同的变量,则会使用vars中变量的值;如果vars里是新变量,就会和主请求的变量一起传递过去。

2.share_all_vars参数,用来共享主请求和子请求的变量,如果在子请求中修改了共享变量的值,主请求的变量值也会被改变。不推荐使用此参数,因为可能会导致很多意外问题的出现。

3.always_forward_body参数,默认值为false,此时,如果不设置body参数,且请求方法是PUT或POST,则主请求的请求体可以传给子请求。如果把always_forward_body设置为 true,且不设置body参数,无论请求方法是什么,主请求的请求体都会传给子请求。

4.ctx参数,指定一个table作为子请求的ngx.ctx表,它可以使主请求和子请求共享请求头的上下文环境。

关于参数vars的使用方式,示例如下: location = /main { set $m 'hello'; set $mm ''; content_by_lua_block { local ngx = require "ngx"; local res = ngx.location.capture( '/test', { method = ngx.HTTP_POST , vars = {mm = 'MMMMM',m = 'hhhh'}} ) ngx.say(res.body) } } location = /test { content_by_lua_block { local ngx = require "ngx"; ngx.print(ngx.var.m .. ngx.var.mm ) } } 执行结果如下:

# curl 'http://testnginx.com/main'

hhhhMMMMM

主请求的变量在子请求中被修改了,并传给了子请求指定的/test:

注意:使用ngx.location.capture发送子请求时,默认会将主请求的请求头全部传入子请求中,这可能会带来一些不必要的麻烦。例如,如果浏览器发送的压缩头Accept-Encoding:gzip被传入子请求中,且子请求是ngx_proxy的标准模块,则请求的结果会被压缩后再返回,导致Lua无法读取子请求返回的数据。因此应将子请求的 proxy_pass_request_headers设置为off,避免把请求头传递给后端服务器。

13.3 并发子请求

有时需要发送多条子请求去获取信息,这时,就要用到并发操作了。


ngx.location.capture_multi

语法:res1, res2, ... = ngx.location.capture_multi({ {uri, options?}, {uri, options?}, ... })

配置环境:rewrite_by_lua*,access_by_lua*,content_by_lua*

含义:与ngx.location.capture相似,但可以支持多个子请求并行访问,并按配置顺序返回数据。返回的数据也是多个结果集。 示例:

server { listen 80; server_name testnginx.com; default_type 'text/plain'; location = /main { set $m 'hello'; set $mm ''; content_by_lua_block { local ngx = require "ngx"; --发送两个子请求,会返回两个结果集 local res1, res2 = ngx.location.capture_multi{ { "/test1?a=1&b=2" }, { "/test2",{ method = ngx.HTTP_POST},body = "test nginx" }, } --返回的body的方式和ngx.location.capture一样 if res1.status == ngx.HTTP_OK then ngx.say(res1.body) end

        if res2.status == ngx.HTTP_OK then
            ngx.say(res2.body)
        end
    }
}
location = /test1 {
     echo 'test1';
}
location = /test2 {
     echo 'test2';
}

}

执行结果如下:

# curl 'http://testnginx.com/main'

test1

test2

主请求需要等到所有的子请求都返回后才会结束子请求的执行,最慢的子请求的执行时间就是整体的消耗时间,所以在实际业务中需要对子请求的超时时间做好限制。

注意:Nginx对子请求有并发数量限制,目前Nginx 1.1以上的版本限制子请求并发数量为200个,老版本是50个。