转载

Nginx 源代码笔记 - HTTP 模块 - echo [1]

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 模块支持的 opcodeecho 模块将 echo_XXX 配置指令都解析为一个相应的命令,这些 opcode 就是各个命令的标识符。

  • ngx_http_echo_cmd_category_t 将命令分成两类:第一类 echo_handler_cmdlocation handler 调用处理;第二类 echo_filter_cmdoutput 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_thandler_cmdsbefore_body_cmdsafter_body_cmdsngx_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 开始对各模块配置项按作用域进行 mergeecho 模块的在 子作用域中的配置要么直接继承父作用域 (子作用域没有出现同类 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_subrequestecho_subrequest_asyncecho_locationecho_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 handlercontent handlerngx_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 语法,可以根据循环条件多次执行这个语法块中的其它命 令 (暂不支持循环体嵌套)。

当请求 "进入" 配置了 echocontent handler directiveslocation {} 后, 在 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_splitecho_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_cmdcmd_indexnext_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->foreachNULL ,表明循环块的结束。这时, $echo_it 变量在循环块是无效的值。

  • 命令 echo_opcode_echo_sync 在模块解析到 echo_location/echo_location_asyncecho_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_cmdsngx_http_echo_loc_conf_t::after_body_cmds 数组中,存储结构和 content handler 命令一致。这些命令被 ngx_http_echo_header_filterngx_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_bodyecho_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_cmdsngx_http_echo_run_cmds 的逻辑相似, after bodybefore 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_variablengx_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_handlerngx_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_handlerngx_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_handlerngx_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_subrequestecho_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_lengthngx_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 规范只定义了一种传输编码, chunkedngx_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
  • 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 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 未对参数有效性进行检查,而在运行时才检查,这样设计的作用是什么?

  • 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 - 为何没有检查超时

Category:Nginx Tagged:c/c++ notes nginx

Comments

正文到此结束
Loading...