Nginx的try_files模块

1.相关指令

1
2
3
4
Syntax:	try_files file ... uri;
try_files file ... =code;
Default: —
Context: server, location

2.配置解析函数

首先看下配置解析函数ngx_http_try_files

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
static char *
ngx_http_try_files(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_try_files_loc_conf_t *tlcf = conf;

ngx_str_t *value;
ngx_int_t code;
ngx_uint_t i, n;
ngx_http_try_file_t *tf;
ngx_http_script_compile_t sc;

//判断是否重复设置
if (tlcf->try_files) {
return "is duplicate";
}

// 申请必要内存,根据参数申请内存
tf = ngx_pcalloc(cf->pool, cf->args->nelts * sizeof(ngx_http_try_file_t));
if (tf == NULL) {
return NGX_CONF_ERROR;
}

tlcf->try_files = tf;

value = cf->args->elts;

// 解析参数
for (i = 0; i < cf->args->nelts - 1; i++) {

tf[i].name = value[i + 1];
// 判断当前参数是否可以作为文件查找的目录,如果是,则将test_dir置位
if (tf[i].name.len > 0
&& tf[i].name.data[tf[i].name.len - 1] == '/'
&& i + 2 < cf->args->nelts)
{
tf[i].test_dir = 1;
tf[i].name.len--;
tf[i].name.data[tf[i].name.len] = '\0';
}

// 计算参数中的变量的个数
n = ngx_http_script_variables_count(&tf[i].name);

// 处理变量
if (n) {
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));

sc.cf = cf;
sc.source = &tf[i].name;
sc.lengths = &tf[i].lengths;
sc.values = &tf[i].values;
sc.variables = n;
sc.complete_lengths = 1;
sc.complete_values = 1;

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

} else {
/* add trailing '\0' to length */
tf[i].name.len++;
}
}
//如果最后一个参数第一个字符是=,则解析最后一个参数的code
if (tf[i - 1].name.data[0] == '=') {

code = ngx_atoi(tf[i - 1].name.data + 1, tf[i - 1].name.len - 2);

if (code == NGX_ERROR || code > 999) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid code \"%*s\"",
tf[i - 1].name.len - 1, tf[i - 1].name.data);
return NGX_CONF_ERROR;
}

tf[i].code = code;
}

return NGX_CONF_OK;
}

filter模块需要在配置解析完成之后,在适当的阶段介入,在ngx_http_try_files_module_ctx中设置
1
2
3
4
5
6
7
8
9
10
11
12
13
static ngx_http_module_t  ngx_http_try_files_module_ctx = {
NULL, /* preconfiguration */
ngx_http_try_files_init, /* postconfiguration */

NULL, /* create main configuration */
NULL, /* init main configuration */

NULL, /* create server configuration */
NULL, /* merge server configuration */

ngx_http_try_files_create_loc_conf, /* create location configuration */
NULL /* merge location configuration */
};

看下ngx_http_try_files_init函数实现,可以看到try_files模块的filter在precontent阶段介入
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static ngx_int_t
ngx_http_try_files_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;

cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

// 从precontent阶段的handler中取一个
h = ngx_array_push(&cmcf->phases[NGX_HTTP_PRECONTENT_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}

*h = ngx_http_try_files_handler;

return NGX_OK;
}


接下来看下ngx_http_try_files_handler模块具体实现
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
static ngx_int_t
ngx_http_try_files_handler(ngx_http_request_t *r)
{
size_t len, root, alias, reserve, allocated;
u_char *p, *name;
ngx_str_t path, args;
ngx_uint_t test_dir;
ngx_http_try_file_t *tf;
ngx_open_file_info_t of;
ngx_http_script_code_pt code;
ngx_http_script_engine_t e;
ngx_http_core_loc_conf_t *clcf;
ngx_http_script_len_code_pt lcode;
ngx_http_try_files_loc_conf_t *tlcf;

tlcf = ngx_http_get_module_loc_conf(r, ngx_http_try_files_module);
//如果没有配置try_file直接返回
if (tlcf->try_files == NULL) {
return NGX_DECLINED;
}

ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"try files handler");

allocated = 0;
root = 0;
name = NULL;
/* suppress MSVC warning */
path.data = NULL;

tf = tlcf->try_files;

clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
// 获取location下配置的alias
alias = clcf->alias;

for ( ;; ) {
// 解析try_files后面的变量
if (tf->lengths) {
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));

e.ip = tf->lengths->elts;
e.request = r;

/* 1 is for terminating '\0' as in static names */
len = 1;

while (*(uintptr_t *) e.ip) {
lcode = *(ngx_http_script_len_code_pt *) e.ip;
len += lcode(&e);
}

} else {
len = tf->name.len;
}
// 如果没有alias,则判断try_files第一个参数长度与uri的长度
if (!alias) {
reserve = len > r->uri.len ? len - r->uri.len : 0;

} else if (alias == NGX_MAX_SIZE_T_VALUE) {
reserve = len;

} else {
reserve = len > r->uri.len - alias ? len - (r->uri.len - alias) : 0;
}

if (reserve > allocated || !allocated) {

/* 16 bytes are preallocation */
allocated = reserve + 16;
// 根据uri映射磁盘文件
if (ngx_http_map_uri_to_path(r, &path, &root, allocated) == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

name = path.data + root;
}

if (tf->values == NULL) {

/* tf->name.len includes the terminating '\0' */

ngx_memcpy(name, tf->name.data, tf->name.len);

path.len = (name + tf->name.len - 1) - path.data;

} else {
e.ip = tf->values->elts;
e.pos = name;
e.flushed = 1;
//处理变量
while (*(uintptr_t *) e.ip) {
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);
}

path.len = e.pos - path.data;

*e.pos = '\0';

if (alias && alias != NGX_MAX_SIZE_T_VALUE
&& ngx_strncmp(name, r->uri.data, alias) == 0)
{
ngx_memmove(name, name + alias, len - alias);
path.len -= alias;
}
}

test_dir = tf->test_dir;
// 指针后移
tf++;

ngx_log_debug3(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"trying to use %s: \"%s\" \"%s\"",
test_dir ? "dir" : "file", name, path.data);

if (tf->lengths == NULL && tf->name.len == 0) {
// 如果最后一个参数code有设置,则直接返回该code
if (tf->code) {
return tf->code;
}

path.len -= root;
path.data += root;
// 如果是@开头的path,则跳转到对应location
if (path.data[0] == '@') {
(void) ngx_http_named_location(r, &path);

} else {
ngx_http_split_args(r, &path, &args);
//内部重定向
(void) ngx_http_internal_redirect(r, &path, &args);
}
// 结束请求
ngx_http_finalize_request(r, NGX_DONE);
return NGX_DONE;
}

ngx_memzero(&of, sizeof(ngx_open_file_info_t));
// 设置cache_file参数
of.read_ahead = clcf->read_ahead;
of.directio = clcf->directio;
of.valid = clcf->open_file_cache_valid;
of.min_uses = clcf->open_file_cache_min_uses;
of.test_only = 1;
of.errors = clcf->open_file_cache_errors;
of.events = clcf->open_file_cache_events;

// 处理软链接
if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 打开文件
if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
!= NGX_OK)
{
if (of.err == 0) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

if (of.err != NGX_ENOENT
&& of.err != NGX_ENOTDIR
&& of.err != NGX_ENAMETOOLONG)
{
ngx_log_error(NGX_LOG_CRIT, r->connection->log, of.err,
"%s \"%s\" failed", of.failed, path.data);
}

continue;
}
// 当前目录不可用于查找文件
if (of.is_dir != test_dir) {
continue;
}

path.len -= root;
path.data += root;

if (!alias) {
r->uri = path;

} else if (alias == NGX_MAX_SIZE_T_VALUE) {
if (!test_dir) {
r->uri = path;
r->add_uri_to_alias = 1;
}

} else {
name = r->uri.data;

r->uri.len = alias + path.len;
r->uri.data = ngx_pnalloc(r->pool, r->uri.len);
if (r->uri.data == NULL) {
r->uri.len = 0;
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}

p = ngx_copy(r->uri.data, name, alias);
// 保存path
ngx_memcpy(p, path.data, path.len);
}
// 设置文件后缀
ngx_http_set_exten(r);

ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"try file uri: \"%V\"", &r->uri);

return NGX_DECLINED;
}

/* not reached */

3.总结

以上就是try_files模块的详解,从指令到handler都进行了一些说明。