ngx_http_rewrite_module代码分析
概述
该模块是个复杂的模块,11个执行阶段其中3个阶段是为该模块设置的。rewrite模块强依赖pcre这个正则表达式库。
该模块提供了个简单的脚本执行引擎,可以在配置文件中写一些简单的脚本语言,该模块也是ngx_http_script.c的对外表现。 脚本引擎分为2个阶段,编译和执行。编译是在ngx启动时解析配置文件就开始的,执行是在请求处理过程中。
代码分析
通过ngx_http_rewrite_create_loc_conf函数分配一个下面的结构体保存配置。 其中,codes是保存脚本编译后的执行回调函数。 也就是说编译阶段是向codes数组中添加执行函数和变量,执行阶段是遍历codes数组调用函数。
typedef struct {
ngx_array_t *codes; /* uintptr_t */
ngx_uint_t stack_size;
ngx_flag_t log;
ngx_flag_t uninitialized_variable_warn;
} ngx_http_rewrite_loc_conf_t;
通过ngx_http_rewrite_merge_loc_conf合并配置。其中codes并不会合并。
会用到的结构体:
typedef struct {
u_char *ip; // 指向回调函数
u_char *pos; // 指向buf
ngx_http_variable_value_t *sp; // 数组,作为存放执行中间结果的栈
ngx_str_t buf; // 保存最终结果
ngx_str_t line;
/* the start of the rewritten arguments */
u_char *args;
unsigned flushed:1;
unsigned skip:1;
unsigned quote:1;
unsigned is_args:1;
unsigned log:1;
ngx_int_t status;
ngx_http_request_t *request;
} ngx_http_script_engine_t;
typedef ngx_variable_value_t ngx_http_variable_value_t;
typedef struct {
unsigned len:28;
unsigned valid:1;
unsigned no_cacheable:1;
unsigned not_found:1;
unsigned escape:1;
u_char *data;
} ngx_variable_value_t;
// 脚本执行结构体
typedef struct {
// 脚本执行的结构体,第一个元素必需是ngx_http_script_code_pt类型的函数指针。
// 因为codes数组元素是变长的,遍历数组是通过这个回调函数修改的。编程技巧。
ngx_http_script_code_pt code;
ngx_http_regex_t *regex;
ngx_array_t *lengths;
uintptr_t size;
uintptr_t status;
uintptr_t next;
unsigned test:1;
unsigned negative_test:1;
unsigned uri:1;
unsigned args:1;
/* add the r->args to the new arguments */
unsigned add_args:1;
unsigned redirect:1;
unsigned break_cycle:1;
ngx_str_t name;
} ngx_http_script_regex_code_t;
接下来会按照指令分析代码:
set
语法: set $name "${host}_liwq"
-
启动阶段执行解析
nginx会调用ngx_http_rewrite_set解析配置文件的set指令。
- 调用ngx_http_add_variable添加变量到cmcf->variables_keys。
- 调用ngx_http_get_variable_index添加变量到cmcf->variables。
- 调用ngx_http_rewrite_value添加计算变量的值的结构体到lcf->codes(其中codes是个数组,元素是变量)。
- 调用ngx_http_script_start_code函数添加一个赋值函数结构体(结构体包含了变量在cmcf->variables数组中的下标)到lcf->codes。
- 返回成功。
-
运行阶段指令执行
运行到11个阶段的NGX_HTTP_SERVER_REWRITE_PHASE或NGX_HTTP_REWRITE_PHASE阶段会调用ngx_http_rewrite_handler函数。
- 检测是否该执行。
- 分配ngx_http_script_engine_t 结构体e,以及e的sp指针为ngx_http_variable_value_t类型的数组。
- 让e的ip指向rlcf->codes->elts。即数组codes的首地址。
- e->ip不为空,则强制把首地址转为ngx_http_script_code_pt函数指针,并执行。 a. 执行ngx_http_script_value_code函数,把值保存到e的sp数组中。移动ip指针。 b. 执行ngx_http_script_set_var_code函数,把a步骤计算好的sp数组中的值保存到request->variables数组中。移动ip指针。 c. ip为空退出。正确则返回NGX_DECLINED,执行该阶段下一个handler。
-
附:codes中添加的回调
-
常量字符串: a. ngx_http_script_value_code_t 结构体,ngx_http_script_value_code回调。 b. ngx_http_script_var_code_t 结构体, ngx_http_script_set_var_code回调,变量赋值函数。
-
常量+变量字符串: a. ngx_http_script_complex_value_code_t 结构体, ngx_http_script_complex_value_code回调,计算变量结果长度,分配字符串所需要的内存。 b. 字符串常量ngx_http_script_copy_code_t结构体,ngx_http_script_copy_code回调。 向lengths数组添加ngx_http_script_copy_code_t结构体,ngx_http_script_copy_len_code回调。 c. 变量ngx_http_script_var_code_t结构体, ngx_http_script_copy_var_code回调。 向lengths数组添加ngx_http_script_var_code_t结构体,ngx_http_script_copy_var_len_code回调。 d. ngx_http_script_var_code_t结构体, ngx_http_script_set_var_code 回调。
-
return
语法:return code [text]
- 启动阶段执行解析
ngx会调用ngx_http_rewrite_return函数解析return指令。
向codes分配一个结构体ngx_http_script_return_code_t,其code赋值ngx_http_script_return_code回调函数。其status保存了指令的code。其text是对应了指令的text。
-
运行阶段指令执行
-
codes中添加的函数
- ngx_http_script_return_code_t结构体,ngx_http_script_return_code回调。
相关代码:
typedef struct {
ngx_http_script_code_pt code; // 回调函数
uintptr_t status;
ngx_http_complex_value_t text; // 脚本编译后的结果
} ngx_http_script_return_code_t;
void
ngx_http_script_return_code(ngx_http_script_engine_t *e)
{
ngx_http_script_return_code_t *code;
code = (ngx_http_script_return_code_t *) e->ip;
if (code->status < NGX_HTTP_BAD_REQUEST
|| code->text.value.len
|| code->text.lengths)
{
e->status = ngx_http_send_response(e->request, code->status, NULL,
&code->text);
} else {
e->status = code->status;
}
e->ip = ngx_http_script_exit;
}
ngx_int_t
ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status,
ngx_str_t *ct, ngx_http_complex_value_t *cv)
{
ngx_int_t rc;
ngx_str_t val;
ngx_buf_t *b;
ngx_chain_t out;
// 丢弃请求的包体内容
if (ngx_http_discard_request_body(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
r->headers_out.status = status;
// 根据编译后的脚本获取结果
if (ngx_http_complex_value(r, cv, &val) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 302等跳转
if (status == NGX_HTTP_MOVED_PERMANENTLY
|| status == NGX_HTTP_MOVED_TEMPORARILY
|| status == NGX_HTTP_SEE_OTHER
|| status == NGX_HTTP_TEMPORARY_REDIRECT
|| status == NGX_HTTP_PERMANENT_REDIRECT)
{
ngx_http_clear_location(r);
r->headers_out.location = ngx_list_push(&r->headers_out.headers);
if (r->headers_out.location == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
r->headers_out.location->hash = 1;
ngx_str_set(&r->headers_out.location->key, "Location");
r->headers_out.location->value = val;
return status;
}
r->headers_out.content_length_n = val.len;
if (ct) {
r->headers_out.content_type_len = ct->len;
r->headers_out.content_type = *ct;
} else {
if (ngx_http_set_content_type(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
}
// head方法 或 子请求 仅仅发送resp的头。
if (r->method == NGX_HTTP_HEAD || (r != r->main && val.len == 0)) {
return ngx_http_send_header(r);
}
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->pos = val.data;
b->last = val.data + val.len;
b->memory = val.len ? 1 : 0;
b->last_buf = (r == r->main) ? 1 : 0;
b->last_in_chain = 1;
out.buf = b;
out.next = NULL;
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
return ngx_http_output_filter(r, &out);
}
if
语法: if (condition) { ... }
if是一个特殊的location,但还是location。还会挂在上一级的clcf->locations双链表上。
-
启动阶段执行解析
if指令会调用ngx_http_rewrite_if函数。
- 分配location所需的ctx,并赋值。调用ngx_http_add_location函数把location挂到上一级clcf->locations的链表上。
- 调用ngx_http_rewrite_if_condition函数处理条件,向lcf->codes数组中添加元素。即:$x = 'XXX' if 这写条件对应的函数和变量。变量添加的是ngx_http_script_var_code函数和变量的index,值添加的是ngx_http_script_value_code函数和对应的值,是否等于添加的是ngx_http_script_equal_code函数。
- 向lcf->codes 添加条件执行函数ngx_http_script_if_code。
- 调用ngx_conf_parse解析{}的内容,其cmd_type是NGX_HTTP_SIF_CONF或NGX_HTTP_LIF_CONF。而if location下的rewrite模块的codes是继承了上一级rewrite模块的。
- 解析{return 444} 参考return指令。
- if_code 的next是 if条件为false跳转的步长,也就是解析{}后codes的结尾。
-
运行阶段指令执行
rewrite
语法: rewrite regex replacement [flag];
-
启动阶段执行解析 rewrite指令的解析会调用ngx_http_rewrite函数。
- 在codes分配一个ngx_http_script_regex_code_t结构体,其回调函数code为ngx_http_script_regex_start_code,其regex为调用ngx_http_regex_compile函数编译好的正则表达式。
- 调用ngx_http_script_compile编译脚本。向codes添加ngx_http_script_copy_capture_code_t结构体,其回调函数code是ngx_http_script_copy_capture_code,其参数n是2占位符。同时向regex->lengths中添加ngx_http_script_copy_capture_code_t结构体,其回调函数code是ngx_http_script_copy_capture_len_code,其参数n是2占位符。regex->lengths还会添加个空指针。
- 向codes添加结构体ngx_http_script_regex_end_code_t,其回调函数code是ngx_http_script_regex_end_code。
- 配置了flag,也就是last为1,向codes添加一个空指针表示结束。
-
运行阶段指令执行 rewrite阶段checker函数调用ngx_http_rewrite_handler函数。
- 执行ngx_http_script_regex_start_code函数,该函数先调用ngx_http_regex_exec函数匹配正则表达式。把捕获的值下标保存到请求结构体r->captures数组中。r->ncaptures为捕获结果最大坐标。r->captures_data为匹配字符串。同时分配保存结果需要的内存。
- 执行ngx_http_script_copy_capture_code函数,把上述步骤捕获的变量添加到结果内存上。
- 执行ngx_http_script_regex_end_code函数修改请求结构体的uri。
- 配置了flag,codes为空返回。handler返回NGX_DECLINED。
返回到调用的checker函数ngx_http_core_rewrite_phase。NGX_HTTP_REWRITE_PHASE阶段只添加了一个回调函数。 执行到NGX_HTTP_POST_REWRITE_PHASE阶段的checker函数ngx_http_core_post_rewrite_phase。如果没有标识r->uri_changed(flag是break),则直接返回执行下一阶段的checker。如果标识了(flag是last)则跳回NGX_HTTP_FIND_CONFIG_PHASE阶段执行。
-
codes数组中添加的回调
- ngx_http_script_regex_code_t结构体, ngx_http_script_regex_start_code回调。
- 捕获变量结构体ngx_http_script_copy_capture_code_t, ngx_http_script_copy_capture_code回调函数。 在lengths添加ngx_http_script_copy_capture_code_t,回调函数是ngx_http_script_copy_capture_len_code。
- ngx_http_script_regex_end_code_t结构体,ngx_http_script_regex_end_code回调。
总结:
没有设置flag则会一直执行rewrite阶段所有的指令,包括return等。 如果flag设置了break,则会跳过rewrite阶段该模块剩余的指令,继续执行。 如果flag设置了是last,则会跳转到对应的find config阶段重新匹配新的location执行(rewrite配置在location内会重新跳转会find config阶段重新执行),rewrite配置在server内会改变url的location匹配。
- 相关代码
static char *
ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_rewrite_loc_conf_t *lcf = conf;
ngx_str_t *value;
ngx_uint_t last;
ngx_regex_compile_t rc;
ngx_http_script_code_pt *code;
ngx_http_script_compile_t sc;
ngx_http_script_regex_code_t *regex;
ngx_http_script_regex_end_code_t *regex_end;
u_char errstr[NGX_MAX_CONF_ERRSTR];
// 在codes分配一个元素,类型为ngx_http_script_regex_code_t
regex = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_regex_code_t));
if (regex == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(regex, sizeof(ngx_http_script_regex_code_t));
value = cf->args->elts;
ngx_memzero(&rc, sizeof(ngx_regex_compile_t));
// 正则表达式
rc.pattern = value[1];
rc.err.len = NGX_MAX_CONF_ERRSTR;
rc.err.data = errstr;
/* TODO: NGX_REGEX_CASELESS */
// 编译正则表达式
regex->regex = ngx_http_regex_compile(cf, &rc);
if (regex->regex == NULL) {
return NGX_CONF_ERROR;
}
// 脚本运行的回调
regex->code = ngx_http_script_regex_start_code;
regex->uri = 1;
regex->name = value[1];
if (value[2].data[value[2].len - 1] == '?') {
/* the last "?" drops the original arguments */
value[2].len--;
} else {
regex->add_args = 1;
}
last = 0;
if (ngx_strncmp(value[2].data, "http://", sizeof("http://") - 1) == 0
|| ngx_strncmp(value[2].data, "https://", sizeof("https://") - 1) == 0
|| ngx_strncmp(value[2].data, "$scheme", sizeof("$scheme") - 1) == 0)
{
regex->status = NGX_HTTP_MOVED_TEMPORARILY;
regex->redirect = 1;
last = 1;
}
if (cf->args->nelts == 4) {
if (ngx_strcmp(value[3].data, "last") == 0) {
last = 1;
} else if (ngx_strcmp(value[3].data, "break") == 0) {
regex->break_cycle = 1;
last = 1;
} else if (ngx_strcmp(value[3].data, "redirect") == 0) {
regex->status = NGX_HTTP_MOVED_TEMPORARILY;
regex->redirect = 1;
last = 1;
} else if (ngx_strcmp(value[3].data, "permanent") == 0) {
regex->status = NGX_HTTP_MOVED_PERMANENTLY;
regex->redirect = 1;
last = 1;
} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[3]);
return NGX_CONF_ERROR;
}
}
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.cf = cf;
sc.source = &value[2];
sc.lengths = ®ex->lengths;
sc.values = &lcf->codes;
sc.variables = ngx_http_script_variables_count(&value[2]); // 带$的个数
sc.main = regex;
sc.complete_lengths = 1;
sc.compile_args = !regex->redirect; // 不是直接302等跳转的
if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_CONF_ERROR;
}
regex = sc.main;
regex->size = sc.size; // 变量总的长度
regex->args = sc.args; // 参数添加了变量,就是 ?host=$host
// 没有配置变量且没有重复使用占位符变量的
if (sc.variables == 0 && !sc.dup_capture) {
regex->lengths = NULL;
}
//
regex_end = ngx_http_script_add_code(lcf->codes,
sizeof(ngx_http_script_regex_end_code_t),
®ex);
if (regex_end == NULL) {
return NGX_CONF_ERROR;
}
regex_end->code = ngx_http_script_regex_end_code;
regex_end->uri = regex->uri;
regex_end->args = regex->args; // 添加参数是变量
regex_end->add_args = regex->add_args; // 是否要添加参数
regex_end->redirect = regex->redirect; // 是否是直接302等跳转
if (last) {
// 结束标志,也就是配置了flag的
code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), ®ex);
if (code == NULL) {
return NGX_CONF_ERROR;
}
*code = NULL;
}
// 该条rewrite指令的添加到codes中所占的字节数。
// 不匹配rewrite的正则表达式时会用到。
regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
- (u_char *) regex;
return NGX_CONF_OK;
}
- 执行阶段的函数
void
ngx_http_script_regex_start_code(ngx_http_script_engine_t *e)
{
size_t len;
ngx_int_t rc;
ngx_uint_t n;
ngx_http_request_t *r;
ngx_http_script_engine_t le;
ngx_http_script_len_code_pt lcode;
ngx_http_script_regex_code_t *code;
code = (ngx_http_script_regex_code_t *) e->ip;
r = e->request;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http script regex: \"%V\"", &code->name);
if (code->uri) {
// 匹配uri
e->line = r->uri;
} else {
e->sp--;
e->line.len = e->sp->len;
e->line.data = e->sp->data;
}
// 执行正则匹配
rc = ngx_http_regex_exec(r, code->regex, &e->line);
if (rc == NGX_DECLINED) {
if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
"\"%V\" does not match \"%V\"",
&code->name, &e->line);
}
r->ncaptures = 0;
if (code->test) {
if (code->negative_test) {
e->sp->len = 1;
e->sp->data = (u_char *) "1";
} else {
e->sp->len = 0;
e->sp->data = (u_char *) "";
}
e->sp++;
e->ip += sizeof(ngx_http_script_regex_code_t);
return;
}
e->ip += code->next;
return;
}
if (rc == NGX_ERROR) {
e->ip = ngx_http_script_exit;
e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
return;
}
if (e->log || (r->connection->log->log_level & NGX_LOG_DEBUG_HTTP)) {
ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
"\"%V\" matches \"%V\"", &code->name, &e->line);
}
if (code->test) {
if (code->negative_test) {
e->sp->len = 0;
e->sp->data = (u_char *) "";
} else {
e->sp->len = 1;
e->sp->data = (u_char *) "1";
}
e->sp++;
e->ip += sizeof(ngx_http_script_regex_code_t);
return;
}
if (code->status) {
// 302等跳转
e->status = code->status;
if (!code->redirect) {
e->ip = ngx_http_script_exit;
return;
}
}
if (code->uri) {
r->internal = 1;
r->valid_unparsed_uri = 0;
if (code->break_cycle) {
r->valid_location = 0;
r->uri_changed = 0;
} else {
// 内部跳转
r->uri_changed = 1;
}
}
if (code->lengths == NULL) {
e->buf.len = code->size;
if (code->uri) {
if (r->ncaptures && (r->quoted_uri || r->plus_in_uri)) {
e->buf.len += 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len,
NGX_ESCAPE_ARGS);
}
}
for (n = 2; n < r->ncaptures; n += 2) {
// 根据捕获结果计算长度
e->buf.len += r->captures[n + 1] - r->captures[n];
}
} else {
ngx_memzero(&le, sizeof(ngx_http_script_engine_t));
le.ip = code->lengths->elts;
le.line = e->line;
le.request = r;
le.quote = code->redirect;
len = 0;
while (*(uintptr_t *) le.ip) {
lcode = *(ngx_http_script_len_code_pt *) le.ip;
len += lcode(&le);
}
e->buf.len = len;
}
if (code->add_args && r->args.len) {
// 需要添加参数,则计算添加的参数的长度
e->buf.len += r->args.len + 1;
}
// 分配输出结果的内存
e->buf.data = ngx_pnalloc(r->pool, e->buf.len);
if (e->buf.data == NULL) {
e->ip = ngx_http_script_exit;
e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
return;
}
e->quote = code->redirect;
e->pos = e->buf.data;
e->ip += sizeof(ngx_http_script_regex_code_t);
}
rewrite 添加的回调函数
rewrite ^(/download/)(?<file>.*)\.mp3$ $1/mp3/${file}?aaa=bbb last;
添加file变量
regex->regex->variables[i].capture
regex->regex->variables[i].index <----- script
v->get_handler = ngx_http_variable_not_found;
### lcf->codes
// index 0
ngx_http_script_regex_code_t:
code = ngx_http_script_regex_start_code;
(作用)
执行正则匹配,分配结果buf结构
// index 1(捕获位置变量)
ngx_http_script_copy_capture_code_t
code = ngx_http_script_copy_capture_code
n = 2 * n
// index 2(常量)
ngx_http_script_copy_code_t + len + NULL
ngx_http_script_copy_code
len
// index 3(变量)
ngx_http_script_var_code_t
ngx_http_script_copy_var_code
index
// index 4(参数)
uintptr_t
ngx_http_script_start_args_code
// index 5()
ngx_http_script_regex_end_code_t
ngx_http_script_regex_end_code
// index 5()
NULL
### regex->lengths
// index 0(捕获位置变量)
ngx_http_script_copy_capture_code_t
code->code = (ngx_http_script_code_pt)
ngx_http_script_copy_capture_len_code;
code->n = 2 * n;
// index 1 (常量)
ngx_http_script_copy_code_t
ngx_http_script_copy_len_code
len
// index 2(变量)
ngx_http_script_var_code_t
ngx_http_script_var_code_t
index
// index 3(参数)
uintptr_t
ngx_http_script_mark_args_code
// index 4
NULL