11. HTTP 阶段执行

下面会依次执行以下阶段:

  1. NGX_HTTP_SERVER_REWRITE_PHASE: 在将请求的 URI 与 location 表达式匹配前,修改请求的 URI (所谓重定向)是一个独立的 HTTP 阶段。
  2. NGX_HTTP_FIND_CONFIG_PHASE:根据请求的 URI 寻找匹配的 location 表达式,这个阶段只能由 ngx_http_core_module 模块实现,不建议其他模块介入该阶段。
  3. NGX_HTTP_REWRITE_PHASE:在 NGX_HTTP_FIND_CONFIG_PHASE 阶段寻找到匹配的 location 之后再修改请求的 URI。
  4. NGX_HTTP_POST_REWRITE_PHASE:这一阶段是用于在 rewrite 重写 URL 后,防止错误的 nginx.conf 配置导致死循环(递归地修改 URI)。因此,这一阶段仅由 ngx_http_core_module 模块处理。目前,控制死循环的方式很简单,首先检查 rewrite 的次数,如果一个请求超过 10 次重定向,就认为进入了 rewrite 死循环,这时在 NGX_HTTP_POST_REWRITE_PHASE 阶段就会向用户返回 500,表示服务器内部错误.
  5. NGX_HTTP_PREACCESS_PHASE:该阶段表示在处理 NGX_HTTP_ACCESS_PHASE 阶段决定请求的访问权限前,HTTP 模块可以介入的处理阶段。
  6. NGX_HTTP_ACCESS_PHASE:这个阶段用于让 HTTP 模块判断是否允许这个请求访问 Nginx 服务器.
  7. NGX_HTTP_POST_ACCESS_PHASE:在 NGX_HTTP_ACCESS_PAHSE 阶段中,当 HTTP 模块的 handler 处理函数返回不允许访问的错误码时(实际就是 NGX_HTTP_FORBIDDEN 或 NGX_HTTP_AUTHORIZED),这里将负责向用户发送拒绝服务的错误响应。因此,这个阶段实际上用于给 NGX_HTTP_ACCESS_PAHSE 阶段收尾.
  8. NGX_HTTP_CONTENT_PHASE:该阶段用于处理 HTTP 请求内容的阶段,这是大部分 HTTP 模块最愿意介入的阶段。

11.1 NGX_HTTP_SERVER_REWRITE_PHASE

基于当前环境配置下,首先执行的第一个阶段为 NGX_HTTP_SERVER_REWRITE_PHASE,由前面 ngx_http_init_phase_handlers 函数知,该阶段实现了 checker 回调函数 ngx_http_core_rewrite_phase。因此会首先调用该 checker 函数,在 checker 函数中才会去调用 handler 方法。

ngx_http_core_rewrite_phase 源码如下:

ngx_int_t
ngx_http_core_rewrite_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
ngx_int_t rc; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"rewrite phase: %ui", r->phase_handler); rc = ph->handler(r); /* 当前的 handler 执行完毕,按照顺序执行下一个 ngx_http_handler_pt 处理方法 */
if (rc == NGX_DECLINED) {
r->phase_handler++;
return NGX_AGAIN;
} /* 当前 ngx_http_handler_pt 处理方法尚未结束,这意味着该处理方法在当前阶段
* 中有机会再次被调用,这里返回 NGX_OK,会使得 HTTP 框架立刻把控制权交还给
* epoll 等事件模块,不再处理当前请求,唯有这个请求上的事件再次被触发时才会
* 继续执行 */
if (rc == NGX_DONE) {
return NGX_OK;
} /* NGX_OK, NGX_AGAIN, NGX_ERROR, NGX_HTTP_... */ ngx_http_finalize_request(r, rc); return NGX_OK;
}

对于 NGX_HTTP_SERVER_REWRITE_PHASE 阶段,当前 HTTP 框架仅有 ngx_http_rewrite_module 模块介入了该阶段,handler 函数为 ngx_http_rewrite_handler。

11.1.1 ngx_http_rewrite_handler

static ngx_int_t
ngx_http_rewrite_handler(ngx_http_request_t *r)
{
ngx_int_t index;
ngx_http_script_code_pt code;
ngx_http_script_engine_t *e;
ngx_http_core_srv_conf_t *cscf;
ngx_http_core_main_conf_t *cmcf;
ngx_http_rewrite_loc_conf_t *rlcf; cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
/*
* 表示 NGX_HTTP_REWRITE_PHASE 阶段第 1 个 ngx_http_phase_handler_t 处理方法
* 在 handlers 数组中的序号,用于在执行 HTTP 请求的任何阶段中快速跳转到
* NGX_HTTP_REWRITE_PHASE 阶段处理请求
*/
index = cmcf->phase_engine.location_rewrite_index; if (r->phase_handler == index && r->loc_conf == cscf->ctx->loc_conf) {
/* skipping location rewrite phase for server null location */
return NGX_DECLINED;
} rlcf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module); /* 若没有在配置文件中对该模块的相应配置项进行设置,则这里直接返回
* NGX_DECLINED,表示该 handler 执行完毕,依次指向下一个 handler */
if (rlcf->codes == NULL) {
return NGX_DECLINED;
} e = ngx_pcalloc(r->pool, sizeof(ngx_http_script_engine_t));
if (e == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
} e->sp = ngx_pcalloc(r->pool,
rlcf->stack_size * sizeof(ngx_http_variable_value_t));
if (e->sp == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
} e->ip = rlcf->codes->elts;
e->request = r;
e->quote = 1;
e->log = rlcf->log;
e->status = NGX_DECLINED; while (*(uintptr_t *) e->ip) {
code = *(ngx_http_script_code_pt *) e->ip;
code(e);
} if (e->status < NGX_HTTP_BAD_REQUEST) {
return e->status;
} if (r->err_status == 0) {
return e->status;
} return r->err_status;
}

11.2 NGX_HTTP_FIND_CONFIG_PHASE

该阶段是:根据请求的 URI 寻找匹配的 location 表达式,这个阶段只能由 ngx_http_core_module 模块实现,不建议其他模块介入该阶段

该阶段实现的 checker 方法为 ngx_http_core_find_config_phase。

ngx_int_t
ngx_http_core_find_config_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph)
{
u_char *p;
size_t len;
ngx_int_t rc;
ngx_http_core_loc_conf_t *clcf; /* content_handler:
* 表示 NGX_HTTP_CONTENT_PHASE 阶段提供给 HTTP 模块处理请求的一种方式,
* content_handler 指向 HTTP 模块实现的请求处理方法 */
r->content_handler = NULL;
/* 标志位,为 1 时表示 URL 发生过 rewrite 重写 */
r->uri_changed = 0; rc = ngx_http_core_find_location(r); if (rc == NGX_ERROR) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_OK;
} clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!r->internal && clcf->internal) {
ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
return NGX_OK;
} ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"using configuration \"%s%V\"",
(clcf->noname ? "*" : (clcf->exact_match ? "=" : "")),
&clcf->name); ngx_http_update_location_config(r); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http cl:%O max:%O",
r->headers_in.content_length_n, clcf->client_max_body_size); if (r->headers_in.content_length_n != -1
&& !r->discard_body
&& clcf->client_max_body_size
&& clcf->client_max_body_size < r->headers_in.content_length_n)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"client intended to send too large body: %O bytes",
r->headers_in.content_length_n); r->expect_tested = 1;
(void) ngx_http_discard_request_body(r);
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_ENTITY_TOO_LARGE);
return NGX_OK;
} if (rc == NGX_DONE) {
ngx_http_clear_location(r); r->headers_out.location = ngx_list_push(&r->headers_out.headers);
if (r->headers_out.location == NULL) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_OK;
} r->headers_out.location->hash = 1;
ngx_str_set(&r->headers_out.location->key, "Location"); if (r->args.len == 0) {
r->headers_out.location->value = clcf->name; } else {
len = clcf->name.len + 1 + r->args.len;
p = ngx_pnalloc(r->pool, len); if (p == NULL) {
ngx_http_clear_location(r);
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_OK;
} r->headers_out.location->value.len = len;
r->headers_out.location->value.data = p; p = ngx_cpymem(p, clcf->name.data, clcf->name.len);
*p++ = '?';
ngx_memcpy(p, r->args.data, r->args.len);
} ngx_http_finalize_request(r, NGX_HTTP_MOVED_PERMANENTLY);
return NGX_OK;
} r->phase_handler++;
return NGX_AGAIN;
}

11.2.1 ngx_http_core_find_location

/*
* NGX_OK - exact or regex match
* NGX_DONE - auto redirect
* NGX_AGAIN - inclusive match
* NGX_ERROR - regex error
* NGX_DECLINED - no match
*/ static ngx_int_t
ngx_http_core_find_location(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_http_core_loc_conf_t *pclcf;
#if (NGX_PCRE)
ngx_int_t n;
ngx_uint_t noregex;
ngx_http_core_loc_conf_t *clcf, **clcfp; noregex = 0;
#endif pclcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); rc = ngx_http_core_find_static_location(r, pclcf->static_locations); if (rc == NGX_AGAIN) { #if (NGX_PCRE)
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); noregex = clcf->noregex;
#endif /* look up nested locations */ rc = ngx_http_core_find_location(r);
} if (rc == NGX_OK || rc == NGX_DONE) {
return rc;
} /* rc == NGX_DECLINED or rc == NGX_AGAIN in nested location */ #if (NGX_PCRE) if (noregex == 0 && pclcf->regex_locations) { for (clcfp = pclcf->regex_locations; *clcfp; clcfp++) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"test location: ~ \"%V\"", &(*clcfp)->name); n = ngx_http_regex_exec(r, (*clcfp)->regex, &r->uri); if (n == NGX_OK) {
r->loc_conf = (*clcfp)->loc_conf; /* look up nested locations */ rc = ngx_http_core_find_location(r); return (rc == NGX_ERROR) ? rc : NGX_OK;
} if (n == NGX_DECLINED) {
continue;
} return NGX_ERROR;
}
}
#endif return rc;
}

11.2.2 ngx_http_core_find_static_location

/*
* NGX_OK - exact match
* NGX_DONE - auto redirect
* NGX_AGAIN - inclusive match
* NGX_DECLINED - no match
*/ static ngx_int_t
ngx_http_core_find_static_location(ngx_http_request_t *r,
ngx_http_location_tree_node_t *node)
{
u_char *uri;
size_t len, n;
ngx_int_t rc, rv; len = r->uri.len;
uri = r->uri.data; rv = NGX_DECLINED; for ( ;; ) { if (node == NULL) {
return rv;
} ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"test location: \"%*s\"",
(size_t) node->len, node->name); /* 以最小的长度为准 */
n = (len <= (size_t) node->len) ? len : node->len; /* 比较这两个字符串的前 n 个字符是否相同,相同返回 0 */
rc = ngx_filename_cmp(uri, node->name, n); if (rc != 0) {
/* 当前节点不匹配 */
node = (rc < 0) ? node->left : node->right; continue;
} if (len > (size_t) node->len) { if (node->inclusive) { r->loc_conf = node->inclusive->loc_conf;
rv = NGX_AGAIN; node = node->tree;
uri += n;
len -= n; continue;
} /* exact only */ node = node->right; continue;
} if (len == (size_t) node->len) { if (node->exact) {
r->loc_conf = node->exact->loc_conf;
return NGX_OK; } else {
r->loc_conf = node->inclusive->loc_conf;
return NGX_AGAIN;
}
} /* len < node->len */ if (len + 1 == (size_t) node->len && node->auto_redirect) { r->loc_conf = (node->exact) ? node->exact->loc_conf:
node->inclusive->loc_conf;
rv = NGX_DONE;
} node = node->left;
}
}

11.3 NGX_HTTP_REWRITE_PHASE

该阶段是:在 NGX_HTTP_FIND_CONFIG_PHASE 阶段寻找到匹配的 location 之后再修改请求的 URI。

该阶段实现的 checker 方法为 ngx_http_core_rewrite_phase,介入该阶段的模块也仅为 ngx_http_rewrite_module,handler 为 ngx_http_rewrite_handler。因此与 NGX_HTTP_SERVER_REWRITE_PHASE 阶段一样。具体可看该阶段的实现。

11.4 NGX_HTTP_POST_REWRITE_PHASE

这一阶段是用于在 rewrite 重写 URL 后,防止错误的 nginx.conf 配置导致死循环(递归地修改 URI)。因此,这一阶段仅由 ngx_http_core_module 模块处理。目前,控制死循环的方式很简单,首先检查 rewrite 的次数,如果一个请求超过 10 次重定向,就认为进入了 rewrite 死循环,这时在 NGX_HTTP_POST_REWRITE_PHASE 阶段就会向用户返回 500,表示服务器内部错误.

该阶段实现的 checker 方法为 ngx_http_core_post_rewrite_phase(不允许其他模块介入):

ngx_int_t
ngx_http_core_post_rewrite_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph)
{
ngx_http_core_srv_conf_t *cscf; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"post rewrite phase: %ui", r->phase_handler); /* 标志位,为 1 表示 URL 发生过 rewrite 重写 */
if (!r->uri_changed) {
/* 若没有发生过 rewrite 重写,phase_handler 加 1,按序执行下一个 handler */
r->phase_handler++;
return NGX_AGAIN;
} ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"uri changes: %d", r->uri_changes); /*
* gcc before 3.3 compiles the broken code for
* if (r->uri_changes-- == 0)
* if the r->uri_changes is defined as
* unsigned uri_changes:4
*/ r->uri_changes--; if (r->uri_changes == 0) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"rewrite or internal redirection cycle "
"while processing \"%V\"", &r->uri); ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return NGX_OK;
} r->phase_handler = ph->next; cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
r->loc_conf = cscf->ctx->loc_conf; return NGX_AGAIN;
}

11.5 NGX_HTTP_PREACCESS_PHASE

该阶段表示在处理 NGX_HTTP_ACCESS_PHASE 阶段决定请求的访问权限前,HTTP 模块可以介入的处理阶段。

该阶段默认的 checker 方法为 ngx_http_core_generic_phase,该阶段分别有以下两个模块介入:

  • ngx_http_limit_req_module: handler 为 ngx_http_limit_req_handler
  • ngx_http_limit_conn_module: handler 为 ngx_http_limit_conn_handler

以下两个模块需要通过指定添加到 nginx 中才会在 ngx_modules 中有这里两个模块,否则默认无:

  • ngx_http_degradation_module: handler 为 ngx_http_degradation_handler
  • ngx_http_realip_module:handler 为 ngx_http_realip_handler

ngx_http_core_generic_phase 的实现如下:

ngx_int_t
ngx_http_core_generic_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
ngx_int_t rc; /*
* generic phase checker,
* used by the post read and pre-access phases
*/ ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"generic phase: %ui", r->phase_handler); rc = ph->handler(r); if (rc == NGX_OK) {
r->phase_handler = ph->next;
return NGX_AGAIN;
} if (rc == NGX_DECLINED) {
r->phase_handler++;
return NGX_AGAIN;
} if (rc == NGX_AGAIN || rc == NGX_DONE) {
return NGX_OK;
} /* rc == NGX_ERROR || rc == NGX_HTTP_... */ ngx_http_finalize_request(r, rc); return NGX_OK;
}

11.5.1 ngx_http_limit_req_handler

static ngx_int_t
ngx_http_limit_req_handler(ngx_http_request_t *r)
{
uint32_t hash;
ngx_str_t key;
ngx_int_t rc;
ngx_uint_t n, excess;
ngx_msec_t delay;
ngx_http_limit_req_ctx_t *ctx;
ngx_http_limit_req_conf_t *lrcf;
ngx_http_limit_req_limit_t *limit, *limits; if (r->main->limit_req_set) {
return NGX_DECLINED;
} lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
limits = lrcf->limits.elts; excess = 0; rc = NGX_DECLINED; #if (NGX_SUPPRESS_WARN)
limit = NULL;
#endif for (n = 0; n < lrcf->limits.nelts; n++) { ...
} if (rc == NGX_DECLINED) {
return NGX_DECLINED;
} ...
}

对于当前配置文件,没有对该 ngx_http_limit_req_module 模块进行设置,因此该函数中会直接返回,不做其他处理。

11.5.2 ngx_http_limit_conn_handler

static ngx_int_t
ngx_stream_limit_conn_handler(ngx_stream_session_t *s)
{
size_t n;
uint32_t hash;
ngx_str_t key;
ngx_uint_t i;
ngx_slab_pool_t *shpool;
ngx_rbtree_node_t *node;
ngx_pool_cleanup_t *cln;
ngx_stream_limit_conn_ctx_t *ctx;
ngx_stream_limit_conn_node_t *lc;
ngx_stream_limit_conn_conf_t *lccf;
ngx_stream_limit_conn_limit_t *limits;
ngx_stream_limit_conn_cleanup_t *lccln; lccf = ngx_stream_get_module_srv_conf(s, ngx_stream_limit_conn_module);
limits = lccf->limits.elts; for (i = 0; i < lccf->limits.nelts; i++) {
...
} return NGX_DECLINED;
}

该模块 ngx_http_limit_conn_module 没有进行相应配置,暂时忽略。

11.6 NGX_HTTP_ACCESS_PHASE

这个阶段用于让 HTTP 模块判断是否允许这个请求访问 Nginx 服务器.

该阶段实现的 checker 方法为 ngx_http_core_access_phase,介入的模块有:

  • ngx_http_access_module:handler 为 ngx_http_access_handler
  • ngx_http_auth_basic_module:handler 为 ngx_http_auth_basic_handler

该模块没有指定添加到 nginx 中进行:

  • ngx_http_auth_request_module:handler 为 ngx_http_auth_request_handler

ngx_http_core_access_phase 的实现如下:

ngx_int_t
ngx_http_core_access_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
ngx_int_t rc;
ngx_http_core_loc_conf_t *clcf; /* 若当前请求 r 为子请求的话,直接返回 */
if (r != r->main) {
r->phase_handler = ph->next;
return NGX_AGAIN;
} ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"access phase: %ui", r->phase_handler); rc = ph->handler(r); if (rc == NGX_DECLINED) {
r->phase_handler++;
return NGX_AGAIN;
} if (rc == NGX_AGAIN || rc == NGX_DONE) {
return NGX_OK;
} clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->satisfy == NGX_HTTP_SATISFY_ALL) { if (rc == NGX_OK) {
r->phase_handler++;
return NGX_AGAIN;
} } else {
if (rc == NGX_OK) {
r->access_code = 0; if (r->headers_out.www_authenticate) {
r->headers_out.www_authenticate->hash = 0;
} r->phase_handler = ph->next;
return NGX_AGAIN;
} if (rc == NGX_HTTP_FORBIDDEN || rc == NGX_HTTP_UNAUTHORIZED) {
if (r->access_code != NGX_HTTP_UNAUTHORIZED) {
r->access_code = rc;
} r->phase_handler++;
return NGX_AGAIN;
}
} /* rc == NGX_ERROR || rc == NGX_HTTP_... */ ngx_http_finalize_request(r, rc);
return NGX_OK;
}

11.6.1 ngx_http_access_handler

该 ngx_http_access_module 同样没有进行相应的配置,因此暂时不分析。

11.6.2 ngx_http_auth_basic_module

没有配置,暂不分析.

11.7 NGX_HTTP_POST_ACCESS_PHASE

在 NGX_HTTP_ACCESS_PAHSE 阶段中,当 HTTP 模块的 handler 处理函数返回不允许访问的错误码时(实际就是 NGX_HTTP_FORBIDDEN 或 NGX_HTTP_AUTHORIZED),这里将负责向用户发送拒绝服务的错误响应。因此,这个阶段实际上用于给 NGX_HTTP_ACCESS_PAHSE 阶段收尾.

该阶段实现的 checker 方法为 ngx_http_core_post_access_phase,且没有 handler 方法,即不允许其他 HTTP 模块介入。

ngx_int_t
ngx_http_core_post_access_phase(ngx_http_request_t *r,
ngx_http_phase_handler_t *ph)
{
ngx_int_t access_code; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"post access phase: %ui", r->phase_handler); access_code = r->access_code; if (access_code) {
if (access_code == NGX_HTTP_FORBIDDEN) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"access forbidden by rule");
} r->access_code = 0;
ngx_http_finalize_request(r, access_code);
return NGX_OK;
} r->phase_handler++;
return NGX_AGAIN;
}

注:由于没有配置 try_files 配置项,因此直接跳过 NGX_HTTP_TRY_FILES_PHASE 阶段。

11.8 NGX_HTTP_CONTENT_PHASE

该阶段用于处理 HTTP 请求内容的阶段,这是大部分 HTTP 模块最愿意介入的阶段。

该阶段实现的 checker 方法为 ngx_http_core_content_phase,介入的模块有:

  • ngx_http_index_module:handler 为 ngx_http_index_handler
  • ngx_http_autoindex_module: handler 为 ngx_http_autoindex_handler
  • ngx_http_dav_module:handler 为 ngx_http_dav_handler
  • ngx_http_gzip_static_module:handler 为 ngx_http_gzip_static_handler
  • ngx_http_random_index_module:handler 为 ngx_http_random_index_handler
  • ngx_http_static_module:handler ngx_http_static_handler

ngx_http_core_content_phase 的实现如下:

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; 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); 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;
}

11.8.1 ngx_http_index_handler

/*
* Try to open/test the first index file before the test of directory
* existence because valid requests should prevail over invalid ones.
* If open()/stat() of a file will fail then stat() of a directory
* should be faster because kernel may have already cached some data.
* Besides, Win32 may return ERROR_PATH_NOT_FOUND (NGX_ENOTDIR) at once.
* Unix has ENOTDIR error; however, it's less helpful than Win32's one:
* is only indicates that path points to a regular file, not a directory.
*/
static ngx_int_t
ngx_http_index_handler(ngx_http_request_t *r)
{
... /* 因为当前请求 uri 的最后一个字符不为 '/',直接返回 */
if (r->uri.data[r->uri.len - 1] != '/') {
return NGX_DECLINED;
} ...
}

11.8.2 ngx_http_autoindex_handler

static ngx_int_t
ngx_http_autoindex_handler(ngx_http_request_t *r)
{
... /* 由于当前请求 uri 的最后一个字节不为 '/',因此直接返回 */
if (r->uri.data[r->uri.len - 1] != '/') {
return NGX_DECLINED;
} ...
}

11.8.3 ngx_http_static_handler

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
*/ /* 根据所访问文件的根目录和锁请求访问的静态文件,得出该文件的
* 绝对路径,保存在 path 中 */
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_list_push(&r->headers_out.headers);
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) {
ngx_http_clear_location(r);
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);
}
} r->headers_out.location->hash = 1;
ngx_str_set(&r->headers_out.location->key, "Location");
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;
} /* 丢弃 HTTP 的请求包体,若本来就没有,则直接返回 */
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;
/* 该文件上一次修改时间(Unix时间戳) */
r->headers_out.last_modified_time = of.mtime; /* 添加一个 ETag 响应头,ETag 对应实体内容的一个实体标签,与实体
* 内容紧密相关,实体内容发生任何变化都会使这个头的值发生变化 */
if (ngx_http_set_etag(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
} /* 实体头 Content-Type:服务器在返回内容时需要告诉浏览器本响应的内容是什么
* 类型。HTTP 中把这种不同媒体类型的格式称为多媒体文件格式(MIME),而本实体
* 头指出所传输实体内容的 MIME。由于 Web 服务器不知道所返回的的内容文件是
* 哪种 MIME,所以需要通过对 Web 服务器进行设置,使文件扩展名与 MIME 之间进行
* 映射。如 : Content-Type: text/html; */
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_calloc_buf(r->pool);
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;
} /* 该函数主要是调用 ngx_http_top_header_filter 链表中的函数,
* 构造响应消息的消息报头,写入到 r->out 链表中 */
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;
/* 若当前请求为父请求,则表明这个最后一个 buf 了 */
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; /* 接下来调用 ngx_http_top_body_filter 链表中的函数构造响应消息的响应正文 */
return ngx_http_output_filter(r, &out);
}

11.8.3.1 ngx_http_map_uri_to_path

u_char *
ngx_http_map_uri_to_path(ngx_http_request_t *r, ngx_str_t *path,
size_t *root_length, size_t reserved)
{
u_char *last;
size_t alias;
ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); alias = clcf->alias; if (alias && !r->valid_location) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"\"alias\" cannot be used in location \"%V\" "
"where URI was rewritten", &clcf->name);
return NULL;
} if (clcf->root_lengths == NULL) { /* 根目录路径的长度 */
*root_length = clcf->root.len; /* 计算根目录的路径加上客户当访问的文件的长度 */
path->len = clcf->root.len + reserved + r->uri.len - alias + 1; path->data = ngx_pnalloc(r->pool, path->len);
if (path->data == NULL) {
return NULL;
} /* 将 clcf->root.data 中保存的静态文件的根目录拷贝到 path->data 中 */
last = ngx_copy(path->data, clcf->root.data, clcf->root.len); } else { if (alias == NGX_MAX_SIZE_T_VALUE) {
reserved += r->add_uri_to_alias ? r->uri.len + 1 : 1; } else {
reserved += r->uri.len - alias + 1;
} if (ngx_http_script_run(r, path, clcf->root_lengths->elts, reserved,
clcf->root_values->elts)
== NULL)
{
return NULL;
} if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, path)
!= NGX_OK)
{
return NULL;
} *root_length = path->len - reserved;
last = path->data + *root_length; if (alias == NGX_MAX_SIZE_T_VALUE) {
if (!r->add_uri_to_alias) {
*last = '\0';
return last;
} alias = 0;
}
} /* 若没有别名的话,这里拷贝的 last 为空字符串 */
last = ngx_cpystrn(last, r->uri.data + alias, r->uri.len - alias + 1); return last;
}

11.8.3.2 ngx_open_cached_file

ngx_int_t
ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name,
ngx_open_file_info_t *of, ngx_pool_t *pool)
{
time_t now;
uint32_t hash;
ngx_int_t rc;
ngx_file_info_t fi;
ngx_pool_cleanup_t *cln;
ngx_cached_open_file_t *file;
ngx_pool_cleanup_file_t *clnf;
ngx_open_file_cache_cleanup_t *ofcln; of->fd = NGX_INVALID_FILE;
of->err = 0; if (cache == NULL) { if (of->test_only) { if (ngx_file_info_wrapper(name, of, &fi, pool->log)
== NGX_FILE_ERROR)
{
return NGX_ERROR;
} of->uniq = ngx_file_uniq(&fi);
of->mtime = ngx_file_mtime(&fi);
of->size = ngx_file_size(&fi);
of->fs_size = ngx_file_fs_size(&fi);
of->is_dir = ngx_is_dir(&fi);
of->is_file = ngx_is_file(&fi);
of->is_link = ngx_is_link(&fi);
of->is_exec = ngx_is_exec(&fi); return NGX_OK;
} /* 该接口用于对内存与其他资源文件的管理,从内存池里申请一块内存时,
* 可能外部会附加一些其他资源(如打开的文件),这些资源的使用和申请的
* 内存是绑定在一起的,那么在进行资源释放的时候,希望这些资源的释放
* 能和内存池释放一起进行(通过 handler 回调函数),既能避免无意的
* 资源泄露,又省得单独执行资源释放的麻烦. */
cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
if (cln == NULL) {
return NGX_ERROR;
} /* 打开该文件并获取该文件的信息 */
rc = ngx_open_and_stat_file(name, of, pool->log); /* 若该文件不是一个目录 */
if (rc == NGX_OK && !of->is_dir) {
/* 设置当释放该资源时,调用的回调函数 */
cln->handler = ngx_pool_cleanup_file;
clnf = cln->data; /* 该文件的打开描述符 */
clnf->fd = of->fd;
/* 客户端请求的文件的绝对路径名 */
clnf->name = name->data;
clnf->log = pool->log;
} return rc;
} cln = ngx_pool_cleanup_add(pool, sizeof(ngx_open_file_cache_cleanup_t));
if (cln == NULL) {
return NGX_ERROR;
} now = ngx_time(); hash = ngx_crc32_long(name->data, name->len); file = ngx_open_file_lookup(cache, name, hash); if (file) { file->uses++; ngx_queue_remove(&file->queue); if (file->fd == NGX_INVALID_FILE && file->err == 0 && !file->is_dir) { /* file was not used often enough to keep open */ rc = ngx_open_and_stat_file(name, of, pool->log); if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
goto failed;
} goto add_event;
} if (file->use_event
|| (file->event == NULL
&& (of->uniq == 0 || of->uniq == file->uniq)
&& now - file->created < of->valid
#if (NGX_HAVE_OPENAT)
&& of->disable_symlinks == file->disable_symlinks
&& of->disable_symlinks_from == file->disable_symlinks_from
#endif
))
{
if (file->err == 0) { of->fd = file->fd;
of->uniq = file->uniq;
of->mtime = file->mtime;
of->size = file->size; of->is_dir = file->is_dir;
of->is_file = file->is_file;
of->is_link = file->is_link;
of->is_exec = file->is_exec;
of->is_directio = file->is_directio; if (!file->is_dir) {
file->count++;
ngx_open_file_add_event(cache, file, of, pool->log);
} } else {
of->err = file->err;
#if (NGX_HAVE_OPENAT)
of->failed = file->disable_symlinks ? ngx_openat_file_n
: ngx_open_file_n;
#else
of->failed = ngx_open_file_n;
#endif
} goto found;
} ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0,
"retest open file: %s, fd:%d, c:%d, e:%d",
file->name, file->fd, file->count, file->err); if (file->is_dir) { /*
* chances that directory became file are very small
* so test_dir flag allows to use a single syscall
* in ngx_file_info() instead of three syscalls
*/ of->test_dir = 1;
} of->fd = file->fd;
of->uniq = file->uniq; rc = ngx_open_and_stat_file(name, of, pool->log); if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
goto failed;
} if (of->is_dir) { if (file->is_dir || file->err) {
goto update;
} /* file became directory */ } else if (of->err == 0) { /* file */ if (file->is_dir || file->err) {
goto add_event;
} if (of->uniq == file->uniq) { if (file->event) {
file->use_event = 1;
} of->is_directio = file->is_directio; goto update;
} /* file was changed */ } else { /* error to cache */ if (file->err || file->is_dir) {
goto update;
} /* file was removed, etc. */
} if (file->count == 0) { ngx_open_file_del_event(file); if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
ngx_close_file_n " \"%V\" failed", name);
} goto add_event;
} ngx_rbtree_delete(&cache->rbtree, &file->node); cache->current--; file->close = 1; goto create;
} /* not found */ rc = ngx_open_and_stat_file(name, of, pool->log); if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
goto failed;
} create: if (cache->current >= cache->max) {
ngx_expire_old_cached_files(cache, 0, pool->log);
} file = ngx_alloc(sizeof(ngx_cached_open_file_t), pool->log); if (file == NULL) {
goto failed;
} file->name = ngx_alloc(name->len + 1, pool->log); if (file->name == NULL) {
ngx_free(file);
file = NULL;
goto failed;
} ngx_cpystrn(file->name, name->data, name->len + 1); file->node.key = hash; ngx_rbtree_insert(&cache->rbtree, &file->node); cache->current++; file->uses = 1;
file->count = 0;
file->use_event = 0;
file->event = NULL; add_event: ngx_open_file_add_event(cache, file, of, pool->log); update: file->fd = of->fd;
file->err = of->err;
#if (NGX_HAVE_OPENAT)
file->disable_symlinks = of->disable_symlinks;
file->disable_symlinks_from = of->disable_symlinks_from;
#endif if (of->err == 0) {
file->uniq = of->uniq;
file->mtime = of->mtime;
file->size = of->size; file->close = 0; file->is_dir = of->is_dir;
file->is_file = of->is_file;
file->is_link = of->is_link;
file->is_exec = of->is_exec;
file->is_directio = of->is_directio; if (!of->is_dir) {
file->count++;
}
} file->created = now; found: file->accessed = now; ngx_queue_insert_head(&cache->expire_queue, &file->queue); ngx_log_debug5(NGX_LOG_DEBUG_CORE, pool->log, 0,
"cached open file: %s, fd:%d, c:%d, e:%d, u:%d",
file->name, file->fd, file->count, file->err, file->uses); if (of->err == 0) { if (!of->is_dir) {
cln->handler = ngx_open_file_cleanup;
ofcln = cln->data; ofcln->cache = cache;
ofcln->file = file;
ofcln->min_uses = of->min_uses;
ofcln->log = pool->log;
} return NGX_OK;
} return NGX_ERROR; failed: if (file) {
ngx_rbtree_delete(&cache->rbtree, &file->node); cache->current--; if (file->count == 0) { if (file->fd != NGX_INVALID_FILE) {
if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
ngx_close_file_n " \"%s\" failed",
file->name);
}
} ngx_free(file->name);
ngx_free(file); } else {
file->close = 1;
}
} if (of->fd != NGX_INVALID_FILE) {
if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
ngx_close_file_n " \"%V\" failed", name);
}
} return NGX_ERROR;
}

11.8.3.3 ngx_pool_cleanup_add

ngx_pool_cleanup_t *
ngx_pool_cleanup_add(ngx_pool_t *p, size_t size)
{
ngx_pool_cleanup_t *c; c = ngx_palloc(p, sizeof(ngx_pool_cleanup_t));
if (c == NULL) {
return NULL;
} if (size) {
c->data = ngx_palloc(p, size);
if (c->data == NULL) {
return NULL;
} } else {
c->data = NULL;
} c->handler = NULL;
c->next = p->cleanup; p->cleanup = c; ngx_log_debug1(NGX_LOG_DEBUG_ALLOC, p->log, 0, "add cleanup: %p", c); return c;
}

11.8.3.4 ngx_open_and_stat_file

static ngx_int_t
ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of,
ngx_log_t *log)
{
ngx_fd_t fd;
ngx_file_info_t fi; /* 若该文件已经打开了 */
if (of->fd != NGX_INVALID_FILE) { if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) {
of->fd = NGX_INVALID_FILE;
return NGX_ERROR;
} if (of->uniq == ngx_file_uniq(&fi)) {
goto done;
} } else if (of->test_dir) { if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) {
of->fd = NGX_INVALID_FILE;
return NGX_ERROR;
} if (ngx_is_dir(&fi)) {
goto done;
}
} if (!of->log) { /*
* Use non-blocking open() not to hang on FIFO files, etc.
* This flag has no effect on a regular files.
*/ /* 使用只读且为非阻塞方法打开该文件 */
fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
NGX_FILE_OPEN, 0, log); } else {
fd = ngx_open_file_wrapper(name, of, NGX_FILE_APPEND,
NGX_FILE_CREATE_OR_OPEN,
NGX_FILE_DEFAULT_ACCESS, log);
} if (fd == NGX_INVALID_FILE) {
of->fd = NGX_INVALID_FILE;
return NGX_ERROR;
} /* 调用 fstat 获取该文件的信息 */
if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
ngx_fd_info_n " \"%V\" failed", name); if (ngx_close_file(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
ngx_close_file_n " \"%V\" failed", name);
} of->fd = NGX_INVALID_FILE; return NGX_ERROR;
} /* 检测是否是一个目录 */
if (ngx_is_dir(&fi)) {
if (ngx_close_file(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
ngx_close_file_n " \"%V\" failed", name);
} of->fd = NGX_INVALID_FILE; } else {
/* 否则为一个普通的文件 */
of->fd = fd; if (of->read_ahead && ngx_file_size(&fi) > NGX_MIN_READ_AHEAD) {
if (ngx_read_ahead(fd, of->read_ahead) == NGX_ERROR) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
ngx_read_ahead_n " \"%V\" failed", name);
}
} if (of->directio <= ngx_file_size(&fi)) {
if (ngx_directio_on(fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
ngx_directio_on_n " \"%V\" failed", name); } else {
of->is_directio = 1;
}
}
} done: /* 根据上面 stat 返回的文件信息初始化该文件 */
of->uniq = ngx_file_uniq(&fi);
of->mtime = ngx_file_mtime(&fi);
of->size = ngx_file_size(&fi);
of->fs_size = ngx_file_fs_size(&fi);
of->is_dir = ngx_is_dir(&fi);
of->is_file = ngx_is_file(&fi);
of->is_link = ngx_is_link(&fi);
of->is_exec = ngx_is_exec(&fi); return NGX_OK;
}

11.8.3.5 ngx_open_file_wrapper

static ngx_fd_t
ngx_open_file_wrapper(ngx_str_t *name, ngx_open_file_info_t *of,
ngx_int_t mode, ngx_int_t create, ngx_int_t access, ngx_log_t *log)
{
ngx_fd_t fd; #if !(NGX_HAVE_OPENAT) fd = ngx_open_file(name->data, mode, create, access); if (fd == NGX_INVALID_FILE) {
of->err = ngx_errno;
of->failed = ngx_open_file_n;
return NGX_INVALID_FILE;
} return fd; #else u_char *p, *cp, *end;
ngx_fd_t at_fd;
ngx_str_t at_name; if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_OFF) {
/* 调用 open 函数打开该文件 */
fd = ngx_open_file(name->data, mode, create, access); if (fd == NGX_INVALID_FILE) {
of->err = ngx_errno;
of->failed = ngx_open_file_n;
return NGX_INVALID_FILE;
} /* 打开成功,则直接返回该文件描述符 */
return fd;
} p = name->data;
end = p + name->len; at_name = *name; if (of->disable_symlinks_from) { cp = p + of->disable_symlinks_from; *cp = '\0'; at_fd = ngx_open_file(p, NGX_FILE_SEARCH|NGX_FILE_NONBLOCK,
NGX_FILE_OPEN, 0); *cp = '/'; if (at_fd == NGX_INVALID_FILE) {
of->err = ngx_errno;
of->failed = ngx_open_file_n;
return NGX_INVALID_FILE;
} at_name.len = of->disable_symlinks_from;
p = cp + 1; } else if (*p == '/') { at_fd = ngx_open_file("/",
NGX_FILE_SEARCH|NGX_FILE_NONBLOCK,
NGX_FILE_OPEN, 0); if (at_fd == NGX_INVALID_FILE) {
of->err = ngx_errno;
of->failed = ngx_openat_file_n;
return NGX_INVALID_FILE;
} at_name.len = 1;
p++; } else {
at_fd = NGX_AT_FDCWD;
} for ( ;; ) {
cp = ngx_strlchr(p, end, '/');
if (cp == NULL) {
break;
} if (cp == p) {
p++;
continue;
} *cp = '\0'; if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER) {
fd = ngx_openat_file_owner(at_fd, p,
NGX_FILE_SEARCH|NGX_FILE_NONBLOCK,
NGX_FILE_OPEN, 0, log); } else {
fd = ngx_openat_file(at_fd, p,
NGX_FILE_SEARCH|NGX_FILE_NONBLOCK|NGX_FILE_NOFOLLOW,
NGX_FILE_OPEN, 0);
} *cp = '/'; if (fd == NGX_INVALID_FILE) {
of->err = ngx_errno;
of->failed = ngx_openat_file_n;
goto failed;
} if (at_fd != NGX_AT_FDCWD && ngx_close_file(at_fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
ngx_close_file_n " \"%V\" failed", &at_name);
} p = cp + 1;
at_fd = fd;
at_name.len = cp - at_name.data;
} if (p == end) { /*
* If pathname ends with a trailing slash, assume the last path
* component is a directory and reopen it with requested flags;
* if not, fail with ENOTDIR as per POSIX.
*
* We cannot rely on O_DIRECTORY in the loop above to check
* that the last path component is a directory because
* O_DIRECTORY doesn't work on FreeBSD 8. Fortunately, by
* reopening a directory, we don't depend on it at all.
*/ fd = ngx_openat_file(at_fd, ".", mode, create, access);
goto done;
} if (of->disable_symlinks == NGX_DISABLE_SYMLINKS_NOTOWNER
&& !(create & (NGX_FILE_CREATE_OR_OPEN|NGX_FILE_TRUNCATE)))
{
fd = ngx_openat_file_owner(at_fd, p, mode, create, access, log); } else {
fd = ngx_openat_file(at_fd, p, mode|NGX_FILE_NOFOLLOW, create, access);
} done: if (fd == NGX_INVALID_FILE) {
of->err = ngx_errno;
of->failed = ngx_openat_file_n;
} failed: if (at_fd != NGX_AT_FDCWD && ngx_close_file(at_fd) == NGX_FILE_ERROR) {
ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
ngx_close_file_n " \"%V\" failed", &at_name);
} return fd;
#endif
}

11.8.3.6 ngx_http_set_etag

ngx_int_t
ngx_http_set_etag(ngx_http_request_t *r)
{
ngx_table_elt_t *etag;
ngx_http_core_loc_conf_t *clcf; clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (!clcf->etag) {
return NGX_OK;
} /* 将 ETag 头部放入到 headers_out.header 链表中 */
etag = ngx_list_push(&r->headers_out.headers);
if (etag == NULL) {
return NGX_ERROR;
} etag->hash = 1;
ngx_str_set(&etag->key, "ETag"); etag->value.data = ngx_pnalloc(r->pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + 3);
if (etag->value.data == NULL) {
etag->hash = 0;
return NGX_ERROR;
} /* 根据该文件的上一次修改时间和该文件的大小构成一个 ETag 头部的值 */
etag->value.len = ngx_sprintf(etag->value.data, "\"%xT-%xO\"",
r->headers_out.last_modified_time,
r->headers_out.content_length_n)
- etag->value.data; r->headers_out.etag = etag; return NGX_OK;
}

11.8.3.7 ngx_http_set_content_type

ngx_int_t
ngx_http_set_content_type(ngx_http_request_t *r)
{
u_char c, *exten;
ngx_str_t *type;
ngx_uint_t i, hash;
ngx_http_core_loc_conf_t *clcf; /* 若该 headers_out 中已经有 Content-Type 头了,则直接返回 */
if (r->headers_out.content_type.len) {
return NGX_OK;
} clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); /* 该请求文件的扩展名,如 "html" */
if (r->exten.len) { hash = 0; for (i = 0; i < r->exten.len; i++) {
c = r->exten.data[i]; if (c >= 'A' && c <= 'Z') { exten = ngx_pnalloc(r->pool, r->exten.len);
if (exten == NULL) {
return NGX_ERROR;
} hash = ngx_hash_strlow(exten, r->exten.data, r->exten.len); r->exten.data = exten; break;
} /* 生成一个哈希查找的 key */
hash = ngx_hash(hash, c);
} /* 在哈希表 types_hash 中根据 key(即 hash)查找与 r->exten.data 相同的项 */
type = ngx_hash_find(&clcf->types_hash, hash,
r->exten.data, r->exten.len); if (type) {
r->headers_out.content_type_len = type->len;
/* 对于 html 类型的,这里对应的 Content-Type 为 "text/html" */
r->headers_out.content_type = *type; return NGX_OK;
}
} r->headers_out.content_type_len = clcf->default_type.len;
r->headers_out.content_type = clcf->default_type; return NGX_OK;
}

11.8.3.8 ngx_hash_find

/*
* 返回散列表中关键字与 name、len 指定关键字完全相同的槽中,
* ngx_hash_elt_t 结构体中 value 成员所指向的用户数据.
*/
void *
ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len)
{
ngx_uint_t i;
ngx_hash_elt_t *elt; #if 0
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, "hf:\"%*s\"", len, name);
#endif /* 对 key 取模得到对应的 hash 节点 */
elt = hash->buckets[key % hash->size]; if (elt == NULL) {
return NULL;
} /* 然后在该 hash 节点所对应的 bucket 里逐个(该 bucket 的实现类似数组,结束有
* 哨兵保证)对于元素名称来找到唯一的那个实际元素,最后返回其 value 值
* (比如,如果在 addr->hash 结构里找到对应的实际元素,返回的 value 就是
* 其 ngx_http_core_srv_cnf_t 配置)*/
while (elt->value) {
if (len != (size_t) elt->len) {
goto next;
} for (i = 0; i < len; i++) {
if (name[i] != elt->name[i]) {
goto next;
}
} /* 将返回该名称和完成都相同的项 */
return elt->value; next: elt = (ngx_hash_elt_t *) ngx_align_ptr(&elt->name[0] + elt->len,
sizeof(void *));
continue;
} return NULL;
}

11.8.3.9 ngx_http_send_header

ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
if (r->post_action) {
return NGX_OK;
} /* 标志位,为 1 表示该 header 已经发送了 */
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;
} /* ngx_http_top_header_filter 是个全局函数指针,由各个HTTP模块
* 共同构造的一个单链表,存放的是各个 HTTP 模块在此阶段介入的
* 处理 */
return ngx_http_top_header_filter(r);
}

11.3.8.10 ngx_http_output_filter

ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_connection_t *c; c = r->connection; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http output filter \"%V?%V\"", &r->uri, &r->args); /* ngx_http_top_body_filter 是一个由各个 HTTP 模块构造的函数链表,
* 保存的是各个 HTTP 模块想要在响应消息的响应正文中做的处理 */
rc = ngx_http_top_body_filter(r, in); if (rc == NGX_ERROR) {
/* NGX_ERROR may be returned by any filter */
c->error = 1;
} return rc;
}

11.3.8.11 ngx_http_finalize_request

void
ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
ngx_connection_t *c;
ngx_http_request_t *pr;
ngx_http_core_loc_conf_t *clcf; c = r->connection; ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http finalize request: %i, \"%V?%V\" a:%d, c:%d",
rc, &r->uri, &r->args, r == c->data, r->main->count); if (rc == NGX_DONE) {
ngx_http_finalize_connection(r);
return;
} if (rc == NGX_OK && r->filter_finalize) {
c->error = 1;
} if (rc == NGX_DECLINED) {
r->content_handler = NULL;
r->write_event_handler = ngx_http_core_run_phases;
ngx_http_core_run_phases(r);
return;
} if (r != r->main && r->post_subrequest) {
rc = r->post_subrequest->handler(r, r->post_subrequest->data, rc);
} if (rc == NGX_ERROR
|| rc == NGX_HTTP_REQUEST_TIME_OUT
|| rc == NGX_HTTP_CLIENT_CLOSED_REQUEST
|| c->error)
{
if (ngx_http_post_action(r) == NGX_OK) {
return;
} ngx_http_terminate_request(r, rc);
return;
} if (rc >= NGX_HTTP_SPECIAL_RESPONSE
|| rc == NGX_HTTP_CREATED
|| rc == NGX_HTTP_NO_CONTENT)
{
if (rc == NGX_HTTP_CLOSE) {
ngx_http_terminate_request(r, rc);
return;
} if (r == r->main) {
if (c->read->timer_set) {
ngx_del_timer(c->read);
} if (c->write->timer_set) {
ngx_del_timer(c->write);
}
} c->read->handler = ngx_http_request_handler;
c->write->handler = ngx_http_request_handler; ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
return;
} if (r != r->main) {
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->background) {
if (!r->logged) {
if (clcf->log_subrequest) {
ngx_http_log_request(r);
} r->logged = 1; } else {
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"subrequest: \"%V?%V\" logged again",
&r->uri, &r->args);
} r->done = 1;
ngx_http_finalize_connection(r);
return;
} if (r->buffered || r->postponed) { if (ngx_http_set_write_handler(r) != NGX_OK) {
ngx_http_terminate_request(r, 0);
} return;
} pr = r->parent; if (r == c->data) { r->main->count--; if (!r->logged) {
if (clcf->log_subrequest) {
ngx_http_log_request(r);
} r->logged = 1; } else {
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"subrequest: \"%V?%V\" logged again",
&r->uri, &r->args);
} r->done = 1; if (pr->postponed && pr->postponed->request == r) {
pr->postponed = pr->postponed->next;
} c->data = pr; } else { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http finalize non-active request: \"%V?%V\"",
&r->uri, &r->args); r->write_event_handler = ngx_http_request_finalizer; if (r->waited) {
r->done = 1;
}
} if (ngx_http_post_request(pr, NULL) != NGX_OK) {
r->main->count++;
ngx_http_terminate_request(r, 0);
return;
} ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http wake parent request: \"%V?%V\"",
&pr->uri, &pr->args); return;
} if (r->buffered || c->buffered || r->postponed) { if (ngx_http_set_write_handler(r) != NGX_OK) {
ngx_http_terminate_request(r, 0);
} return;
} if (r != c->data) {
ngx_log_error(NGX_LOG_ALERT, c->log, 0,
"http finalize non-active request: \"%V?%V\"",
&r->uri, &r->args);
return;
} /* 标志位,为 1 表示当前请求结束 */
r->done = 1; r->read_event_handler = ngx_http_block_reading;
r->write_event_handler = ngx_http_request_empty_handler; if (!r->post_action) {
/* 为 1 表示当前请求已经完全处理完 */
r->request_complete = 1;
} if (ngx_http_post_action(r) == NGX_OK) {
return;
} if (c->read->timer_set) {
ngx_del_timer(c->read);
} if (c->write->timer_set) {
c->write->delayed = 0;
ngx_del_timer(c->write);
} if (c->read->eof) {
ngx_http_close_request(r, 0);
return;
} ngx_http_finalize_connection(r);
}

Nginx-HTTP之静态网页访问流程分析二的更多相关文章

  1. Nginx-HTTP之静态网页访问流程分析一

    假设访问静态网页的配置如下: worker_processes 1; error_log stderr debug; daemon off; master_process on; events { w ...

  2. MSM8909中LK阶段LCM屏适配与显示流程分析(二)

    1.前言 在前面的文章MSM8909中LK阶段LCM屏适配与显示流程分析(一),链接如下: https://www.cnblogs.com/Cqlismy/p/12019317.html 介绍了如何使 ...

  3. Android 4.4 音量调节流程分析(二)

    之前在Android 4.4 音量调节流程分析(一)里已经有简单的分析音量控制的流程,今天想接着继续分析下音量大小计算的方法.对于任一播放文件而言其本身都有着固定大小的音量Volume_Max,而在A ...

  4. Android 7.1 WindowManagerService 屏幕旋转流程分析 (二)

    一.概述 从上篇[Android 7.1 屏幕旋转流程分析]知道实际的旋转由WindowManagerService来完成,这里接着上面具体详细展开. 调了三个函数完成了三件事,即首先调用update ...

  5. Uboot启动流程分析(二)

    1.前言 在前面的文章Uboot启动流程分析(一)中,链接如下: https://www.cnblogs.com/Cqlismy/p/12000889.html 已经简单地分析了low_level_i ...

  6. Gradle之Android Gradle Plugin 主要流程分析(二)

    [Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要流程分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plugin ...

  7. Nginx 多进程连接请求/事件分发流程分析

    Nginx使用多进程的方法进行任务处理,每个worker进程只有一个线程,单线程循环处理全部监听的事件.本文重点分析一下多进程间的负载均衡问题以及Nginx多进程事件处理流程,方便大家自己写程序的时候 ...

  8. TI IPNC Web网页之流程分析

    流程 Appro IPNC使用的web服务器是boa. 请仔细理解下面这段话. boa这个web服务器是GUI界面和IPNC应用程序之间的通信的桥梁.它的责任是从web GUI中接收HTTP请求,并且 ...

  9. 【TCP/IP】【网络基础】网页访问流程

    引用自 <鸟哥的linux私房菜> http://cn.linux.vbird.org/linux_server/0110network_basic_1.php#ps7 那 TCP/IP ...

随机推荐

  1. 5. Java的注释,标识符、标识符的命名规范

      什么是标识符符? 凡是可以由自己命名的地方都称为修饰符. 例: 项目名 ,包名 ,类名 .方法名 2.   命名规范. ①    不可使用java关键字和保留字,但是可以包含关键字和保留字. ②  ...

  2. PHP转码函数mb_convert_encoding() 和iconv()

    注意:函数mb_convert_encoding的执行效率比iconv差,且需开启php的mbstring扩展. 一般情况下使用iconv,但此函数在碰到无法转码字符会丢弃,此种情况下可以用mb_co ...

  3. Emeditor代码编辑器常见的正则表达式总结

    Emeditor 目前来说是我个人感觉非常不错的一款记事本软件, 其中查找替换功能由于支持正则表达式而显得非常强大. <tr[^>]*> 匹配:<tr xxxxxxxxxxxx ...

  4. 总结 String、StringBuffer与StringBuilder类中常用的方法

    一.String类的常用方法 1.获取: 1)获取字符串str长度 int i = str.length(); 2)根据位置(index)获取字符 char c = str.charAt(index) ...

  5. mock.js学习之路一(Vue中使用)

    1.安装mockjs 2.配置mockjs在开发环境中启用,生产环境中禁用 3.创建mock文件夹,以及mock数据文件 4.在main.js中引入与否 5.页面获取数据 testMock(){ th ...

  6. 【python】多进程、多线程、序列

    一.多进程 1.子进程永远返回0,而父进程返回子进程的ID.这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程 ...

  7. 【Day3】项目实战。百度针对Xpath的反爬策略和解决方式

    import lxml.etree as le with open('edu.html','r',encoding='utf-8') as f: html = f.read() html_x = le ...

  8. shell变量引用

    var="www.sina.com.cn" echo ${var#*.} #sina.com.cn 从前向后删 echo ${var##*.} #.cn 贪婪模式从前向后删 ech ...

  9. linux 的常用命令(2)

    tail [必要参数] [选择参数] [文件] | 显示文件结尾内容  -v  显示详细的处理信息-q  不显示处理信息-num/-n (-)num      显示最后num行内容-n +num 从第 ...

  10. LoadRunner(6)

    一.脚本录制技术细节 1.选择合适的协议: 1)B/S架构:常用Web[HTTP/HTML]协议,如果项目中使用了其它技术,比如Ajax.JDBC.FTP等,就需要选择多协议: 2)C/S架构:常用W ...