Nginx的rewrite模块详解

1.相关指令

1.1 if指令

1
Context: server, location

依据指定的条件决定是否执行 if 块语句中的内容

1.2 break指令

1
Context: server, location, if

停止执行 ngx_http_rewrite_module 的指令集,但是其他模块指令是不受影响的

1.3 rewrite指令

1
2
3
Context: server, location, if

rewrite regex replacement [flag];

rewrite 指令是使用指定的正则表达式regex来匹配请求的urI,如果匹配成功,则使用replacement更改URI。rewrite指令按照它们在配置文件中出现的顺序执行。可以使用flag标志来终止指令的进一步处理。如果替换字符串replacement以http://,https://或$scheme开头,则停止处理后续内容,并直接重定向返回给客户端。

1.4 return指令

1
2
3
4
5
6
Context: server, location, if

return code [text];
return code URL;
return URL;

停止处理并将指定的code码返回给客户端。 非标准code码 444 关闭连接而不发送响应报头

2.源码解析

2.1 rewrite指令源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139

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];

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 */
//解析正则表达式,填写ngx_http_regex_t结构并返回。正则句柄,命名子模式等都在里面了。
regex->regex = ngx_http_regex_compile(cf, &rc);
if (regex->regex == NULL) {
return NGX_CONF_ERROR;
}
//将其设置为第一个code函数,求出目标字符串大小。尾部还有ngx_http_script_regex_end_code
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 = &regex->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;

if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_CONF_ERROR;
}

regex = sc.main;

regex->size = sc.size;
regex->args = sc.args;

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),
&regex);
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;

if (last) {
code = ngx_http_script_add_code(lcf->codes, sizeof(uintptr_t), &regex);
if (code == NULL) {
return NGX_CONF_ERROR;
}

*code = NULL;
}
//下一个解析句柄组的地址。如果匹配失败,则会直接跳过该regex匹配相关的所有code
regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
- (u_char *) regex;

return NGX_CONF_OK;
}

2.2 return指令源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
static char *
ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_rewrite_loc_conf_t *lcf = conf;

u_char *p;
ngx_str_t *value, *v;
ngx_http_script_return_code_t *ret;
ngx_http_compile_complex_value_t ccv;

ret = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_return_code_t));
if (ret == NULL) {
return NGX_CONF_ERROR;
}

value = cf->args->elts;

ngx_memzero(ret, sizeof(ngx_http_script_return_code_t));
//注册code为ngx_http_script_return_code
ret->code = ngx_http_script_return_code;

p = value[1].data;

ret->status = ngx_atoi(p, value[1].len);

if (ret->status == (uintptr_t) NGX_ERROR) {

if (cf->args->nelts == 2
&& (ngx_strncmp(p, "http://", sizeof("http://") - 1) == 0
|| ngx_strncmp(p, "https://", sizeof("https://") - 1) == 0
|| ngx_strncmp(p, "$scheme", sizeof("$scheme") - 1) == 0))
{
ret->status = NGX_HTTP_MOVED_TEMPORARILY;
v = &value[1];

} else {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid return code \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}

} else {

if (ret->status > 999) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid return code \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}

if (cf->args->nelts == 2) {
return NGX_CONF_OK;
}

v = &value[2];
}

ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));

ccv.cf = cf;
ccv.value = v;
ccv.complex_value = &ret->text;

if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}

return NGX_CONF_OK;
}

2.3 break指令源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

static char *
ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_rewrite_loc_conf_t *lcf = conf;

ngx_http_script_code_pt *code;

code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(uintptr_t));
if (code == NULL) {
return NGX_CONF_ERROR;
}

*code = ngx_http_script_break_code;

return NGX_CONF_OK;
}

配合ngx_http_rewrite_handler读代码,可以看到如果设置一个code节点到codes数组,那么在ngx_http_rewrite_handler的for循环执行到该节点code的时候,就会把e->ip置为NULL,这样就直接退出while ((uintptr_t ) e->ip){}循环

2.4 if指令源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

static char *
ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_rewrite_loc_conf_t *lcf = conf;

void *mconf;
char *rv;
u_char *elts;
ngx_uint_t i;
ngx_conf_t save;
ngx_http_module_t *module;
ngx_http_conf_ctx_t *ctx, *pctx;
ngx_http_core_loc_conf_t *clcf, *pclcf;
ngx_http_script_if_code_t *if_code;
ngx_http_rewrite_loc_conf_t *nlcf;

//if的解析过程和location{}解析过程差不多,也有ctx
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}

pctx = cf->ctx; //父块{}的上下文ctx
ctx->main_conf = pctx->main_conf;
ctx->srv_conf = pctx->srv_conf;

ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
if (ctx->loc_conf == NULL) {
return NGX_CONF_ERROR;
}

for (i = 0; ngx_modules[i]; i++) {
if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
continue;
}

module = ngx_modules[i]->ctx;

if (module->create_loc_conf) {
/*
在解析if时, nginx会把它当做一个location来对待的,并且它的location type为noname。通过ngx_http_add_location将该“location”添
加到上层的locations中。这里将if看做location自然有它的合理性,因为if的配置也是需要进行url匹配的。
*/
mconf = module->create_loc_conf(cf);
if (mconf == NULL) {
return NGX_CONF_ERROR;
}

ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
}
}

pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];//该if{}所在location{}的配置信息

clcf = ctx->loc_conf[ngx_http_core_module.ctx_index]; //if{}的配置信息
clcf->loc_conf = ctx->loc_conf;
clcf->name = pclcf->name;
clcf->noname = 1; //if配置被作为location的noname形式

if (ngx_http_add_location(cf, &pclcf->locations, clcf) != NGX_OK) {
return NGX_CONF_ERROR;
}

if (ngx_http_rewrite_if_condition(cf, lcf) != NGX_CONF_OK) {
return NGX_CONF_ERROR;
}

if_code = ngx_array_push_n(lcf->codes, sizeof(ngx_http_script_if_code_t));
if (if_code == NULL) {
return NGX_CONF_ERROR;
}

if_code->code = ngx_http_script_if_code;

elts = lcf->codes->elts;


/* the inner directives must be compiled to the same code array */

nlcf = ctx->loc_conf[ngx_http_rewrite_module.ctx_index];
nlcf->codes = lcf->codes;


save = *cf;
cf->ctx = ctx;

if (pclcf->name.len == 0) {
if_code->loc_conf = NULL;
cf->cmd_type = NGX_HTTP_SIF_CONF;

} else {
if_code->loc_conf = ctx->loc_conf;
cf->cmd_type = NGX_HTTP_LIF_CONF;
}

rv = ngx_conf_parse(cf, NULL);

*cf = save;

if (rv != NGX_CONF_OK) {
return rv;
}


if (elts != lcf->codes->elts) {
if_code = (ngx_http_script_if_code_t *)
((u_char *) if_code + ((u_char *) lcf->codes->elts - elts));
}

if_code->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
- (u_char *) if_code;

/* the code array belong to parent block */

nlcf->codes = NULL;

return NGX_CONF_OK;
}

2.5 set指令源码解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

/*Syntax: set $variable value
1. 将$variable加入到变量系统中,cmcf->variables_keys->keys和cmcf->variables。


a. 如果value是简单字符串,那么解析之后,lcf->codes就会追加这样的到后面:
ngx_http_script_value_code 直接简单字符串指向一下就行,都不用拷贝了。
b. 如果value是复杂的包含变量的串,那么lcf->codes就会追加如下的进去 :
ngx_http_script_complex_value_code 调用lengths的lcode获取组合字符串的总长度,并且申请内存
lengths
values,这里根据表达式的不同而不同。 分别将value代表的复杂表达式拆分成语法单元,进行一个个求值,并合并在一起。
ngx_http_script_set_var_code 负责将上述合并出的最终结果设置到variables[]数组中去。
*/
static char *
ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_rewrite_loc_conf_t *lcf = conf;

ngx_int_t index;
ngx_str_t *value;
ngx_http_variable_t *v;
ngx_http_script_var_code_t *vcode;
ngx_http_script_var_handler_code_t *vhcode;

value = cf->args->elts;

if (value[1].data[0] != '$') {//变量必须以$开头
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid variable name \"%V\"", &value[1]);
return NGX_CONF_ERROR;
}

value[1].len--;
value[1].data++;
//下面根据这个变量名,将其加入到cmcf->variables_keys->keys里面。
v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
if (v == NULL) {
return NGX_CONF_ERROR;
}
//将其加入到cmcf->variables里面,并返回其下标
index = ngx_http_get_variable_index(cf, &value[1]);
if (index == NGX_ERROR) {
return NGX_CONF_ERROR;
}

//set $variable value中的第一个参数$variable对应的在这里或者ngx_http_variables_init_vars设置ngx_http_variable_t的get_handler和data成员
if (v->get_handler == NULL
&& ngx_strncasecmp(value[1].data, (u_char *) "http_", 5) != 0
&& ngx_strncasecmp(value[1].data, (u_char *) "sent_http_", 10) != 0
&& ngx_strncasecmp(value[1].data, (u_char *) "upstream_http_", 14) != 0
&& ngx_strncasecmp(value[1].data, (u_char *) "cookie_", 7) != 0
&& ngx_strncasecmp(value[1].data, (u_char *) "upstream_cookie_", 16)
!= 0
&& ngx_strncasecmp(value[1].data, (u_char *) "arg_", 4) != 0)
//如果变量名称不是以上开头,则其get_handler为ngx_http_rewrite_var,data为index 。
{
//设置一个默认的handler。在ngx_http_variables_init_vars里面其实是会将上面这些"http_" "sent_http_"这些变量get_hendler的
v->get_handler = ngx_http_rewrite_var;
v->data = index;
}

/*
脚本引擎是一系列的凹调函数以及相关数据(它们被组织成ngx_httpscript_ xxx_codet这样的结构体,代表各种不同功能的操
作步骤),被保存在变量lcf->codes数组内,而ngx_httprewrite_loc_conf_t类型变量Icf是与当前location相关联的,所以这个脚本引擎只有
当客户端请求访问当前这个location时才会被启动执行。如下配置中,“set $file t_a;”构建的脚本引擎只有当客户端请求访问/t日录时才会
被触发,如果当客户端请求访问根目录时则与它毫无关系。
location / {
root web;
}
location /t {
set $file t_a;
}
*/
//ngx_http_rewrite_handler中会移除执行lcf->codes数组中的各个ngx_http_script_xxx_code_t->code函数,

//set $variable value的value参数在这里处理 ,

/*
从下面可以看出没set一次就会创建一个ngx_http_script_var_code_t和ngx_http_script_xxx_value_code_t但是如果连续多次设置同样的
变量不同的值,那么就会有多个var_code_t和value_code_t对,实际上在ngx_http_rewrite_handler变量执行的时候,以最后面的为准,例如:
50: location / {
51: root web;
52: set $file indexl.html;
53: index $file;
54:
65: set $file index2.html;
}
上面的例子追踪访问到的是index2.html
*/

/*
如果set $variable value中的value是普通字符串,则下面的ngx_http_rewrite_value从ngx_http_rewrite_loc_conf_t->codes数组中获取ngx_http_script_value_code_t空间,紧接着在后面的
ngx_http_script_start_code函数同样从ngx_http_rewrite_loc_conf_t->codes数组中获取ngx_http_script_var_code_t空间,因此在codes数组中
存放变量值value的ngx_http_script_value_code_t空间与存放var变量名的ngx_http_script_var_code_t在空间上是靠着的,图形化见<深入剖析nginx 图8-4>

如果set $variable value中的value是变量名,则下面的ngx_http_rewrite_value从ngx_http_rewrite_loc_conf_t->codes数组中获取ngx_http_script_complex_value_code_t空间,紧接着在后面的
ngx_http_script_start_code函数同样从ngx_http_rewrite_loc_conf_t->codes数组中获取ngx_http_script_complex_value_code_t空间,因此在codes数组中
存放变量值value的ngx_http_script_value_code_t空间与存放var变量名的ngx_http_script_var_code_t在空间上是靠着的,图形化见<深入剖析nginx 图8-4>
*///
if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) {
return NGX_CONF_ERROR;
}

if (v->set_handler) {
vhcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_var_handler_code_t));
if (vhcode == NULL) {
return NGX_CONF_ERROR;
}

vhcode->code = ngx_http_script_var_set_handler_code;
vhcode->handler = v->set_handler;
vhcode->data = v->data;

return NGX_CONF_OK;
}

vcode = ngx_http_script_start_code(cf->pool, &lcf->codes,
sizeof(ngx_http_script_var_code_t));
if (vcode == NULL) {
return NGX_CONF_ERROR;
}

vcode->code = ngx_http_script_set_var_code;
vcode->index = (uintptr_t) index;

return NGX_CONF_OK;
}

3.总结