静态请求处理 -- ngx_http_static_handler

2015-10-21 23:30:26   最后更新: 2015-10-21 23:33:20   访问数量:770




在上一篇博客的介绍中,我们介绍了 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 阶段主要执行了以下流程:

  1. 检测并补全默认的 index URI
  2. 获取通过 index 补全的真实 URI,并获取文件内容
  3. 对文件进行 gzip 压缩
  4. 设置 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 发送请求包体

 

 

 






技术帖      龙潭书斋      nginx      源码      opensource      sourcecode      http      webserver      开源      source      header      ngx_http_static_handler      http协议      响应     


京ICP备15018585号