echo
是个很有意思,也相当有用模块。能把编程语言的特性引入 Nginx 配置文件, 作者 的想像力很是丰富!
本篇来先来分析一下 echo
模块的总体框架和大致流程。下一篇:细节篇 分另解析 一下此模块用到的各种 Nginx 知识点。
cmd
,下面将 cmd
称为 命令 。同时,配置指令对应的函数对请 求的处理,我们称为 在请求上执行命令 。 关注点: 模块文档 已经列举的 - 这 个模块对 Nginx 模块开发者可能会用到的技术做了示范。这些技术包括:
output_chain
, flush and its friends. ngx_http_echo_module
- 作为 "dual-role" module, 提供了 location handler
( ngx_http_echo_handler
) 和 output filter
( ngx_http_echo_header_filter
, ngx_http_echo_body_filter
)。
如果 ngx_http_echo_arg_template_t
命名为 ngx_http_echo_arg_value_t
要好理 解一点。Nginx 核心代码里好多 ngx_http_complex_value_t
,很直抒胸意啊。每一个 cmd
实际上就是定义了一个操作 ( opcode
) 和一组操作数 ( args
)。
typedef struct { ngx_str_t raw_value; ngx_array_t *lengths; ngx_array_t *values; } ngx_http_echo_arg_template_t; typedef struct { ngx_http_echo_opcode_t opcode; ngx_array_t *args; /* of ngx_http_echo_arg_templte_t */ } ngx_http_echo_cmd_t;
ngx_http_echo_opcode_t
定义了 echo
模块支持的 opcode
。 echo
模块将 echo_XXX
配置指令都解析为一个相应的命令,这些 opcode
就是各个命令的标识符。
ngx_http_echo_cmd_category_t
将命令分成两类:第一类 echo_handler_cmd
由 location handler
调用处理;第二类 echo_filter_cmd
由 output filter
调用处 理。
配置解析过程中处理包含变量的参数的标准模式 (使用 ngx_http_script_compile
):
n = ngx_http_script_variables_count(&arg->raw_value); if (n > 0) { ngx_memzero(&sc, sizeof(ngx_http_script_compile_t)); sc.cf = cf; sc.source = &arg->raw_value; sc.lengths = &arg->lengths; sc.values = &arg->values; sc.variables = n; sc.complete_lengths = 1; /* 是否添加结束标识 (uintptr_t) NULL */ sc.complete_values = 1; /* 同上 */ if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_CONF_ERROR; } } --------------------------------------------------------- ngx_str_t *arg, *raw; ... raw = &value[i].raw_value; if (value[i].lengths == NULL) { *arg = *raw; } else { /* arg 存储最终的参数取值 */ if (ngx_http_script_run(r, arg, value[i].lengths->elts, 0, value[i].values->elts) == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } }
ngx_http_echo_helper
- 它的基本功能就是把当前作用域中的 echo_XXX
系列配置 指令解析为 ngx_http_echo_cmd_t
后,按顺序追加到 ngx_http_echo_loc_conf_t
的 handler_cmds
, before_body_cmds
, after_body_cmds
等 ngx_http_echo_cmd_t
类型的数组中。配置文件中 echo_XXX
配置指令的顺序就是这么保证的。另外,这个函数 会被除了 echo_status
之外的所有配置指令解析函数调用。配置解析完成后,构造出的 几个结构体的关系图大致描述如下:
ngx_http_echo_loc_conf_t + ngx_array_t *handler_cmds --> [ ngx_http_echo_cmd_t, ... ] | + ngx_http_echo_opcode_t opcode | + ngx_array_t *args | | | [ ngx_http_echo_arg_template_t, ... ] <-- | + raw_value/lengths/values | + ngx_array_t *before_body_cmds | ... (same as handler_cmds) ... + ngx_array_t *after_body_cmds | ... (same as handler_cmds) ...
ngx_http_echo_ctx_t
- 存储和请求实例相关的 echo
模块状态 (命令序列的执行进 度, echo_foreach_split
的循环变量 等),由函数 ngx_http_echo_create_ctx
创建。
模块的配置解析和初始化
配置指令解析过程基本上和 ngx_http_echo_helper
条描述的一致。在 http {}
段解析完成后,Nginx 开始对各模块配置项按作用域进行 merge
。 echo
模块的在 子作用域中的配置要么直接继承父作用域 (子作用域没有出现同类 echo_XXX
配置 指令) 或者忽略父作用域的配置项 (子作用域已经有了某类 echo_XXX
配置指令)。
if (conf->handler_cmds == NULL) { conf->handler_cmds = prev->handler_cmds; } if (conf->before_body_cmds == NULL) { conf->before_body_cmds = prev->before_body_cmds; } if (conf->after_body_cmds == NULL) { conf->after_body_cmds = prev->after_body_cmds; }
seen_leading_output
- 如果 echo_subrequest
, echo_subrequest_async
, echo_location
, echo_location_async
是当前作用域的第一个 echo
配置指令, 则将这四个配置指令对应的 cmd
加入 handler_cmds
之前先将其作为 echo_opcode_echo_sync
类型 cmd
添加一次。如果当前作用域已经出现过了其它 echo_XXX
配置指令,则直接将这四个配置指令对应的 cmd
按照实际类型添加。
if (!elcf->seen_leading_output) { elcf->seen_leading_output = 1; ret = ngx_http_echo_helper(echo_opcode_echo_sync, echo_handler_cmd, cf, cmd, conf); ... } return ngx_http_echo_helper(echo_opcode_echo_subrequest, echo_handler_cmd, cf, cmd, conf);
配置指令解析完成后,在 post config
阶段,Nginx 调用 ngx_http_echo_post_config
函数根据刚刚解析完的配置向 Nginx 注册新的 output filter
和 模块变量。
rc = ngx_http_echo_filter_init(cf); ... rc = ngx_http_echo_echo_init(cf); ... return ngx_http_echo_add_variables(cf);
从 echo
模块的介绍信息,我们得知它是 dual-role
的模块:除了提供 output filter
外,它还提供了 content handler
。 content handler
在 ngx_http_echo_helper
函数中进行注册,任何 echo_handler_cmd
类型的 cmd
都可以触发注册逻辑:
if (*cmds_ptr == NULL) { ... if (cat == echo_handler_cmd) { ... clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); clcf->handler = ngx_http_echo_handler; } else { emcf->require_filter = 1; } }
配置解析和模块的初始化主要流程到这里就完成了。
echo
模块实现了一种语法简单的 命令式语言 。 一系列 echo_XXX
命令按照在 Nginx 配置文件中出现的次序被依次执行。同时,这种 "语言" 目前还支持 foreach
语法,可以根据循环条件多次执行这个语法块中的其它命 令 (暂不支持循环体嵌套)。
当请求 "进入" 配置了 echo
的 content handler directives 的 location {}
后, 在 CONTENT
PHASE,Nginx 会将它交由 ngx_http_echo_handler
函数完成请求的响应 生成。
ngx_http_echo_handler
随后调用 ngx_http_echo_run_cmds
在当前请求上依次执行 location
中配置的 echo
命令 (调用命令对应的处理函数)。
下面是 ngx_http_echo_run_cmds
的主要逻辑:
ngx_int_t ngx_http_echo_run_cmds(ngx_http_request_t *r) { ... elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module); cmds = elcf->handler_cmds; ... ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); ... cmd_elts = cmds->elts; for ( ; ctx->next_handler_cmd < cmds->nelts; ctx->next_handler_cmd++) { cmd = &cmd_elts[ctx->next_handler_cmd]; if (cmd->args) { /* computed_args */ /* opts */ } switch (cmd->opcode) { case echo_opcode_echo_sync: case echo_opcode_echo: case echo_opcode_echo_request_body: case echo_opcode_echo_location_async: case echo_opcode_echo_location: case echo_opcode_echo_subrequest_async: case echo_opcode_echo_subrequest: case echo_opcode_echo_sleep: case echo_opcode_echo_flush: case echo_opcode_echo_blocking_sleep: case echo_opcode_echo_reset_timer: case echo_opcode_echo_duplicate: case echo_opcode_echo_read_request_body: case echo_opcode_echo_foreach_split: case echo_opcode_echo_end: case echo_opcode_echo_exec: default: } ... } rc = ngx_http_echo_send_chain_link(r, ctx, NULL /* indicate LAST */); ... return NGX_OK; }
对以上代码的补充说明:
命令处理函数在运行过程中可能需要等待新事件触发 ( read-request-body
等) 后被 再次调用。所以, ngx_http_echo_run_cmds
需要支持被多次调用。 echo
模块使用 ngx_http_echo_ctx_t::next_handler_cmd
指向下一次 ngx_http_echo_run_cmds
函数 调用需要执行的 cmd
(存储 ngx_http_echo_loc_conf_t::handler_cmds
数组索引)。
echo_foreach_split
和 echo_end
组成的循环结构就是靠操纵 next_handler_cmd
值实现的。
示例
echo_foreach_split ',' 'a,b,c'; echo $echo_it; echo_end;
echo
模块使用 ngx_http_echo_foreach_ctx_t
存储循环体的当前执行状态。
typedef struct { ngx_array_t *choices; /* 存储 foreach 参数解析后,每次循环可见的 $echo_it 值 */ ngx_uint_t next_choice; /* 下一个 choice,choices 数组索引 */ ngx_uint_t cmd_index; /* 备份 next_handler_cmd,指向 `foreach` 命令索引 */ } ngx_http_echo_foreach_ctx_t;
ngx_http_echo_run_cmds
碰到 foreach
命令 后,构造 foreach ctx
,解析 循环条件,并存储于 choices
中;使用 cmd_index
保存 foreach
命令的索引。
当前命令为 echo_end
的话,表明一次循环执行完成。 echo_end
的处理函数 ngx_http_echo_exec_echo_end
检查循环条件,决定进行下一次循环还是退出循环。 当可以进行下一次循环 ( next_choice >= choices->nelts
),时,重置 next_handler_cmd
为 cmd_index
。 next_handler_cmd
会被 ngx_http_echo_run_cmds
加 1,这样下一条要执行的命令即为 foreach
命令的 下一条,即循环体中定义的第一条指令。当循环执行完毕后, echo_end
不再修改 next_handler_cmd
,这样,控制交给 ngx_http_echo_run_cmds
后, for
循环 依旧将其加 1,这样它就指向了 echo_end
后面紧跟着的一条指令。
循环块执行结束后, echo_end
命令处理函数把 ngx_http_echo_ctx_t::ctx->foreach
置 NULL
,表明循环块的结束。这时, $echo_it
变量在循环块是无效的值。
命令 echo_opcode_echo_sync
在模块解析到 echo_location/echo_location_async
和 echo_subrequest/echo_subrequest_async
指令时添加到它们对应的命令前。它的作 用 - 根据作者描述 - 是:
Forgot to mention that the `sync` bufs generated in `ngx_echo` are just for working with output filter modules like `ngx_xss`: https://github.com/agentzh/xss-nginx-module
上面提到过,有些命令需要多次事件触发并调用 ngx_http_echo_run_cmds
才能完成处 理 (后续的调用由写事件处理函数 ngx_http_echo_wev_handler
调用 ngx_http_echo_run_cmds
完成)。
我们按此行为将 echo
提供的 content handler 命令分为两类:
一次 ngx_http_echo_run_cmds
调用就能完成的 echo
命令 ( cmd
):
echo echo_reqeust_body echo_sleep echo_flush echo_blocking_sleep echo_reset_timer echo_duplicate echo_exec
(可能) 多次 ngx_http_echo_run_cmds
才能完成的 echo
命令 ( cmd
)。
echo_location # 完整读取请求包体、子请求完成后 `run_cmds` 被触发 echo_location_async # 完整读取请求包体 echo_subrequest # 完整读取请求包体、子请求完成后 `run_cmds` 被触发 echo_subrequest_async # 完整读取请求包体 echo_read_request_body # 完整读取请求包体
另外,相对于配置上下文来说,除了 echo_location_async/echo_subrequest_async
是 异步的外,其它命令都是同步执行的,也就是说,当前命令执行完毕之前不会执行下一个 命令。
这些同步执行的命令,要么是在 run_cmds
中一次性完成了,要么中断当前 run_cmds
的执行 ( return
),再由事件回调函数在合适的时机调用 run_cmds
完成上次未完成的 命令。
switch (cmd->opcode) { ... case echo_opcode_echo: rc = ngx_http_echo_exec_echo(r, ctx, computed_args, 0, /* in filter */, opts); break; ... case echo_opcode_echo_location: ... return ngx_http_echo_exec_echo_location(r, ctx, computed_args); case echo_opcode_echo_location_async: rc = ngx_http_echo_exec_echo_location_async(r, ctx, computed_args); break; ... }
filter
命令存储于 ngx_http_echo_loc_conf_t::before_body_cmds
和 ngx_http_echo_loc_conf_t::after_body_cmds
数组中,存储结构和 content handler
命令一致。这些命令被 ngx_http_echo_header_filter
和 ngx_http_echo_body_filter
使用。
filter
命令在当前请求的执行状态依然保存在 ngx_http_echo_ctx_t
结构体中:
typedef struct { ... ngx_uint_t next_before_body_cmd; /* 下一个被调用的命令索引 */ ngx_uint_t next_after_body_cmd; /* 下一个被调用的命令索引 */ ... unsigned before_body_sent:1; /* before body 的命令输出是否已 经发送 */ unsigned skip_filter:1; /* 是否调用 `echo body filter` 处 理响应包体 */ ... } ngx_http_echo_ctx_t;
echo_before_body
和 echo_after_body
用来在请求响应的首部和尾部添加命令的输 出结果,它们在响应的 body filter chain
中被调用 ( ngx_http_echo_body_filter
):
static ngx_int_t ngx_http_echo_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { ... if (!ctx->before_body_sent) { ctx->before_body_sent = 1; if (conf->before_body_cmds != NULL) { rc = ngx_http_echo_exec_filter_cmds(r, ctx, conf->before_body_cmds, &ctx->next_before_body_cmd); ... } } if (conf->after_body_cmds == NULL) { ctx->skip_filter = 1; return ngx_http_echo_next_body_filter(r, in); } ... /* output original body */ rc = ngx_http_echo_exec_filter_cmds(r, ctx, conf->after_body_cmds, &ctx->next_after_body_cmd); ... b = ngx_calloc_buf(r->pool); ... return ngx_http_echo_next_body_filter(r, cl); }
对上面代码的补充说明:
ngx_http_echo_exec_filter_cmds
和 ngx_http_echo_run_cmds
的逻辑相似, after body
和 before body
命令都使用 echo
命令的处理函数 ngx_http_echo_exec_echo
对命令参数进行求值。 到这里为止, echo
模块的大致工作流程算是描述完毕了。接下来,我们再来分析一下 echo
模块对变量支撑是如何实现的。
echo
模块目前 (v0.50) 支持的变量有:
$echo_it # echo_foreach_split 结构的循环变量 $echo_timer_elapsed # 请求己用时,可被 echo_reset_timer 重置 $echo_request_body # 己接收的请求包体,可能为空 (还未接收) # 或部分包体 (只接收了部分) $echo_request_method # 当前请求的 HTTP method (r->method_name) $echo_client_request_method # 当前主请求的 HTTP mehod (r->main->method_name) $echo_client_request_headers # 当前主请求的请求包头 $echo_cacheable_request_uri # 当前请求 parsed URI (r->uri),被缓存的值 $echo_request_uri # 当前请求 parsed URI (r->uri),未被缓存的值 $echo_incr # 和当前主请求绑定的计数器,每次使用会自增1 $echo_response_status # 当前请求的响应码 (r->headers_out->status)
echo
模块提供的变量在 ngx_http_echo_var.c
中定义 (只列举部分):
static ngx_http_variable_t ngx_http_echo_variables[] = { { ngx_string("echo_timer_elapsed"), NULL, ngx_http_echo_timer_elapsed_variale, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, ... { ngx_string("echo_request_uri"), NULL, ngx_http_echo_request_uri_variable, 0, 0, 0 }, ... { ngx_string("echo_request_body"), NULL, ngx_http_echo_request_body_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, ... { ngx_string("echo_it"), NULL, ngx_http_echo_it_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, ... { ngx_string("echo_response_status"), NULL, ngx_http_echo_response_status_variable, 0, NGX_HTTP_VAR_NOCACHEABLE, 0 }, { ngx_null_string, NULL, NULL, 0, 0, 0 } };
echo
模块初始化的 post config
阶段, ngx_http_echo_post_config
函数调用 ngx_http_echo_add_variables
将上面定义的变量注册到 Nginx 里。
ngx_int_t ngx_http_echo_add_variables(ngx_conf_t *cf) { ngx_http_variable_t *var, *v; for (v = ngx_http_echo_variables; v->name.len; v++) { var = ngx_http_add_variable(cf, &v->name, v->flags); ... var->get_handler = v->get_handler; var->data = v->data; } return NGX_OK; }
对上述代码的补充说明:
调用 ngx_http_add_variable
将 ngx_http_variable_t
类型的变量定义添加到 Nginx 的变量定义存储结构 cmcf->variables_keys
中。
Nginx 各个模块和 echo
模块定义的变量并不一定都会被用到 (出现在配置文件里), Nginx 使用 cmcf->variables
跟踪被引用到的变量定义,并给它们分配一个唯一的索引 值 index
。
配置指令的参数中出现变量的话,在配置指令解析时会调用 ngx_http_get_variable_index
获取此变量的索引。这个索引在变量求值时作为 ngx_http_get_indexed_variable
函数 的参数,查找此变量的属性 ( flags
) 和调用变量的 get_handler
完成实际的取值操 作。
综上, ngx_http_add_variable
, ngx_http_get_variable_index
, ngx_http_get_indexed_variables
。
Nginx 的变量求值过程在配置变量 一文中进行过详细分析,此处不在赘述。
变量的值都由变量的 get_handler
生成,接下来,分析几个 echo
变量的 get_handler
实现。
echo_timer_elapsed
- 其 get_handler
是 ngx_http_echo_timer_elapsed_variale
ngx_int_t ngx_http_echo_timer_elapsed_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ... ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); if (ctx->timer_begin.sec == 0) { ctx->timer_begin.sec = r->start_sec; ctx->timer_begin.msec = (ngx_msec_t) r->start_msec; } ngx_time_update(); ... p = ngx_palloc(r->pool, size); ... v->len = ngx_snprintf(p, size, "%T.%03M", ms / 1000, ms % 1000) - p; v->data = p; v->valid = 1; v->no_cacheable = 1; v->not_found = 0; return NGX_OK; }
echo_request_uri
- 其 get_handler
是 ngx_http_echo_request_uri_variable
ngx_int_t ngx_http_echo_request_uri_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { if (r->uri.len) { v->data = r->uri.data; r->len = r->uri.len; v->valid = 1; r->no_cacheable = 1; v->not_found = 0; } else { v->not_found = 1; } return NGX_OK; }
echo_it
- 其 get_handler
是 ngx_http_echo_it_variable
ngx_int_t ngx_http_echo_it_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { ... ctx = ngx_http_get_module_ctx(r, ngx_http_echo_module); if (ctx && ctx->foreach != NULL) { choices = ctx->foreach->choices; i = ctx->foreach->next_choice; if (i < choices->nelts) { choice_elts = choices->elts; choice = &choice_elts[i]; v->data = choice->data; v->len = choice->len; v->valid = 1; v->no_cacheable = 1; v->not_found = 0; return NGX_OK; } } v->not_found = 1; return NGX_OK; }
从上面三个变量的 get_handler
例子可以看到 ngx_http_variable_value_t
代表变 量值及变量值的各种属性。 get_handler
需要为变量值分配内存。
再回顾一下开始提到的,我们希望从这个模块学到的知识:
* Issue parallel subrequests directly from content handler. * Issue chained subrequests directly from content handler, by passing continuation along the subrequest chain. * Issue subrequests with all HTTP 1.1 methods and even an optional faked HTTP request body. * Interact with the Nginx event model directly from content handler using custom events and timers, and resume the content handler back if necessary. * Dual-role module that can (lazily) server as a content handler or an output filter or both. * Nginx config file variable creation and interpolation. * Streaming output control using `output_chain`, flush and its friends. * Read client request body from the content handler, and returns back (asynchronously) to the content handler after completion. * Use Perl-based declarative test suite to driven the development of Nginx C modules.
本篇分析了 echo
模块提供的主要功能和整体工作流程;分析了 "Dual-role module" 是如何实现的;自定义模块提供变量支持要做的工作;和如何在配置指令中支持变量参数。
接下来,将对模块实现的 "异步子请求"、"同步子请求" 和 "响应的输出控制" 等功能点 进行详细的分析。
echo_xxx
配置指令为什么不能用于 server {}
或者 http {}
. A. 参见 ngx-module-devel.md#phases
。
Q. echo_subrequest
和 echo_location
的区别。
A. echo_subrequest
is very much like a generalized version of the echo_location
directive. 它比 echo_location
提供了更多选项。
Q. 模块自定义变量的处理流程。
A. 查看 Variables 一节
Q. ngx_http_clear_content_length
和 ngx_http_clear_accept_ranges
起到了什 么作用,都有哪些场合需要使用?
A. 这两个函数用于确定无法获知响应的实际长度时,告诉其它 filter 对响应做相应的 处理。具体分析如下:
ngx_http_clear_content_length
清空响应包头中 Content-Length
相关的字段:
r->headers_out.content_length_n = -1; if (r->headers_out.content_length) { r->headers_out.content_length->hash = 0; r->headers_out.content_length = NULL; }
ngx_http_clear_accept_ranges
清空响应包头中和 Accept-Ranges
相关的字段:
r->allow_ranges = 0; if (r->headers_out.accept_ranges) { r->headers_out.accept_ranges->hash = 0; r->headers_out.accept_ranges = NULL; }
ngx_http_range_filter_module
模块由 accept_ranges
向响应包头填充 Accept-Ranges
字段 (告知客户端是否可以请求资源的某个范围内的数据)。动态生 成的资源,是不支持通过范围方式请求的。
content_length_n
标识 Nginx 明确知道请求资源的长度。动态生成的资源,无法 获知准确长度的情况下,需要使用 传输编码 对整个报文进行编码,即 Transfer-Encoding
。目前最新的 HTTP 规范只定义了一种传输编码, chunked 。 ngx_http_chunked_filter_module
负责根据 content_length_n
的值判断是否 添加 Transfer-Encoding
包头 (设置 r->chunked
,由 header_filter
添加)。
Q. url parse 具体是指什么样的转换操作?Nginx 里由哪些函数完成?对应的其它语言 呢?
A. 几个线索。
location
direction in manul
The matching is performed against a normalized URI, after decoding the text encoded in the “%XX” form, resolving references to relative path components “.” and “..”, and possible compression of two or more adjacent slashes into a single slash.
$uri
- current URI in request, normalized
$request_uri
- full original request URI (with arguments)
ngx_http_parse_request_line
case '.': r->complex_uri = 1; ... case '/': r->complex_uri = 1; ... case '#': r->complex_uri = 1; ... case '%': r->quoted_uri = 1;
ngx_http_process_request_line
if (r->args_start) { r->uri.len = r->args_start - 1 - r->uri_start; } else { r->uri.len = r->uri_end - r->uri_start; }
if (r->complex_uri || r->quoted_uri) {
r->uri.data = ngx_pnalloc(r->pool, r->uri.len + 1); ... ngx_http_parse_complex_uri(r, cscf->merge_slashes); ...
} else { r->uri.data = r->uri_start; }
r->unparsed_uri.len = r->uri_end - r->uri_start; r->unparsed_uri.data = r->uri_start;
ngx_http_parse_complex_uri
urllib.quote(string[, safe])
- replace special characters in string
using the %xx
escape. [a-zA-Z0-9_.-]
are never quoted. By default, this function is intended for quoting the path section of the URL. The optional safe
parameter specifies additional characters that should not be quoted, its default value is /
.
Q. echo_read_request_body; echo_request_body;
后,接下来的 echo_XXX
配置 指令都不再生效了?
echo "header"; echo_read_request_body; echo_request_body; echo "trailer"; # missing # curl -i http://127.0.0.1/echo -d "body"
A. from the author:
Yes, according to the current implementation of echo_request_body, the original request body bufs are used directly, which contains a "last buf", terminating the response body stream. A better way is to copy all the bufs (both ngx_chain_t and ngx_buf_t) from r->request_body->bufs (but not the data pointed to by the bufs) and to clear the last_buf flag in our copy.
抓了一下包,"trailer" 实际上是收到了。但是 last_buf
造成 chunk 格式错误
POST /echo HTTP/1.1 User-Agent: curl/7.32.0 Host: 127.0.0.1 Accept: */* Content-Length: 11 Content-Type: application/x-www-form-urlencoded I am a bodyHTTP/1.1 200 OK Server: nginx/1.4.3 Date: Sat, 04 Jan 2014 18:42:10 GMT Content-Type: text/plain Transfer-Encoding: chunked Connection: keep-alive 7 header b I am a body 0 8 trailer 0
ngx_http_echo_exec_echo_request_body
调用 ngx_http_read_request_body
了么? r->request_body
是在什么时候存入请求包体的? A. 如果没有使用诸如 echo_location[_async]
, echo_subrequest[_async]
之类的 指令 (它们都会先读完请求包体再往下执行自己的逻辑) 的话, echo
模块是不会读请求 包体数据的。 echo_request_body
的含义是:Outputs the contents of the request body previous read. It is a "no-op" if no request body has been read yet. 那么 echo_request_body
指令完全可能得到空字符串。
ngx_http_echo_exec_echo_request_body
并不调用 ngx_http_read_request_body
。
r->request_body
在调用了 echo_location[_async]
, echo_subrequest[_async]
或者 echo_read_request_body
命令后,才会读取并存储请求包体到其中。
echo_request_body
- outputs the contents of the request body previous read. This directive will show the whole request body even if some parts or all parts of it are saved in temporary files on the disk.
echo_read_request_body
- explicitly reads request body so that the $request_body
variable will always have non-empty values.
$echo_request_body
- evaluates to the current request's request body previously read if no part of the body has been saved to a temporary file.
Q. 文件存储的 request_body
没有设置 last_buf
字段为 1。
A. From agentzh: The last_buf
flag in r->request_body->bufs
is not significant for Nginx core's own usage. So it is an inconsistency for in-file request bodies, but it does not matter for the Nginx core. It did matter for our echo_request_body in ngx_echo but gladly we've fixed this anyway.
Q. 什么时候使用 subrequest
,什么时候用 internal redirect
?
A. 它们俩个的关系大致可以类比为 Bash 的 subshell 和 exec
,什么时候怎么使用看 模块需要吧。
Q. echo_XXX
未对参数有效性进行检查,而在运行时才检查,这样设计的作用是什么?
27 location /echo { 28 echo_foreach_split '-a-' 'cat-a-dog-a-mouse'; 29 echo /echo_exec; 30 echo_end; 31 }
需要在 '-a-' 前添加 --,否则报错:echo_foreach should take at least two arguments. (if your delimiter starts with "-"
27 location /echo { 28 echo_foreach_split -- '-a-' 'cat-a-dog-a-mouse'; 29 echo_exec /echo_exec; 30 echo_end; 31 }
只会被执行一次
cmd->args 会不会被重复 eval'ed,会!并且每一个 cmd 执行前都会被 eval,不管有 没有参数。
if (cmd->args) { } -> if (cmd->args.nelts) { }
在没有使用任何 echo 指令的情况下使用 echo 模块提供的变量时,会有 coredump 发生:
30 location /echo { 31 if ($echo_timer_elapsed) { 32 } 33 } Core was generated by `sbin/nginx'. Program terminated with signal 11, Segmentation fault. #0 0x00000000004fc1d2 in ngx_http_echo_timer_elapsed_variable (r=0x2739770, v=0x273a1b0, data=0) at ../echo-nginx-module-0.49/src/ngx_http_echo_timer.c:25 25 if (ctx->timer_begin.sec == 0) { (gdb) p ctx $1 = (ngx_http_echo_ctx_t *) 0x0
{ ngx_string("echo_after_body"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_ANY, ngx_http_echo_echo_after_body, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_echo_loc_conf_t, after_body_cmds), NULL }, { ngx_string("echo_location_async"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE12, ngx_http_echo_echo_location_async, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("echo_location"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE12, ngx_http_echo_echo_location, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL },
I find echo_location[_async]
and several other directives's definitions lack offset
somehow. A wild guess: ngx_http_echo_helper
parses them correctly just because handler_cmds
is the first member of ngx_http_echo_loc_conf_t
.
Is this done on purpose or what?
Confirmed and submitted and fixed
ngx_http_echo_handler.h:9 ngx_int_t ngx_http_echo_handler_init(ngx_conf_t *cf);
stale function prototype.
Confirmed and submitted and fixed
引用计数异常。如果 rc > NGX_HTTP_SPECIAL_RESPONSE,r->main->count--一次 造成 count == 0。正常情况下,木有问题,因为echo 的 post_handler 不修改 count,最终值依然为 1. 那么,如何处理这两个流程呢?
怎么着都应该在 post_handler
里负责向下推动流程,而把原先的流程结束掉我·这
303 rc = ngx_http_echo_exec_echo_read_request_body(r, ctx); 304 305 #if defined(nginx_version) && nginx_version >= 8011 306 / XXX read_client_request_body always increments the counter / 307 r->main->count--; 308 #endif
duplicate NGX_ERROR and submitted and fixed
99 ngx_int_t 100 ngx_http_echo_send_chain_link(ngx_http_request_t* r, 101 ngx_http_echo_ctx_t *ctx, ngx_chain_t *in) 102 { 103 ngx_int_t rc; 104 105 rc = ngx_http_echo_send_header_if_needed(r, ctx); 106 107 if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) { 108 return rc; 109 } 110 111 if (rc == NGX_ERROR) { 112 return NGX_HTTP_INTERNAL_SERVER_ERROR; 113 }
Confirmed
ngx_http_echo_wev_handler
- 为何没有检查超时 Category:Nginx Tagged:c/c++ notes nginx