Skip to content

Latest commit

 

History

History
556 lines (371 loc) · 20.4 KB

ngxlocationcapture.md

File metadata and controls

556 lines (371 loc) · 20.4 KB

ngx.location.capture

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

环境: rewrite_by_lua*, access_by_lua*, content_by_lua*

uri 发起一个同步非阻塞 Nginx 子请求

Nginx 子请求是一种非常强有力的方式,它可以发起非阻塞的内部请求访问目标 location。目标 location 可以是配置文件中其他文件目录,或 任何 其他 nginx C 模块,包括 ngx_proxyngx_fastcgingx_memcngx_postgresngx_drizzle,甚至 ngx_lua 自身等等 。

需要注意的是,子请求只是模拟 HTTP 接口的形式, 没有 额外的 HTTP/TCP 流量,也 没有 IPC (进程间通信) 调用。所有工作在内部高效地在 C 语言级别完成。

子请求与 HTTP 301/302 重定向指令 (通过 ngx.redirect) 完全不同,也与内部重定向 ((通过 ngx.exec) 完全不同。

在发起子请求前,用户程序需要读取完整的 HTTP 请求体 (通过调用 ngx.req.read_body 或设置 lua_need_request_body 指令为 on).

下面是一个简单例子:

 res = ngx.location.capture(uri)

返回一个包含四个元素的 Lua 表 (res.status, res.header, res.body, 和 res.truncated)。

res.status (状态) 保存子请求的响应状态码。

res.header (头) 用一个标准 Lua 表储子请求响应的所有头信息。如果是“多值”响应头,这些值将使用 Lua (数组) 表顺序存储。例如,如果子请求响应头包含下面的行:

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

res.header["Set-Cookie"] 将存储 Lua 表 {"a=3", "foo=bar", "baz=blah"}

res.body (体) 保存子请求的响应体数据,它可能被截断。用户需要检测 res.truncated (截断) 布尔值标记来判断 res.body 是否包含截断的数据。这种数据截断的原因只可能是因为子请求发生了不可恢复的错误,例如远端在发送响应体时过早中断了连接,或子请求在接收远端响应体时超时。

URI 请求串可以与 URI 本身连在一起,例如,

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

因为 Nginx 内核限制,子请求不允许类似 @foo 命名 location。请使用标准 location,并设置 internal 指令,仅服务内部请求。

可选的选项表可以作为第二个参数传入,支持以下选项:

  • method 指定子请求的请求方法, 只接受类似 ngx.HTTP_POST 的常量。
  • body 指定子请求的请求体 (仅接受字符串值)。
  • args 指定子请求的 URI 请求参数 (可以是字符串或者 Lua 表)。
  • ctx 指定一个 Lua 表作为子请求的 ngx.ctx 表,可以是当前请求的 ngx.ctx 表。这种方式可以让父请求和子请求共享完全相同的上下文环境。此选项最早出现在版本 v0.3.1rc25 中。
  • vars 用一个 Lua 表设置子请求中的 Nginx 变量值。此选项最早出现在版本 v0.3.1rc31 中。
  • copy_all_vars 设置是否复制所有当前请求的 Nginx 变量值到子请求中,修改子请求的 nginx 变量值不影响当前 (父) 请求。此选项最早出现在版本 v0.3.1rc31 中。
  • share_all_vars 设置是否共享所有当前 (父) 请求的 Nginx 变量值到子请求中,修改子请求的 nginx 变量值将影响当前 (父) 请求。应用此选项将可能导致非常难以发现的错误,这种副作用是非常有害的。所以只有当完全明确知道自己在做什么时才打开此选项。
  • always_forward_body 当设置为 true 时,如果没有设置 body 选项,当前 (父) 请求的请求体将被转发给子请求。被 ngx.req.read_body()lua_need_request_body on 指令读取的请求体将直接转发给子请求,而不是在创建子请求时再复制整个请求体 (无论此请求体是缓存在内存中还是临时文件中)。默认情况下,此选项值为 false,当 body 选项没有设置,且当子请求的请求方法是 PUTPOST 时,当前 (父) 请求的请求体才被转发。

例如,发送一个 POST 子请求,可以这样做:

 res = ngx.location.capture(
     '/foo/bar',
     { method = ngx.HTTP_POST, body = 'hello, world' }
 )

除了 POST 的其他 HTTP 请求方法请参考 HTTP method constantsmethod 选项默认值是 ngx.HTTP_GET

args 选项可以设置附加的 URI 参数,例如:

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

等同于

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

也就是说,这个方法将根据 URI 规则转义参数键和值,并将它们拼接在一起组成一个完整的请求串。args 选项要求的 Lua 表的格式与 ngx.encode_args 方法中使用的完全相同。

args 选项也可以直接包含 (转义过的) 请求串:

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

这个例子与上个例子的功能相同。

share_all_vars 选项控制是否让当前请求与其子请求共享 nginx 变量。如果设为 true,则当前请求与所有子请求共享所有 nginx 变量作用域。因此,在子请求中修改 nginx 变量将影响当前请求。

使用此选项时需要非常小心,因为共享变量作用域容易导致难以预测的副作用。一般来说,推荐使用 argsvars、或 copy_all_vars 来代替。

此选项默认值是 false

 location /other {
     set $dog "$dog world";
     echo "$uri dog: $dog";
 }

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

         ngx.print(res.body)
         ngx.say(ngx.var.uri, ": ", ngx.var.dog)
     ';
 }

请求 location /lua 将输出

/other dog: hello world
/lua: hello world

copy_all_vars 在子请求被创建时,提供给它一份父请求的 Nginx 变量拷贝。在子请求中对这些变量的修改将不会影响父请求和其他任何共享父请求变量的子请求。

 location /other {
     set $dog "$dog world";
     echo "$uri dog: $dog";
 }

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

         ngx.print(res.body)
         ngx.say(ngx.var.uri, ": ", ngx.var.dog)
     ';
 }

请求 GET /lua 会输出

/other dog: hello world
/lua: hello

请注意,当 share_all_varscopy_all_vars 都被设置为 true 时,share_all_vars 优先。

除了上面提到的两个设置方法外,也可以通过配置 vars 选项来设置子请求的变量值。在共享或复制父请求的 nginx 变量值后,子请求被设置这些值。和把特定参数通过 URL 参数编码传入子请求,再在 Nginx 配置文件中解码相比,这个方法效率更高。

 location /other {
     content_by_lua '
         ngx.say("dog = ", ngx.var.dog)
         ngx.say("cat = ", ngx.var.cat)
     ';
 }

 location /lua {
     set $dog '';
     set $cat '';
     content_by_lua '
         res = ngx.location.capture("/other",
             { vars = { dog = "hello", cat = 32 }});

         ngx.print(res.body)
     ';
 }

访问 /lua 将输出

dog = hello
cat = 32

选项 ctx 被用于给子请求设定一个自定义的 Lua 表作为 ngx.ctx 表。

 location /sub {
     content_by_lua '
         ngx.ctx.foo = "bar";
     ';
 }
 location /lua {
     content_by_lua '
         local ctx = {}
         res = ngx.location.capture("/sub", { ctx = ctx })

         ngx.say(ctx.foo);
         ngx.say(ngx.ctx.foo);
     ';
 }

请求 GET /lua 输出

bar
nil

也可以通过 ctx 选项设置当前 (父) 请求与子请求共享同一个 ngx.ctx 表。

 location /sub {
     content_by_lua '
         ngx.ctx.foo = "bar";
     ';
 }
 location /lua {
     content_by_lua '
         res = ngx.location.capture("/sub", { ctx = ngx.ctx })
         ngx.say(ngx.ctx.foo);
     ';
 }

请求 GET /lua 输出

bar

请注意,通过 ngx.location.capture 创建的子请求默认继承当前请求的所有请求头信息,这有可能导致子请求响应中不可预测的副作用。例如,当使用标准的 ngx_proxy 模块服务子请求时,如果主请求头中包含 "Accept-Encoding: gzip",可能导致子请求返回 Lua 代码无法正确处理的 gzip 压缩过的结果。通过设置 proxy_pass_request_headersoff ,在子请求 location 中忽略原始请求头。

当没有设置 body 选项,且 always_forward_body 选项为 false (默认值) 时,POSTPUT 子请求将继承父请求的请求体 (如果有的话)。

Nginx 代码中有一个硬编码的数字,来控制每个主请求最多可以有多少个并发的子请求。在旧版 Nginx 中,这个数字是 50,自 Nginx 1.1.x 以后,并发子请求数被提高到了 200。当超过这个限制时,error.log 文件中将出现下面的信息:

[error] 13983#0: *1 subrequests cycle while processing "/uri"

如果需要修改这个限制,可以手工在 Nginx 源代码树的 nginx/src/http/ngx_http_request.h 文件中修改 NGX_HTTP_MAX_SUBREQUESTS 宏定义。

请参考 subrequest directives of other modules 了解目标 location 的配置限制。

English Source

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

context: rewrite_by_lua*, access_by_lua*, content_by_lua*

Issue a synchronous but still non-blocking Nginx Subrequest using uri.

Nginx's subrequests provide a powerful way to make non-blocking internal requests to other locations configured with disk file directory or any other nginx C modules like ngx_proxy, ngx_fastcgi, ngx_memc, ngx_postgres, ngx_drizzle, and even ngx_lua itself and etc etc etc.

Also note that subrequests just mimic the HTTP interface but there is no extra HTTP/TCP traffic nor IPC involved. Everything works internally, efficiently, on the C level.

Subrequests are completely different from HTTP 301/302 redirection (via ngx.redirect) and internal redirection (via ngx.exec).

You should always read the request body (by either calling ngx.req.read_body or configuring lua_need_request_body on) before initiating a subrequest.

Here is a basic example:

 res = ngx.location.capture(uri)

Returns a Lua table with three slots (res.status, res.header, res.body, and res.truncated).

res.status holds the response status code for the subrequest response.

res.header holds all the response headers of the subrequest and it is a normal Lua table. For multi-value response headers, the value is a Lua (array) table that holds all the values in the order that they appear. For instance, if the subrequest response headers contain the following lines:

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

Then res.header["Set-Cookie"] will be evaluated to the table value {"a=3", "foo=bar", "baz=blah"}.

res.body holds the subrequest's response body data, which might be truncated. You always need to check the res.truncated boolean flag to see if res.body contains truncated data. The data truncation here can only be caused by those unrecoverable errors in your subrequests like the cases that the remote end aborts the connection prematurely in the middle of the response body data stream or a read timeout happens when your subrequest is receiving the response body data from the remote.

URI query strings can be concatenated to URI itself, for instance,

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

Named locations like @foo are not allowed due to a limitation in the nginx core. Use normal locations combined with the internal directive to prepare internal-only locations.

An optional option table can be fed as the second argument, which supports the options:

  • method specify the subrequest's request method, which only accepts constants like ngx.HTTP_POST.
  • body specify the subrequest's request body (string value only).
  • args specify the subrequest's URI query arguments (both string value and Lua tables are accepted)
  • ctx specify a Lua table to be the ngx.ctx table for the subrequest. It can be the current request's ngx.ctx table, which effectively makes the parent and its subrequest to share exactly the same context table. This option was first introduced in the v0.3.1rc25 release.
  • vars take a Lua table which holds the values to set the specified Nginx variables in the subrequest as this option's value. This option was first introduced in the v0.3.1rc31 release.
  • copy_all_vars specify whether to copy over all the Nginx variable values of the current request to the subrequest in question. modifications of the nginx variables in the subrequest will not affect the current (parent) request. This option was first introduced in the v0.3.1rc31 release.
  • share_all_vars specify whether to share all the Nginx variables of the subrequest with the current (parent) request. modifications of the Nginx variables in the subrequest will affect the current (parent) request. Enabling this option may lead to hard-to-debug issues due to bad side-effects and is considered bad and harmful. Only enable this option when you completely know what you are doing.
  • always_forward_body when set to true, the current (parent) request's request body will always be forwarded to the subrequest being created if the body option is not specified. The request body read by either ngx.req.read_body() or lua_need_request_body on will be directly forwarded to the subrequest without copying the whole request body data when creating the subrequest (no matter the request body data is buffered in memory buffers or temporary files). By default, this option is false and when the body option is not specified, the request body of the current (parent) request is only forwarded when the subrequest takes the PUT or POST request method.

Issuing a POST subrequest, for example, can be done as follows

 res = ngx.location.capture(
     '/foo/bar',
     { method = ngx.HTTP_POST, body = 'hello, world' }
 )

See HTTP method constants methods other than POST. The method option is ngx.HTTP_GET by default.

The args option can specify extra URI arguments, for instance,

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

is equivalent to

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

that is, this method will escape argument keys and values according to URI rules and concatenate them together into a complete query string. The format for the Lua table passed as the args argument is identical to the format used in the ngx.encode_args method.

The args option can also take plain query strings:

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

This is functionally identical to the previous examples.

The share_all_vars option controls whether to share nginx variables among the current request and its subrequests. If this option is set to true, then the current request and associated subrequests will share the same Nginx variable scope. Hence, changes to Nginx variables made by a subrequest will affect the current request.

Care should be taken in using this option as variable scope sharing can have unexpected side effects. The args, vars, or copy_all_vars options are generally preferable instead.

This option is set to false by default

 location /other {
     set $dog "$dog world";
     echo "$uri dog: $dog";
 }

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

         ngx.print(res.body)
         ngx.say(ngx.var.uri, ": ", ngx.var.dog)
     ';
 }

Accessing location /lua gives

/other dog: hello world
/lua: hello world

The copy_all_vars option provides a copy of the parent request's Nginx variables to subrequests when such subrequests are issued. Changes made to these variables by such subrequests will not affect the parent request or any other subrequests sharing the parent request's variables.

 location /other {
     set $dog "$dog world";
     echo "$uri dog: $dog";
 }

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

         ngx.print(res.body)
         ngx.say(ngx.var.uri, ": ", ngx.var.dog)
     ';
 }

Request GET /lua will give the output

/other dog: hello world
/lua: hello

Note that if both share_all_vars and copy_all_vars are set to true, then share_all_vars takes precedence.

In addition to the two settings above, it is possible to specify values for variables in the subrequest using the vars option. These variables are set after the sharing or copying of variables has been evaluated, and provides a more efficient method of passing specific values to a subrequest over encoding them as URL arguments and unescaping them in the Nginx config file.

 location /other {
     content_by_lua '
         ngx.say("dog = ", ngx.var.dog)
         ngx.say("cat = ", ngx.var.cat)
     ';
 }

 location /lua {
     set $dog '';
     set $cat '';
     content_by_lua '
         res = ngx.location.capture("/other",
             { vars = { dog = "hello", cat = 32 }});

         ngx.print(res.body)
     ';
 }

Accessing /lua will yield the output

dog = hello
cat = 32

The ctx option can be used to specify a custom Lua table to serve as the ngx.ctx table for the subrequest.

 location /sub {
     content_by_lua '
         ngx.ctx.foo = "bar";
     ';
 }
 location /lua {
     content_by_lua '
         local ctx = {}
         res = ngx.location.capture("/sub", { ctx = ctx })

         ngx.say(ctx.foo);
         ngx.say(ngx.ctx.foo);
     ';
 }

Then request GET /lua gives

bar
nil

It is also possible to use this ctx option to share the same ngx.ctx table between the current (parent) request and the subrequest:

 location /sub {
     content_by_lua '
         ngx.ctx.foo = "bar";
     ';
 }
 location /lua {
     content_by_lua '
         res = ngx.location.capture("/sub", { ctx = ngx.ctx })
         ngx.say(ngx.ctx.foo);
     ';
 }

Request GET /lua yields the output

bar

Note that subrequests issued by ngx.location.capture inherit all the request headers of the current request by default and that this may have unexpected side effects on the subrequest responses. For example, when using the standard ngx_proxy module to serve subrequests, an "Accept-Encoding: gzip" header in the main request may result in gzipped responses that cannot be handled properly in Lua code. Original request headers should be ignored by setting proxy_pass_request_headers to off in subrequest locations.

When the body option is not specified and the always_forward_body option is false (the default value), the POST and PUT subrequests will inherit the request bodies of the parent request (if any).

There is a hard-coded upper limit on the number of concurrent subrequests possible for every main request. In older versions of Nginx, the limit was 50 concurrent subrequests and in more recent versions, Nginx 1.1.x onwards, this was increased to 200 concurrent subrequests. When this limit is exceeded, the following error message is added to the error.log file:

[error] 13983#0: *1 subrequests cycle while processing "/uri"

The limit can be manually modified if required by editing the definition of the NGX_HTTP_MAX_SUBREQUESTS macro in the nginx/src/http/ngx_http_request.h file in the Nginx source tree.

Please also refer to restrictions on capturing locations configured by subrequest directives of other modules.

返回目录