在上一篇博客的介绍中,我们介绍了 nginx 处理 HTTP 请求的 11 个具体的阶段
HTTP 请求处理的 11 个阶段 -- ngx_http_handler
nginx 静态文件的处理是在 request 11 个处理阶段的 NGX_HTTP_CONTENT_PHASE 阶段进行的
NGX_HTTP_CONTENT_PHASE 是 HTTP 请求处理最重要的一个阶段,这个阶段以后 nginx 生成了 具体的 HTTP 响应
// ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r,
// ngx_http_phase_handler_t *ph)
// NGX_HTTP_CONTENT_PHASE 阶段 checker,生成 HTTP 响应 {{{
ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph)
{
size_t root;
ngx_int_t rc;
ngx_str_t path;
// 在 NGX_HTTP_FIND_CONFIG_PHASE 阶段
// 如果发现 location 有对应的 handler 需要执行
// 则会赋值给 content_handler,在此执行
// 从而就不会去执行其他的 handler 了
if (r->content_handler) {
r->write_event_handler = ngx_http_request_empty_handler;
ngx_http_finalize_request(r, r->content_handler(r));
return NGX_OK;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"content phase: %ui", r->phase_handler);
// 执行各个模块的 handler
//
// 检测并补全默认的 index URI
// ngx_http_index_module ngx_http_index_handler
// 获取通过 index 补全的真实 URI,并获取文件内容
// ngx_http_autoindex_module ngx_http_autoindex_handler
// 对文件进行 gzip 压缩
// ngx_http_gzip_static_module ngx_http_gzip_static_handler
// 设置 response header 和 body,处理静态文件响应
// ngx_http_static_module ngx_http_static_handler 静态文件处理
rc = ph->handler(r);
if (rc != NGX_DECLINED) {
ngx_http_finalize_request(r, rc);
return NGX_OK;
}
/* rc == NGX_DECLINED */
ph++;
if (ph->checker) {
r->phase_handler++;
return NGX_AGAIN;
}
/* no content handler was found */
if (r->uri.data[r->uri.len - 1] == '/') {
if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"directory index of \"%s\" is forbidden", path.data);
}
ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
return NGX_OK;
}
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");
ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
return NGX_OK;
} // }}}
对于静态文件的请求,并不会有 location 去决定他的响应函数,因此会执行对应的 handler
如上面代码中的注释描述,对于静态文件响应 NGX_HTTP_CONTENT_PHASE 阶段主要执行了以下流程:
- 检测并补全默认的 index URI
- 获取通过 index 补全的真实 URI,并获取文件内容
- 对文件进行 gzip 压缩
- 设置 response header 和 body,处理静态文件响应
不难看出,最重要的一步就是设置 response header 和 body,处理静态文件响应了
这一操作是在 ngx_http_static_module 模块中实现的,对应的 handler 是 ngx_http_static_handler
// static ngx_int_t ngx_http_static_handler(ngx_http_request_t *r)
// HTTP 静态文件处理 {{{
static ngx_int_t
ngx_http_static_handler(ngx_http_request_t *r)
{
u_char *last, *location;
size_t root, len;
ngx_str_t path;
ngx_int_t rc;
ngx_uint_t level;
ngx_log_t *log;
ngx_buf_t *b;
ngx_chain_t out;
ngx_open_file_info_t of;
ngx_http_core_loc_conf_t *clcf;
// 限制请求类型
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
return NGX_HTTP_NOT_ALLOWED;
}
// 末尾为 / 说明需要补全、重定向
if (r->uri.data[r->uri.len - 1] == '/') {
return NGX_DECLINED;
}
log = r->connection->log;
/*
* ngx_http_map_uri_to_path() allocates memory for terminating '\0'
* so we do not need to reserve memory for '/' for possible redirect
*/
// 获取 uri 对应的文件路径
last = ngx_http_map_uri_to_path(r, &path, &root, 0);
if (last == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
path.len = last - path.data;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
"http filename: \"%s\"", path.data);
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_memzero(&of, sizeof(ngx_open_file_info_t));
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.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)
{
switch (of.err) {
case 0:
return NGX_HTTP_INTERNAL_SERVER_ERROR;
case NGX_ENOENT:
case NGX_ENOTDIR:
case NGX_ENAMETOOLONG:
level = NGX_LOG_ERR;
rc = NGX_HTTP_NOT_FOUND;
break;
case NGX_EACCES:
#if (NGX_HAVE_OPENAT)
case NGX_EMLINK:
case NGX_ELOOP:
#endif
level = NGX_LOG_ERR;
rc = NGX_HTTP_FORBIDDEN;
break;
default:
level = NGX_LOG_CRIT;
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
break;
}
if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
ngx_log_error(level, log, of.err,
"%s \"%s\" failed", of.failed, path.data);
}
return rc;
}
r->root_tested = !r->error_page;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
if (of.is_dir) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
ngx_http_clear_location(r);
r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t));
if (r->headers_out.location == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
len = r->uri.len + 1;
if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
location = path.data + clcf->root.len;
*last = '/';
} else {
if (r->args.len) {
len += r->args.len + 1;
}
location = ngx_pnalloc(r->pool, len);
if (location == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
last = ngx_copy(location, r->uri.data, r->uri.len);
*last = '/';
if (r->args.len) {
*++last = '?';
ngx_memcpy(++last, r->args.data, r->args.len);
}
}
/*
* we do not need to set the r->headers_out.location->hash and
* r->headers_out.location->key fields
*/
r->headers_out.location->value.len = len;
r->headers_out.location->value.data = location;
return NGX_HTTP_MOVED_PERMANENTLY;
}
#if !(NGX_WIN32) /* the not regular files are probably Unix specific */
if (!of.is_file) {
ngx_log_error(NGX_LOG_CRIT, log, 0,
"\"%s\" is not a regular file", path.data);
return NGX_HTTP_NOT_FOUND;
}
#endif
if (r->method & NGX_HTTP_POST) {
return NGX_HTTP_NOT_ALLOWED;
}
// 处理请求包体并丢弃
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
log->action = "sending response to client";
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = of.size;
r->headers_out.last_modified_time = of.mtime;
if (ngx_http_set_etag(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (ngx_http_set_content_type(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (r != r->main && of.size == 0) {
return ngx_http_send_header(r);
}
r->allow_ranges = 1;
/* we need to allocate all before the header would be sent */
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
if (b->file == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 发送头部
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
b->file_pos = 0;
b->file_last = of.size;
b->in_file = b->file_last ? 1: 0;
b->last_buf = (r == r->main) ? 1: 0;
b->last_in_chain = 1;
b->file->fd = of.fd;
b->file->name = path;
b->file->log = log;
b->file->directio = of.is_directio;
out.buf = b;
out.next = NULL;
// 把产生的内容传递给后续的filter去处理
return ngx_http_output_filter(r, &out);
} // }}}
这个函数首先对请求方式进行了限制,非 GET 或 POST 或 HEAD 请求静态资源会直接返回 405 NOT ALLOWED
然后这个函数通过 URI 与 location 配置获取了实际需要访问的文件,当然了,这里需要处理的一个问题是符号链接,nginx 当然不会忽略他的处理
针对正确获取的文件,nginx 进一步限制了只能通过 GET 或 HEAD 请求,否则同样会返回 405 NOT ALLOWED
这是为什么呢?HTTP 协议的设计中,GET 请求用于从服务器获取资源,POST 请求用于更新服务器资源,对于静态访问的资源,服务器当然只能提供获取而不可能去更新资源,也就意味着 POST 请求是不被允许的
这之后就进行了响应的封装工作,这之中涉及很多 HTTP 协议的细节,可以参考:
HTTP 协议简介
整个过程还是很好理解的,最重要的就是拼装和发送请求的 ngx_http_send_header
// ngx_int_t ngx_http_send_header(ngx_http_request_t *r)
// 发送响应头 {{{
ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
// 已经发送过不再重复发送
if (r->header_sent) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"header already sent");
return NGX_ERROR;
}
if (r->err_status) {
r->headers_out.status = r->err_status;
r->headers_out.status_line.len = 0;
}
// 打包响应头
// 回调函数,由具体的 module 实现
// ngx_http_not_modified_filter_module ngx_http_not_modified_header_filter
// ngx_http_headers_filter_module ngx_http_headers_filter
// ngx_http_userid_filter_module ngx_http_userid_filter
// ngx_http_charset_filter_module ngx_http_charset_header_filter
// ngx_http_ssi_filter_module ngx_http_ssi_header_filter
// ngx_http_gzip_filter_module ngx_http_gzip_header_filter
// ngx_http_range_filter_module ngx_http_range_header_filter
// ngx_http_chunked_filter_module ngx_http_chunked_header_filter
// ngx_http_header_filter_module ngx_http_header_filter
// ngx_http_write_filter_module ngx_http_write_filter
return ngx_http_top_header_filter(r);
} // }}}
这个函数代码并不多,却完成了一个最重要的功能 -- 拼装请求响应并发送响应
ngx_http_top_header_filter 是一个回调函数,由具体的 module 实现,而这个实现了他的 module 仅仅是作为整个工作的入口
在每个 module 的最后都会调用回调函数 ngx_http_next_header_filter 指向下一个 module 的回调函数,由此,实现了全部 filter 的链式调用
直到调用 ngx_http_header_filter 拼装了完整的响应,最后他会调用 ngx_http_write_filter 发送请求包体