vislee / leevis.com

Blog
87 stars 13 forks source link

ngx中http模块的执行顺序 #108

Open vislee opened 7 years ago

vislee commented 7 years ago

概述

ngx中http有11个阶段,其中有7个阶段可以添加模块,例如NGX_HTTP_ACCESS_PHASE和NGX_HTTP_CONTENT_PHASE阶段,这两个阶段有明显的先后顺序,先执行access阶段再执行content阶段。但是相同阶段的不同模块的执行顺序是什么样的呢?和什么有关系呢?

执行阶段

解析完请求行、请求头以后就会调用ngx_http_process_request函数,该函数又调用了ngx_http_handler,ngx_http_handler又调用ngx_http_core_run_phases执行http的10个阶段,代码如下。


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;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    ph = cmcf->phase_engine.handlers;

    while (ph[r->phase_handler].checker) {

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

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

通过以上代码可以看处,10个阶段的执行基本就是循环执行ph这个数组中的函数指针指向的函数。执行阶段的顺序性就是ph这个数组的顺序,那么看下这个数组是什么时候生成的,他的顺序是如何的。

在解析http块的配置文件时,会调用ngx_http_block函数,该函数在解析完http块下配置后,又调用ngx_http_init_phases初始化了cmcf->phases 这11个阶段的数组,又循环调用所有http模块的postconfiguration函数指针向11个阶段添加回调函数,代码如下。然后再调用ngx_http_init_phase_handlers函数初始化ph数组,代码如下。

    // ngx_http_block 函数的部分代码,调用http模块的postconfiguration函数指针指向的函数添加回调
    for (m = 0; cf->cycle->modules[m]; m++) {
        if (cf->cycle->modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = cf->cycle->modules[m]->ctx;

        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }

        // ngx_http_init_phase_handlers函数的部分代码,初始化ph数组
        for (j = cmcf->phases[i].handlers.nelts - 1; j >= 0; j--) {
            ph->checker = checker;
            ph->handler = h[j];
            ph->next = n;
            ph++;
        }

由上述代码可知,模块的顺序和cycle->modules数组的顺序有关,相同阶段模块的调用顺序和添加到cycle->modules数组中的顺序是相反的。

那么,cycle->modules数组是如何赋值的呢? 在初始化cycle调用ngx_init_cycle函数中调用了ngx_cycle_modules函数,该函数是把ngx_modules这个数组的模块复制了一份,代码:ngx_memcpy(cycle->modules, ngx_modules, ngx_modules_n * sizeof(ngx_module_t *));。这个ngx_modules数组是在objs/ngx_modules.c文件中定义的,编译的时候由./auto/configure生成的。

通常情况下模块会按照上述顺序执行,但是也有可能会按照阶段跳过本阶段其余的模块跳到下个阶段。 初始化函数ngx_http_init_phase_handlersph->next = n的赋值就是下个阶段的数组下标。有一点不同的是NGX_HTTP_ACCESS_PHASE的模块会把NGX_HTTP_POST_ACCESS_PHASE阶段也跳过:

        case NGX_HTTP_ACCESS_PHASE:
            checker = ngx_http_core_access_phase;
            n++;    // 跳过 NGX_HTTP_POST_ACCESS_PHASE的数组下标
            break;

模块编译

官方的模块通过./auto/configure的with-参数开启或通过without-参数关闭。例如:--with-http_geoip_module--without-http_gzip_module。 第三方模块通过./auto/configure的--add-module指定。例如指定我github上clone下来的helloworld模块,--add-module=./github.com/vislee/ngx_http_hello_world_module

看下编译的脚本吧,通过./auto/configure 检测编译的系统以及该系统可以支持的一些功能并生成makefile和模块文件./objs/ngx_modules.c

综上, 其中,objs/ngx_modules.c文件是由modules数组生成的,grep下该数组是由以下数组赋值的。其中HTTP_MODULES数组是由auto/modules文件调用auto/module添加的。因此如果是添加的第三方模块,先调用--add-module=的反而后执行。

auto/modules:1251:modules="$CORE_MODULES $EVENT_MODULES"
auto/modules:1256:    modules="$modules $THREAD_POOL_MODULE"
auto/modules:1261:    modules="$modules $HTTP_MODULES $HTTP_FILTER_MODULES \
auto/modules:1262:                      $HTTP_AUX_FILTER_MODULES $HTTP_INIT_FILTER_MODULES"

auto/modules:1271:        modules="$modules $MAIL_MODULES"
auto/modules:1291:        modules="$modules $STREAM_MODULES"
auto/modules:1333:modules="$modules $MISC_MODULES"

测试脚本:


CC=${CC:-cc}

echo "CC:"$CC
echo "             configure"
HTTP_GEOIP=NO
HTTP_GZIP=YES
NGX_ADDONS=
opt=

echo "======================================"
for opts
do
    # echo "===>["$opts"]"
    opt="$opt `echo $opts | sed -e \"s/\(--[^=]*=\)\(.* .*\)/\1'\2'/\"`"

    case "$opts" in
        -*=*) value=`echo "$opts" | sed -e 's/[-_a-zA-Z0-9]*=//'` ;;
           *) value="" ;;
    esac

    case "$opts" in
        --with-http_geoip_module)        echo "geoip enable";      HTTP_GEOIP=YES                  ;;
        --without-http_gzip_module)      echo "gzip disable";      HTTP_GZIP=NO                    ;;
        --add-module=*)                  echo "add module "$value; NGX_ADDONS="$NGX_ADDONS $value" ;;
        *)                               echo "opts:" $opts;  echo "val:" $value;;
    esac

done

echo "\n\n"
echo "             add module"
echo "====================================="
NGX_ADDONS=

NGX_ADDONS="$NGX_ADDONS ngx_http_test1_module"
NGX_ADDONS="$NGX_ADDONS ngx_http_test2_module"

if test -n "$NGX_ADDONS"; then
    for ngx_addon_dir in $NGX_ADDONS; do
        echo $ngx_addon_dir
    done
fi

echo "\n\n"
echo "             makefile"
echo "====================================="

ngx_include_opt="-I "
ngx_regex_dirsep="\/"
ngx_dirsep='/'

ngx_regex_cont=' \\\
    '
ngx_cont=' \
    '
ngx_tab=' \
        '
ngx_spacer=

ngx_incs=`echo "src/core src/http"\
    | sed -e "s/  *\([^ ][^ ]*\)/$ngx_regex_cont$ngx_include_opt\1/g" \
          -e "s/\//$ngx_regex_dirsep/g"`

echo "\n\n"
echo "             result"
echo "--------------------------------------"
echo "opt==["$opt"]"
echo "modules=["$NGX_ADDONS"]"
echo "HTTP_GEOIP="$HTTP_GEOIP
echo "HTTP_GZIP="$HTTP_GZIP
echo "ngx_incs="$ngx_include_opt$ngx_incs
echo "====================================="