echo
是个很有意思,也相当有用模块。能把编程语言的特性引入 Nginx 配置文件,
作者的想像力很是丰富!
本篇来先来分析一下 echo
模块的总体框架和大致流程。
下一篇:细节篇 分另解析
一下此模块用到的各种 Nginx 知识点。
Names
- 配置指令 vs.
cmd
,下面将cmd
称为 命令。同时,配置指令对应的函数对请 求的处理,我们称为 在请求上执行命令。
What to learn
关注点:模块文档 已经列举的 - 这 个模块对 Nginx 模块开发者可能会用到的技术做了示范。这些技术包括:
- 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.
数据结构
-
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
调用处 理。
Work Flow
模块初始化
-
配置解析过程中处理包含变量的参数的标准模式 (使用
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; } }
-
配置解析和模块的初始化主要流程到这里就完成了。
content handler cmds 如何执行的
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 cmds 如何执行的
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
模块对变量支撑是如何实现的。
Variables
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"
是如何实现的;自定义模块提供变量支持要做的工作;和如何在配置指令中支持变量参数。
接下来,将对模块实现的 "异步子请求"、"同步子请求" 和 "响应的输出控制" 等功能点 进行详细的分析。
FAQ
- Q.
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 theecho_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 manulThe 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 instring
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 optionalsafe
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
- Q. 执行
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 inr->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
未对参数有效性进行检查,而在运行时才检查,这样设计的作用是什么? - A. / TODO /
Issues
- Confirmed and fixed.
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 "-"
- Confirmed and By Design
27 location /echo { 28 echo_foreach_split -- '-a-' 'cat-a-dog-a-mouse'; 29 echo_exec /echo_exec; 30 echo_end; 31 }
只会被执行一次
- To be confirmed
cmd->args 会不会被重复 eval'ed,会!并且每一个 cmd 执行前都会被 eval,不管有 没有参数。
if (cmd->args) {
}
->
if (cmd->args.nelts) {
}
- Confirmed and fixed.
在没有使用任何 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
- Confirmed and submitted and fixed.
{ 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
- 为何没有检查超时
Comments
不要轻轻地离开我,请留下点什么...