nginx http模块开发(7) - 处理引擎

2018-07-21 17:41:15

nginx 定义了函数原型 ngx_http_handler_pt,任何 handler 模块想要处理 http 请求都必须实现这个函数,它的形式是

typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);

ngx_http_handler_pt 不仅可以返回 NGX_OK、NGX_DECLIEND、NGX_AGAIN等 nginx 错误码,也可以直接返回 200、302、404 等标准的 http 状态码,nginx 会在函数 ngx_http_finalize_request() 中做出合适的处理。

nginx 使用 ngx_http_phase_t 结构存储每个阶段可用的处理函数(handler),它实际上是动态数组 ngx_array_t,元素类型为 ngx_http_handler_pt。

// 存储在ngx_http_core_main_conf_t里
// 需要操作任何http请求的模块添加进这个数组
typedef struct {
    ngx_array_t                handlers;
} ngx_http_phase_t;
typedef struct {
    ...

    // http handler模块需要向这个数组添加元素
    // 在配置解析后的postconfiguration里向cmcf->phases数组注册
    ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;

通过这种方式,nginx 把所有的 http 模块的handler 组织成了一个表,纵向是处理阶段,横向是各个模块的处理函数,形成了一个二维链。

在配置文件解析完毕之后的 postconfiguration 函数里,模块可以向 ngx_http_core_main_conf_t::phases 数组里添加元素,实现模块 handler 的注册。比如

static ngx_http_module_t  ngx_http_static_module_ctx = {
    NULL,                   /* preconfiguration */
    ngx_http_static_init,  /* postconfiguration */

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

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

    NULL,      /* create location configuration */
    NULL       /* merge location configuration */
};
// postconfiguration,注册处理函数
static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    // 获取core模块的main conf
    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    // 使用CONTENT_PHASE
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    // 添加到phases数组,完成注册
    *h = ngx_http_static_handler;

    return NGX_OK;
}


NGX_HTTP_CONTENT_PHASE 

该阶段与其他阶段不一样,它提供了2种介入该阶段的方式。

第一种:与其他10个阶段一样,通过 postconfiguration 方法向全局的 ngx_http_core_main_conf_t 的 phases 数组添加 ngx_http_handler_pt 处理方法来实现,但是有一个问题,就是在 location_1 里面配置了,而 location_2 里面没配置,那么每次访问 location_2 时,该处理方法还是会被调用。也就是它将会应用于全部的 http 请求。

第二种:是通过设置 ngx_http_core_loc_conf_t 结构体的 handler 指针来实现的,在上几节我们知道,每一个 location 都有它自己独立的 ngx_http_core_loc_conf_t 结构体。这样当我们解析到相关指令时就可以直接把当前 location 的 ngx_http_core_loc_conf_t 中的 handler 设置为 ngx_http_handler_t 处理方法。这样做的好处是,不再应用于全部的 http 请求,而是特定的 location。

struct ngx_http_core_loc_conf_s {
    ngx_http_handler_pt  handler;
    ...
}

注意以下两点:

1.nginx 查找 location 时会检查  ngx_http_core_loc_conf_t  的 handler 成员,如果不为空就会设置 ngx_http_request_t 的 content_handler。在 NGX_HTTP_CONTENT_PHASE 阶段如果有 content_handler,那么 nginx 就不再检查 phases 数组,而是直接调用这个函数处理。所以得出结论,如果同时设置了 一、二 两种,第二种会覆盖第一种

2.由于一个 location 下只有一个 ngx_http_core_loc_conf_t,而它的 handler 是一个指针,所以如果同一个 location 里有多个指令要设置 ngx_http_handler_pt 处理方法,后一个设置会覆盖前面的设置,也就是只有最后一个设置才会生效。 


引擎的数据结构

 使用二维数组 phases 可以调用所有 handler 模块,但它的组织形式不够灵活,效率也不高。实际上,nginx 不会直接使用 handler,而是为每个阶段实现一个特定的 checker 函数,在 checker 里调用 handler,并根据返回值实现阶段的灵活跳转。

checker 的相关定义如下:

typedef struct ngx_http_phase_handler_s  ngx_http_phase_handler_t;

typedef ngx_int_t (*ngx_http_phase_handler_pt)(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph);

// 存储handler/checker,里面用next实现阶段的快速跳转
struct ngx_http_phase_handler_s {

    // 阶段的checker函数
    ngx_http_phase_handler_pt  checker;

    // 每个模块自己的处理函数
    ngx_http_handler_pt        handler;

    // 指向下一个阶段第一个模块在数组里的位置
    ngx_uint_t                 next;
};
// 所有的http请求都要使用这个引擎处理
typedef struct {
    // 存储所有handler/checker的数组,里面用next实现阶段的快速跳转
    ngx_http_phase_handler_t  *handlers;

    // server重写的跳转位置
    ngx_uint_t                 server_rewrite_index;

    // location重写的跳转位置
    ngx_uint_t                 location_rewrite_index;
} ngx_http_phase_engine_t;
typedef struct {
    // 所有的http请求都要使用这个引擎处理
    ngx_http_phase_engine_t    phase_engine;

    ...

    // http handler模块需要向这个数组添加元素
    // 在配置解析后的postconfiguration里向cmcf->phases数组注册
    // 在处理请求时不使用此数组,而是用的phase_engine
    ngx_http_phase_t           phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;

引擎的初始化

从前几篇我们知道在解析到 http{} 块时,ngx_http_block() 函数会调用所有模块的 postconfiguration 函数把自己的 handler 处理函数添加到 phases 数组里。

随后 nginx 执行函数 ngx_http_init_phase_handlers(),该函数会遍历 phases 数组,计算 handler 模块的数量,统计所以已经注册的 handler 数量并分配内存,再安阶段分类,把每个 handler 与对应阶段的 checker 组合起来,填入引擎数组。

static ngx_int_t
ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
    ngx_int_t                   j;
    ngx_uint_t                  i, n;
    ngx_uint_t                  find_config_index, use_rewrite, use_access;
    ngx_http_handler_pt        *h;
    ngx_http_phase_handler_t   *ph;
    ngx_http_phase_handler_pt   checker;

    // 初始化两个rewrite模块的索引地址为-1,即未赋值
    // 这两个索引用来在处理请求时快速跳转
    // 两个分别是sever和location rewrite
    cmcf->phase_engine.server_rewrite_index = (ngx_uint_t) -1;
    cmcf->phase_engine.location_rewrite_index = (ngx_uint_t) -1;

    find_config_index = 0;

    // 看是否有rewrite模块
    use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;

    // 看是否有access模块
    use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;

    // 基本的四个模块数量
    // 1.12.0调整了代码格式
    n = 1                  /* find config phase */
        + use_rewrite      /* post rewrite phase */
        + use_access;      /* post access phase */

    // 统计所有的handler模块数量,但不含log阶段,注意!
    for (i = 0; i < NGX_HTTP_LOG_PHASE; i++) {
        n += cmcf->phases[i].handlers.nelts;
    }

    // 创建数组
    ph = ngx_pcalloc(cf->pool,
                     n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
    if (ph == NULL) {
        return NGX_ERROR;
    }

    // 加入到http core模块的配置结构体里
    cmcf->phase_engine.handlers = ph;
    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,参数是当前的引擎数组,里面的handler是每个模块自己的处理函数
            // decline:表示不处理,继续在本阶段(rewrite)里查找下一个模块
            // done:暂时中断ngx_http_core_run_phases
            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,参数是当前的引擎数组,里面的handler是每个模块自己的处理函数
            // decline:表示不处理,继续在本阶段(rewrite)里查找下一个模块
            // done:暂时中断ngx_http_core_run_phases
            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:
            // 子请求不做访问控制,直接跳过本阶段
            // decline:表示不处理,继续在本阶段(rewrite)里查找下一个模块
            // again/done:暂时中断ngx_http_core_run_phases
            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;

        // 1.13.4去掉了原try_files阶段,改为precontent
        // 访问静态文件,不能介入
        //case NGX_HTTP_TRY_FILES_PHASE:
        //    if (cmcf->try_files) {
        //        ph->checker = ngx_http_core_try_files_phase;
        //        n++;
        //        ph++;
        //    }

        //    continue;

        // 处理请求,产生响应内容,最常用的阶段
        // 这已经是处理的最后阶段了(log阶段不处理请求,不算)
        // ngx_http_core_content_phase
        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;
}

执行之后,phase_engine 的 handlers 数组把各个模块的 handler 组成了一个线性的数组。每个元素里的 next 指定了下一个阶段开始的序号,可以方便直接跳转到下一阶段。

引擎的运行机制

ngx_http_request_t 里的成员 phase_handler 标记了当前处理过程中所在阶段的 handler 序号,从 0 开始,通常是 NGX_HTTP_SERVER_REWRITE_PHASE 的第一个模块 ngx_http_rewrite_module。

在接收完请求头后,nginx 开始调用函数 ngx_http_core_run_phases 执行引擎。

// 启动引擎数组处理请求
// 从phase_handler的位置开始调用模块处理
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_phase_handler_t   *ph;
    ngx_http_core_main_conf_t  *cmcf;

    // 得到core main配置
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    // 获取引擎里的handler数组
    ph = cmcf->phase_engine.handlers;

    // 从phase_handler的位置开始调用模块处理
    // 外部请求的引擎数组起始序号是0,从头执行引擎数组,即先从Post read开始
    // 内部请求,即子请求.跳过post read,直接从server rewrite开始执行,即查找server
    while (ph[r->phase_handler].checker) {

        // 调用引擎数组里的checker
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);

        // checker会检查handler的返回值
        // 如果handler返回again/done那么就返回ok
        // 退出引擎数组的处理
        // 由于r->write_event_handler = ngx_http_core_run_phases
        // 当再有写事件时会继续从之前的模块执行
        //
        // 如果checker返回again,那么继续在引擎数组里执行
        // 模块由r->phase_handler序号指定,可能会有阶段的跳跃
        if (rc == NGX_OK) {
            return;
        }
    }
}

不同阶段的 checker 流程大同小异,我们看下 NGX_HTTP_CONTENT_PHASE 的 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;

    // 检查请求是否有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
        // 相当于处理完后结束请求
        // 这种用法简化了客户代码,相当于模板方法模式
        // rc = handler(r); ngx_http_finalize_request(rc);
        //
        // 结束请求
        // 但如果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;
    }

    // 模块handler返回decline,需要继续执行本阶段的下一个 handler。
    ph++;

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

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


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

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

通常一个 location 没有设置特殊的内容处理函数,默认由 ngx_http_static_module、ngx_http_index_module 等模块来处理,读取磁盘文件。

日志处理阶段

日志处理不在 ngx_http_core_run_phases 里调用,而是在请求完毕时调用。

// 请求已经结束,调用log模块记录日志
// 在ngx_http_free_request里调用
static void
ngx_http_log_request(ngx_http_request_t *r)
{
    ngx_uint_t                  i, n;
    ngx_http_handler_pt        *log_handler;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    // log handler不在引擎数组里
    log_handler = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.elts;
    n = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.nelts;

    // 不检查handler的返回值,直接调用,不使用checker
    for (i = 0; i < n; i++) {
        log_handler[i](r);
    }
}


备注

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


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