总结
-
agentzh 的模块测试用例很完善,暂时可以学习使用。直到找到好的 Python 测试工具 为止。它还使用了 Valgrind's suppression file 来检查内存泄漏问题。很帅。
-
-t
to specifycontent 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.c
和ngx_http_headers_more_headers_in.c
中。-
请求包头的设置指令只支持
-t
选项,因为这个阶段没有响应代码。每一行配置 指令对应一个ngx_http_headers_more_cmd_t
类型变量,此变量中存储着该指令的 所有参数:content-type
,response status
,包头,还有用于标识请求还是响应 处理指令的is_input
。-
出现
-t
选项时 (arg[i].data[0] == '-' && arg[i].data[1] == 't'
), 调用ngx_http_headers_more_parse_types
把arg[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_statuses
把arg[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_filter
和requires_handler
的值 决定是否注册header filter
和phase 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
引用。 -
各个
phase
的handlers
和http {}
相关,两个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
handler
在NGX_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_CONFIG
、POST_REWRITE
、POST_ACCESS
、TRY_FILES
是不允许用户自定义模块注册的 (即使向这几个phase
注册了回调函数,也会被 Nginx 忽略,详见ngx_http_init_phase_handlers
)。 那剩下能用的phase
有:POST_READ
、SERVER_REWRITE
、REWRITE
、PREACCESS
、ACCESS
和CONTENT
了,这几个阶段的checker
基本都是ngx_http_core_generic_phase
(除了ACCESS
的ngx_http_core_access_phase
,REWRITE
的ngx_http_core_rewrite_phase
和CONTENT
的ngx_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_cmd
对r->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_type
由ngx_http_headers_in
定义的包头处理函数 赋值,指向r->headers_in.headers
中的ngx_table_elt_t
类型的元素。它的类 型是ngx_table_elt_t *
。content type
是字符串存储。 -
header
的处理方式和ngx_http_headers_in
和ngx_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
不要轻轻地离开我,请留下点什么...