本文目录

总结

  • agentzh 的模块测试用例很完善,暂时可以学习使用。直到找到好的 Python 测试工具 为止。它还使用了 Valgrind's suppression file 来检查内存泄漏问题。很帅。

  • -t to specify content type criteria; -s to specify HTTP status code criteria.

  • Unlinke the standard headers module, this one's directives will by default apply to all the status codes.

  • 模块全名 ngx_http_headers_more_filter_module,配置结构体命名为 ngx_http_headers_more_loc_conf_t, ngx_http_headers_more_main_conf_t。其它 命名:ngx_http_headers_more_filter_commands, ngx_http_headers_more_filter_module_ctx, ngx_http_headers_more_merge_loc_conf.

  • more_set_headers, more_clear_headers, more_set_input_header, more_clear_input_headers; -t, -s.

  • 模块提供的 filter 类型为 HTTP_AUX_FILTER_MODULES,其初始化顺序如下定义:

        modules="$modules $HTTP_MODULES $HTTP_FILTER_MODULES \
                 $HTTP_HEADERS_FILTER_MODULE \
                 $HTTP_AUX_FILTER_MODULES \
                 $HTTP_COPY_FILTER_MODULE \
                 $HTTP_RANGE_BODY_FILTER_MODULE \
                 $HTTP_NOT_MODIFIED_FILTER_MODULE"
    
  • 配置存储结构体:

    /* 一条配置指令中包含的参数 */
    typedef struct {
        ngx_array_t                 *types; /* of ngx_str_t */
        ngx_array_t                 *statuses; /* of ngx_uint_t */
        ngx_array_t                 *headers; /* of ngx_http_headers_more_header_val_t */
        ngx_flag_t                  is_input; /* headers_out or headers_in */
    } ngx_http_headers_more_cmd_t;
    
    typedef struct {
        ngx_array_t         *cmds; /* of ngx_http_headers_more_cmd_t */
    } ngx_http_headers_more_loc_conf_t;
    
    typedef struct {
        ngx_int_t           postponed_to_phase_end;
        ngx_int_t           requires_filter; /* 设置了输出包头处理指令后,就
                                                需要在输出 `filter chain` 中
                                                注册此模块的 `filter handler`
                                                了 */
        ngx_int_t           requires_handler; /* 设置了输入包头处理指令后,
                                                 就需要在 `NGX_HTTP_REWRITE_PHASE`
                                                 中注册此模块的 回调函数
                                                 `ngx_http_headers_more_handler`
                                                 */
    } ngx_http_headers_more_main_conf_t;
    
    struct ngx_http_headers_more_header_val_s {
        ngx_http_complex_value_t            value;
        ngx_uint_t                          hash;
        ngx_str_t                           key;
        ngx_http_headers_more_set_header_pt handler;
        ngx_uint_t                          offset;
        ngx_flag_t                          replace;
        ngx_flag_t                          wildcard;
    };
    
    /* 标准输出包头的处理函数及上下文 - ngx_http_header_more_set_handlers */
    /* 同时,对非标准的 (或者说未定义到 ngx_http_request_t::headers_out 的)
       提供默认处理函数 `ngx_http_set_header` */
    typedef struct {
        ngx_str_t                               *name;
        ngx_uint_t                              offset;
        ngx_http_headers_more_set_header_pt     handler;
    } ngx_http_headers_more_set_header_t;
    
  • 这个模块提供的4个指令,都能用于 if 上下文中 (指令 flag 含有 NGX_HTTP_LIF_CONF)。父作用域的配置会被合并到子作用域的配置项数组前面,如果子 作用域未对此模块进行配置,则直接继承父作用域的配置。

    orig_len = conf->cmds->nelts;
    
    (void) ngx_array_push_n(conf->cmds, prev->cmds->nelts);
    
    cmd = conf->cmds->elts;
    
    for (i = 0; i < orig_len; i++) {
        cmd[conf->cmds->nelts - 1 - i] = cmd[orig_len - 1 - i];
    }
    
    prev_cmd = prev->cmds->elts;
    
    for (i = 0; i < prev->cmds->nelts; i++) {
        cmd[i] = prev_cmd[i];
    }
    
  • 4 个指令的解析都由 ngx_http_headers_more_parse_directive 完成,通过设置参数 opcode 告诉此函数指令是要 set 还是要 clear 包头。同时,针对输入包头和输出 包头,有两个不同版本的 ngx_http_headers_more_parse_directive,分别静态实现于 ngx_http_headers_more_headers_out.cngx_http_headers_more_headers_in.c 中。

    • 请求包头的设置指令只支持 -t 选项,因为这个阶段没有响应代码。每一行配置 指令对应一个 ngx_http_headers_more_cmd_t 类型变量,此变量中存储着该指令的 所有参数:content-typeresponse status,包头,还有用于标识请求还是响应 处理指令的is_input

      • 出现 -t 选项时 (arg[i].data[0] == '-' && arg[i].data[1] == 't'), 调用 ngx_http_headers_more_parse_typesarg[i + 1] 当成 type 解析:

        ngx_int_t
        ngx_http_headers_more_parse_types(ngx_log_t *log, ngx_str_t *cmd_name,
            ngx_str_t *value, ngx_array_t *types)
        {
            ...
            p = value->data;
            last = p + value->len;
        
            for ( ; p != last; p++) {
                if (t == NULL) {
                    if (isspace(*p)) {
                        continue;
                    }
        
                    t = ngx_array_push(types);
                    ...
                    t->len = 1;
                    t->data = p;
        
                    continue;
                }
        
                if (isspace(*p)) {
                    t = NULL;
                    continue;
                }
        
                t->len++;
            }
        
            return NGX_OK;
        }
        
      • 出现 -s 选项时 (arg[i].data[0] == '-' && arg[i].data[1] == 't'), 调用 ngx_http_headers_more_parse_statusesarg[i + 1] 当成响应码进 行解析。解析过程和ngx_http_headers_more_parse_types` 类似,只是多了整 数的检验和转换逻辑。

      • 其它非选项的参数 (arg[i].data[0] != '-'),都被当成是包头。调用 ngx_http_headers_more_parse_header 处理。再解析出一个包头后,创建一个 ngx_http_headers_more_header_val_t 变量。并且,如果此包头是内建包头 (出现在 ngx_http_headers_more_set_handlers 中),给其赋于预定义的处理 函数;如果不是内建包头,将处理函数设为 ngx_http_set_header。同时,需要 注意的是,内建请求包头和响应包头对应两组同名的静态数组和相应的处理函数。

        ngx_int_t
        ngx_http_headers_more_parse_header(ngx_conf_t *cf, ngx_str_t *cmd_name,
            ngx_str_t *raw_header, ngx_array_t *headers,
            ngx_http_headers_more_opcode_t opcode,
            ngx_http_headers_more_set_header_t *handlers)
        {
            ...
            seen_end_of_key = 0;
            for (i = 0; i < raw_header->len; i++) {
                if (key.len == 0) {
                    if (isspace(raw_header->data[i])) {
                        continue;
                    }
        
                    key.data = raw_header->data;
                    key.len = 1;
        
                    continue;
                }
        
                if (!seen_end_of_key) {
                    if (raw_header->data[i] == ':' || isspace(raw_header->data[i]))
                    {
                        seen_end_of_key = 1;
                        continue;
                    }
        
                    key.len++;
        
                    continue;
                }
        
                if (value.len == 0) {
                    if (raw_header->data[i] == ':' || isspace(raw_header->data[i]))
                    {
                        continue;
                    }
        
                    value.data = &raw->header->data[i];
                    value.len = 1;
        
                    continue;
                }
        
                value.len++; /* 直到参数未尾 */
            }
        
            hv->hash = ngx_hash_key_lc(key.data, key.len);
            hv->key = key;
        
            hv->offset = 0;
        
            if (i = 0; handlers[i].name.len; i++) {
                if (hv->key.len != handlers[i].name.len
                    || ngx_strncasecmp(hv->key.data, handlers[i].name.data,
                                       handlers[i].name.len) != 0)
                {
                    continue;
                }
        
                hv->offset = handlers[i].offset;
                hv->handler = handlers[i].handler;
        
                break;
            }
        
            /* 默认包头回调函数 */
            if (handlers[i].name.len == 0 && handlers[i].handler) {
                hv->offset = handlers[i].offset;
                hv->handler = handlers[i].handler;
            }
        
            /* 包头请求操作 */
            if (opcode == ngx_http_headers_more_opcode_clear) {
                value.len = 0;
            }
        
            if (value.len == 0) {
                ngx_memzero(&hv->value, sizeof(ngx_http_complex_value_t));
                return NGX_OK;
            }
        
            /* Nginx request header value requires to be a null-terminated
             * C string */
        
            p = ngx_palloc(cf->>pool, value.len + 1);
            ...
            ngx_memcpy(p, value.data, value.len);
            p[value.len] = '\0';
            value.data = p;
            value.len++; /* we should also compile the trailing '\0' */
        
            ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
        
            ccv.cf = cf;
            ccv.value = &value;
            ccv.complex_value = &hv->value;
        
            ngx_http_compile_complex_value(&ccv);
        
    • clear 操作和 set 操作的区别就在于包头的 value.len 是否为0.

    • 在指令解析完成后,如果设置了对输出包头的操作,main_conf::requires_filter 被置为1;如果设置了对输入包头的操作,main_conf::requires_handler 被置为1。

    • 模块的 post config 阶段根据 requires_filterrequires_handler 的值 决定是否注册 header filterphase handler

      ngx_http_headers_more_prev_cycle = NULL;
      ...
      if (ngx_http_headers_more_prev_cycle != ngx_cycle) {
          ngx_http_headers_more_prev_cycle = ngx_cycle;
          multi_http_blocks = 0;
      
      } else {
          multi_http_blocks = 1;
      }
      
      if (multi_http_blocks || hmcf->requires_filter) {
          rc = ngx_http_headers_more_filter_init(cf);
          if (rc != NGX_OK) {
              return rc;
          }
      }
      
    • 这里引出一个问题:上面通过判断当前 cycle 是否和上次保存的一致来判断是否 是同一个 http {} (commit/5a70b6b4680b71b3e000d47911416b1474b81698)。那么,

    • 多个 http {} 时,配置是如何存储的?2. reload 配置会不会也会造成两个 cycle 不一样呢?

      • 多个 http {} 时,第二次 ngx_http_block 函数调用生成的 ctx 会覆盖 第一次调用生成的,也就是说 ngx_get_conf(cycle->conf_ctx, ngx_core_module) 只能得到第二个 ctx

      • 这个目前不会影响 Nginx 的正常运行,因为第一个 http {} 解析出来的配置 已经被各个虚拟主机保存,也就是说,第一个 http {} 的配置不会丢失。除了 无法再通过 ngx_get_conf 引用。

      • 各个 phasehandlershttp {} 相关,两个 http {} 可能会有 不同的 phase handlers (cmcf->phase_engine),但是 filter chain 是所 有 http {} 共用的,一个 http {} 对其进行修改 (根据不同的配置是否启用 ) 的话,也会影响到其它 http {}

      • ngx_cycle 的变化时机:初始化时配置解析前 (&init_cycle) 和 初始化完成后 (cycle);重新加载配置文件前和使用新配置初始化后。解析第一个http {}和第二个http {}都属于配置解析阶段,期间的ngx_cycle` 值不会变化。

    • 那么上段代码带来的结果就是:

      • 为了不让第二个 http {} 因没有定义 more_XXX 而未将此模块 filter 添加至 filter_chain

      • 但,第二个 http {}hmcf->requires_filter 即使为 0,也会打开此 模块提供的 filter

      • reload 时不会有任何问题,当前模块保留的 ngx_http_headers_more_prev_cycle 值,在 reload 结束后,总会和新的 ngx_cycle 不相同。

        cycle = ngx_init_cycle(cycle); ... ngx_cycle = cycle;

      • 为了节省几条判断指令,带来的这些逻辑,个人觉得不划算。不如无论如何都 将此 filter 挂载到 filter chain 来得简洁明了。

    • 由于 phase handler 和 具体 http {} 相关,所以,只需要根据当成 http {} 的配置情况开启或者关闭即可。

      if (!hmcf->requires_handler) {
          return NGX_OK;
      }
      
      cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
      
      h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
      ...
      *h = ngx_http_headers_more_handler;
      
  • 按照请求的处理流程,先来关注下 input header 的处理。在前面注册的 phase handlerNGX_HTTP_REWRITE_PHASE 中被调用。

    • 此阶段由 ngx_http_core_rewrite_phase 调度回调函数,回调函数的允许的返回 值有 NGX_DONE (当前回调函数尚未结束,需要在当前阶段被再次调用)、 NGX_DECLINED (当前回调函数执行完毕,可调用下一个回调函数),其余的诸如 NGX_AGAIN, NGX_DONE, NGX_ERROR 等,都表示回调函数处理出错,Nginx 需要 关闭此请求。 (not for 0.8.34)

    • 11 个 phase 中为何选择 REWRITE_PHASE 呢? phase 提供给各种模块正确的 执行时机,以规范和协调请求的处理过程。其中,FIND_CONFIGPOST_REWRITEPOST_ACCESSTRY_FILES 是不允许用户自定义模块注册的 (即使向这几个 phase 注册了回调函数,也会被 Nginx 忽略,详见 ngx_http_init_phase_handlers)。 那剩下能用的 phase 有:POST_READSERVER_REWRITEREWRITEPREACCESSACCESSCONTENT 了,这几个阶段的 checker 基本都是 ngx_http_core_generic_phase (除了 ACCESSngx_http_core_access_phaseREWRITEngx_http_core_rewrite_phaseCONTENTngx_http_core_content_phase。这几个阶段处理逻辑是一样的,使用哪个,基本上 是根据意义上的相近来选择的 (此模块修改、添加、删除包头,和 rewrite 有几分 相近)。

    • 不知道出于何种考虑,如果此模块 handler 不是 REWRITE 阶段的最后一个,将 其移至最后一个。类似的做法在 ngx_log_if 模块中出现过,只不过,ngx_log_if 保证它提供的 handler 始终处于 LOG 阶段的第一个位置。由于在配置解析过程中 无法保证此模块最后一个注册,所以,选择了第一次处理请求时处理。/ TODO /

      if (!hmcf->postponed_to_phase_end) { ... hmcf->postponed_to_phase_end = 1; ... ph = cmcf->phase_engine.handlers; cur_ph = &ph[r->phase_handler]; last_ph = &ph[cur_ph->next - 1];

      if (cur_ph < last_ph) {
          tmp = *cur_ph;
      
          memmove(cur_ph, cur_ph + 1,
                  (last_ph - cur_ph) * sizeof (ngx_http_phase_handler_t));
      
          *last_ph = tmp;
      
          r->phase_handler--; /* redo the current ph */
      
          return NGX_DECLINED;
      }
      

      }

    • 真正的请求包头操作:遍历包头配置,根据 input 包头配置设用 ngx_http_headers_more_exec_input_cmdr->headers_in 进行处理。

      conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_more_filter_module);

      if (conf->cmds) { if (r->http_version < NGX_HTTP_VERSION_10) { return NGX_DECLINED; }

      cmd = conf->cmds->elts;
      for (i = 0; i < conf->cmds->nelts; i++) {
          if (!cmd[i].is_input) {
              continue;
          }
      
          rc = ngx_http_headers_more_exec_input_cmd(r, &cmd[i]);
      
          if (rc != NGX_OK) {
              return rc;
          }
      }
      

      }

  • 响应包头的处理逻辑和请求包头框架上类似,由函数 ngx_http_headers_more_filter 完成:

    conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_more_filter_module);
    
    if (conf->cmds) {
        cmd = conf->cmds->elts;
        for (i = 0; i < conf->cmds->nelts; i++) {
            if (cmd[i].is_input) {
                continue;
            }
    
            rc = ngx_http_headers_more_exec_cmd(r, &cmd[i]);
    
            if (rc != NGX_OK) {
                return rc;
            }
        }
    }
    
  • ngx_http_headers_exec_cmd 执行对响应包头预先设置的操作 (添加、修改或删除指定 包头);ngx_http_headers_exec_input_cmd 执行对请求包头预先设置的操作 (添加、修 改或删除指头包头)。这两个函数很简单,但理解的前提就是配置解析时建立的数据结构。

    • 响应包头的处理过程如下:取出包头的值,存到 value 中,再调用此包头的回调 处理函数。

      ngx_int_t ngx_http_headers_more_exec_cmd(ngx_http_request_t r, ngx_http_headers_more_cmd_t cmd) { ... if (!cmd->headers) [ return NGX_OK; }

      if (cmd->types && !ngx_http_headers_more_check_type(r, cmd->types)) {
          return NGX_OK;
      }
      
      if (cmd->statuses
          && !ngx_http_headers_more_check_status(r, cmd->statuses))
      {
          return NGX_OK;
      }
      
      h = cmd->headers->elts;
      for (i = 0; i < cmd->headers->nelts; i++) {
          if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
              return NGX_ERROR;
          }
      
          if (value.len) {
              value.len--;
          }
      
          if (h[i].handler(r, &h[i], &value) != NGX_OK) {
              return NGX_ERROR;
          }
      }
      
      return NGX_OK;
      

      }

    • 假设,我们上面正在处理包头 Server,那其对应的回调处理函数是 ngx_http_set_builtin_header (in/out 两个版本)。

  • 包头处理过程中的诸多细节。

    • r->headers_in.content_typengx_http_headers_in 定义的包头处理函数 赋值,指向 r->headers_in.headers 中的 ngx_table_elt_t 类型的元素。它的类 型是 ngx_table_elt_t *content type 是字符串存储。

    • header 的处理方式和 ngx_http_headers_inngx_http_headers_out 一 样。唯一不同的是,此模块不需要在请求处过程中查找 header,所以省略了 hash 结构。

    • ngx_http_complex_value 获取当成正在处理包头对应的 value 字符串。 value.len == 0 时,表示需要执行 clear 操作。添加、修改和删除操作在各包 头对应的回调函数中完成。以 User-Agent 为例,它对应的回调函数是 ngx_http_set_user_agent_header

      static ngx_int_t ngx_http_set_user_agent_header(ngx_http_request_t r, ngx_http_headers_more_header_val_t hv, ngx_str_t value) { ... / clear existing settings */

      r->headers_in.msie = 0;
      ...
      if (value->len == 0) { /* 删除操作 */
          return ngx_http_set_builtin_header(r, hv, value);
      }
      
      /* check some widespread browsers */
      ...
          r->headers_in.safari = 1;
      ...
      return ngx_http_set_builtin_header(r, hv, value);
      

      }

    • ngx_http_set_builtin_header 实现了基本的包头回调操作,其它回调函数只需要 在它的基础上做一些特殊的额外处理。

      static ngx_int_t ngx_http_set_builtin_header(ngx_http_request_t r, ngx_http_headers_more_header_val_t hv, ngx_str_t value) { ngx_table_elt_t h, **old;

      if (hv->offset) { /* 包头在 `headers_in` 中有专用字段 */
          old = (ngx_table_elt_t **) ((char *) &r->headers_in + hv->offset);
      
      } else { /* 包头在 `headers_in` 中没有专用字段引用,只存在于
                  `headers_in.headers` 中
                */
          old = NULL;
      }
      
      if (old == NULL || *old == NULL) {
          return ngx_http_set_header_helper(r, hv, value, old);
      }
      
      h = *old; /* 在 `headers_in` 中有专用字段引用,并且此包头已被接收 */
      
      if (value->len == 0) { /* 清理有专用字段引用的包头 */
          h->hash = 0;
          h->value = *value;
      
          return ngx_http_set_header_helper(r, hv, value, old);
      }
      
      h->hash = hv->hash; /* 设置有专用字段引用的包头 */
      h->value = *value;
      
      return NGX_OK;
      

      }

    • ngx_http_set_header_helper 直正的操作 r->headers_in.headers 链表。

    • 上面的流程和输入包头处理类似,这里就不再详细说明了。

Comments

不要轻轻地离开我,请留下点什么...

comments powered by Disqus

Published

Category

Nginx

Tags

Contact