nginx http框架执行流程(4) - 各个阶段

2018-07-28 17:48:13

从上篇 ngx_http_core_run_phases() 的循环可以知道,它会遍历执行每个阶段的处理函数。

while (ph[r->phase_handler].checker) {
    rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

    if (rc == NGX_OK) {
        return;
    }
}

我们从 ngx_http_init_phase_handlers() 函数中可以知道,所有阶段的处理函数都已经连成一条线。

读者可以结合 http模块开发(7) - 处理引擎 来阅读。  

static ngx_int_t
ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
    //...
    
    n = 0;

    // 除了log阶段,处理所有的handler模块
    // 从最开始的post read阶段开始,直至content阶段,不包括log阶段
    for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
        h = cmcf->phases[i].handlers.elts;

        switch (i) {

        // 地址改写阶段
        // ngx_http_core_rewrite_phase
        case NGX_HTTP_SERVER_REWRITE_PHASE:
            // 如果rewrite索引未初始化,那么设置为第一个rewrite模块
            if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
                cmcf->phase_engine.server_rewrite_index = n;
            }

            checker = ngx_http_core_rewrite_phase;
            break;

        // 查找配置,不能介入
        case NGX_HTTP_FIND_CONFIG_PHASE:
            find_config_index = n;

            ph->checker = ngx_http_core_find_config_phase;
            n++;
            ph++;

            continue;

        // 地址改写阶段
        // ngx_http_core_rewrite_phase
        case NGX_HTTP_REWRITE_PHASE:
            // 如果rewrite索引未初始化,那么设置为第一个rewrite模块
            if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {
                cmcf->phase_engine.location_rewrite_index = n;
            }
            checker = ngx_http_core_rewrite_phase;

            break;

        // 改写后,不能介入
        case NGX_HTTP_POST_REWRITE_PHASE:
            if (use_rewrite) {
                ph->checker = ngx_http_core_post_rewrite_phase;
                ph->next = find_config_index;
                n++;
                ph++;
            }

            continue;

        // 检查访问权限
        // ngx_http_core_access_phase
        case NGX_HTTP_ACCESS_PHASE:
            checker = ngx_http_core_access_phase;
            n++;
            break;

        // 检查访问权限后,不能介入
        case NGX_HTTP_POST_ACCESS_PHASE:
            if (use_access) {
                ph->checker = ngx_http_core_post_access_phase;
                ph->next = n;
                ph++;
            }

            continue;

        case NGX_HTTP_CONTENT_PHASE:
            checker = ngx_http_core_content_phase;
            break;

        // NGX_HTTP_POST_READ_PHASE/NGX_HTTP_PREACCESS_PHASE
        default:
            checker = ngx_http_core_generic_phase;
        }

        // n增加该阶段的handler数量
        n += cmcf->phases[i].handlers.nelts;

        // 倒着遍历handler数组
        for (j = cmcf->phases[i].handlers.nelts - 1; j >= 0; j--) {
            ph->checker = checker;
            ph->handler = h[j];
            ph->next = n;
            ph++;
        }
    }

    return NGX_OK;
}

举个例子,假设 POST_READ  阶段有3个模块分别注册了回调函数,SERVER_REWRITE 有2个模块注册了回调函数,那么 ph[0-2] 在 POST_READ 阶段,ph[3-4] 是在 SERVER_REWRITE 阶段。

前一个阶段的所有的 ngx_http_phase_handler_s 中 next 都指向下一个阶段的开始。比如下面3个的值都为 3,序号 3 刚好是下一个阶段 SERVER_REWRITE 序号起始。

ph[0]->next
ph[1]->next
ph[2]->next


通用阶段

通用阶段的处理函数为 ngx_http_core_generic_phase(),管理的是 POST_READ 和 PREACCESS 两个阶段的模块。

如果 handler 处理函数都返回 NGX_DECLINED,那么理论上该函数重复调用的次数为 POST_READ 和 PREACCESS两个阶段注册函数的次数和。

ngx_int_t
ngx_http_core_generic_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
    ngx_int_t  rc;

    // 根据ph,调用不同模块的处理函数
    rc = ph->handler(r);

    // 模块已经处理成功,直接跳过本阶段
    // 注意不是++,而是next
    if (rc == NGX_OK) {
        r->phase_handler = ph->next;

        // again继续引擎数组的循环
        return NGX_AGAIN;
    }

    // 模块handler返回decline,表示不处理
    if (rc == NGX_DECLINED) {
        // 继续在本阶段(rewrite)里查找下一个模块
        // 索引加1
        r->phase_handler++;

        // again继续引擎数组的循环
        return NGX_AGAIN;
    }

    // again/done,暂时中断ngx_http_core_run_phases
    // 由于r->write_event_handler = ngx_http_core_run_phases
    // 当再有写事件时会继续从之前的模块执行
    if (rc == NGX_AGAIN || rc == NGX_DONE) {
        return NGX_OK;
    }

    // 结束请求
    // 但如果count>1,则不会真正结束
    ngx_http_finalize_request(r, rc);

    return NGX_OK;
}

重写阶段

重写阶段处理函数 ngx_http_core_rewrite_phase() 管理 REWRITE 和 SERVER_REWRITE 两个阶段的模块,这个阶段的模块工作是重写 uri,所以逻辑比较简单。

ngx_int_t
ngx_http_core_rewrite_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
    ngx_int_t  rc;

    rc = ph->handler(r);

    // 模块handler返回decline,表示不处理
    if (rc == NGX_DECLINED) {
        // 继续在本阶段(rewrite)里查找下一个模块
        r->phase_handler++;

        // again继续引擎数组的循环
        return NGX_AGAIN;
    }

    // done,暂时中断ngx_http_core_run_phases
    // 由于r->write_event_handler = ngx_http_core_run_phases
    // 当再有写事件时会继续从之前的模块执行
    if (rc == NGX_DONE) {
        return NGX_OK;
    }
    ngx_http_finalize_request(r, rc);

    return NGX_OK;
}

需要注意的是 rewrite 阶段模块执行成功应该返回 NGX_DECLINED 而不是 NGX_OK,这样可以让同阶段的其他重写模块也有改写 uri 的机会。

访问控制阶段

ngx_http_core_access_phase() 处理函数管理着 NGX_HTTP_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;

    // 子请求不做访问控制,直接跳过本阶段
    // 注意不是++,而是next
    if (r != r->main) {
        r->phase_handler = ph->next;
        return NGX_AGAIN;
    }

    // 调用每个模块自己的处理函数
    rc = ph->handler(r);

    // 模块handler返回decline,表示不处理
    if (rc == NGX_DECLINED) {
        // 继续在本阶段(access)里查找下一个模块
        // 索引加1
        r->phase_handler++;

        // again继续引擎数组的循环
        return NGX_AGAIN;
    }

    // agian/done,暂时中断ngx_http_core_run_phases
    // 由于r->write_event_handler = ngx_http_core_run_phases
    // 当再有写事件时会继续从之前的模块执行
    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) {

        // 模块返回ok,满足条件,需要下一个模块再进行检查
        if (rc == NGX_OK) {
            r->phase_handler++;

            // again继续引擎数组的循环
            return NGX_AGAIN;
        }

        // 模块检查失败,需要结束请求

    } else {
        // NGX_HTTP_SATISFY_ANY,仅一个满足即可

        // 模块返回ok,满足条件
        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;

            // again继续引擎数组的循环
            return NGX_AGAIN;
        }

        // 模块检查失败,禁止访问
        // 但因为是any,也许后续的模块检查会成功,所以继续执行引擎
        // 让后续的模块再检查权限,只要一个成功就可以
        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;
        }
    }

    // 结束请求
    // 但如果count>1,则不会真正结束
    ngx_http_finalize_request(r, rc);

    return NGX_OK;
}

内容产生阶段

该阶段将产生具体的响应内容。

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;

    // 检查请求是否有handler,也就是location里定义了handler
    if (r->content_handler) {
        // 设置写事件为ngx_http_request_empty_handler
        // 即暂时不再进入ngx_http_core_run_phases
        // 这是因为内容产生阶段已经是“最后”一个阶段了,不需要再走其他阶段
        // 之后发送数据时会改为ngx_http_set_write_handler
        // 但我们也可以修改,让写事件触发我们自己的回调
        r->write_event_handler = ngx_http_request_empty_handler;

        // 调用location专用的内容处理handler
        // 返回值传递给ngx_http_finalize_request
        // 相当于处理完后结束请求
        //
        // 结束请求
        // 但如果count>1,则不会真正结束
        // handler可能返回done、again,例如调用read body
        ngx_http_finalize_request(r, r->content_handler(r));

        // 需要在之后的处理函数里继续处理,不能调用write_event_handler
        return NGX_OK;
    }

    // 没有专门的handler
    // 调用每个模块自己的处理函数
    rc = ph->handler(r);

    if (rc != NGX_DECLINED) {
        ngx_http_finalize_request(r, rc);
        return NGX_OK;
    }

    /* rc == NGX_DECLINED */

    // 模块handler返回decline,表示不处理
    ph++;

    // 下一个模块有 checker
    if (ph->checker) {
        // 索引加1
        r->phase_handler++;

        // again继续引擎数组的循环
        return NGX_AGAIN;
    }

    // 已经到了引擎数组的最末尾
    // 没有一个content模块可以处理

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

    // 返回404
    ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);

    // 结束引擎数组的循环
    return NGX_OK;
}

内容处理阶段的流程图大致如下


备注

1.测试环境centos7 64位,nginx版本为 1.14.0。
2..原文地址http://www.freecls.com/a/2712/e4


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。