3.3 如何将自己的HTTP模块编译进Nginx

Nginx提供了一种简单的方式将第三方的模块编译到Nginx中。首先把源代码文件全部放到一个目录下,同时在该目录中编写一个文件用于通知Nginx如何编译本模块,这个文件名必须为config。它的格式将在3.3.1节中说明。
这样,只要在configure脚本执行时加入参数--add-module=PATH(PATH就是上面我们给定的源代码、config文件的保存目录),就可以在执行正常编译安装流程时完成Nginx编译工作。
有时,Nginx提供的这种方式可能无法满足我们的需求,其实,在执行完configure脚本后Nginx会生成objs/Makefile和objs/ngx_modules.c文件,完全可以自己去修改这两个文件,这是一种更强大也复杂得多的方法,我们将在3.3.3节中说明如何直接修改它们。

3.3.1 config文件的写法

config文件其实是一个可执行的Shell脚本。如果只想开发一个HTTP模块,那么config文件中需要定义以下3个变量:
ngx_addon_name:仅在configure执行时使用,一般设置为模块名称。
HTTP_MODULES:保存所有的HTTP模块名称,每个HTTP模块间由空格符相连。在重新设置HTTP_MODULES变量时,不要直接覆盖它,因为configure调用到自定义的config脚本前,已经将各个HTTP模块设置到HTTP_MODULES 变量中了,因此,要像如下这样设置:

"$HTTP_MODULES ngx_http_mytest_module"

NGX_ADDON_SRCS:用于指定新增模块的源代码,多个待编译的源代码间以空格符相连。注意,在设置NGX_ADDON_SRCS时可以使用$ngx_addon_dir变量,它等价于configure执行时--add-module=PATH的PATH参数。
因此,对于mytest模块,可以这样编写config文件:

ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"

注意 以上3个变量并不是唯一可以在config文件中自定义的部分。如果我们不是开发HTTP模块,而是开发一个HTTP过滤模块,那么就要用HTTP_FILTER_MODULES替代上面的HTTP_MODULES变量。事实上,包括$CORE_MODULES、$EVENT_MODULES、$HTTP_MODULES、 $HTTP_FILTER_MODULES、$HTTP_HEADERS_FILTER_MODULE等模块变量都可以重定义,它们分别对应着Nginx的核心模块、事件模块、HTTP模块、HTTP过滤模块、HTTP头部过滤模块。除了NGX_ADDON_SRCS变量,或许还有一个变量我们会用到,即$NGX_ADDON_DEPS变量,它指定了模块依赖的路径,同样可以在config中设置。

3.3.2 利用configure脚本将定制的模块加入到Nginx中

在1.6节提到的configure执行流程中,其中有两行脚本负责将第三方模块加入到Nginx中,如下所示。

. auto/modules
. auto/make

下面完整地解释一下configure脚本是如何与3.3.1节中提到的config文件配合起来把定制的第三方模块加入到Nginx中的。
在执行configure --add-module=PATH命令时,PATH就是第三方模块所在的路径。在configure中,通过auto/options脚本设置了NGX_ADDONS变量:

--add-module=*)                  NGX_ADDONS="$NGX_ADDONS $value" ;;

在configure命令执行到auto/modules脚本时,将在生成的ngx_modules.c文件中加入定制的第三方模块。

if test -n "$NGX_ADDONS"; then

    echo configuring additional modules

    for ngx_addon_dir in $NGX_ADDONS
    do
        echo "adding module in $ngx_addon_dir"

        if test -f $ngx_addon_dir/config; then
            #在这里执行自定义的config脚本
            . $ngx_addon_dir/config

            echo " + $ngx_addon_name was configured"

        else
            echo "$0: error: no $ngx_addon_dir/config was found"
            exit 1
        fi
    done
fi

可以看到,$NGX_ADDONS可以包含多个目录,对于每个目录,如果其中存在config文件就会执行,也就是说,在config中重新定义的变量都会生效。之后,auto/modules脚本开始创建ngx_modules.c文件,这个文件的关键点就是定义了ngx_module_t *ngx_modules[]数组,这个数组存储了Nginx中的所有模块。Nginx在初始化、处理请求时,都会循环访问ngx_modules数组,确定该用哪一个模块来处理。下面来看一下auto/modules是如何生成数组的,代码如下所示:

modules="$CORE_MODULES $EVENT_MODULES"

if [ $USE_OPENSSL = YES ]; then
    modules="$modules $OPENSSL_MODULE"
    CORE_DEPS="$CORE_DEPS $OPENSSL_DEPS"
    CORE_SRCS="$CORE_SRCS $OPENSSL_SRCS"
fi

if [ $HTTP = YES ]; then
    modules="$modules $HTTP_MODULES $HTTP_FILTER_MODULES \
             $HTTP_HEADERS_FILTER_MODULE \
             $HTTP_AUX_FILTER_MODULES \
             $HTTP_COPY_FILTER_MODULE \
             $HTTP_RANGE_BODY_FILTER_MODULE \
             $HTTP_NOT_MODIFIED_FILTER_MODULE"

    NGX_ADDON_DEPS="$NGX_ADDON_DEPS \$(HTTP_DEPS)"
fi

首先,auto/modules会按顺序生成modules变量。注意,这里的$HTTP_MODULES等已经在config文件中重定义了。这时,modules变量是包含所有模块的。然后,开始生成ngx_modules.c文件:

cat << END                                    > $NGX_MODULES_C

#include <ngx_config.h>
#include <ngx_core.h>

$NGX_PRAGMA

END

for mod in $modules
do
    echo "extern ngx_module_t  $mod;"         >> $NGX_MODULES_C
done

echo                                          >> $NGX_MODULES_C
echo 'ngx_module_t *ngx_modules[] = {'        >> $NGX_MODULES_C

for mod in $modules
do
    #向ngx_modules数组里添加Nginx模块
    echo "    &$mod,"                         >> $NGX_MODULES_C
done

cat << END                                    >> $NGX_MODULES_C
    NULL
};

END

这样就已经确定了Nginx在运行时会调用自定义的模块,而auto/make脚本负责把相关模块编译进Nginx。
在Makefile中生成编译第三方模块的源代码如下:

if test -n "$NGX_ADDON_SRCS"; then

    ngx_cc="\$(CC) $ngx_compile_opt \$(CFLAGS) $ngx_use_pch \$(ALL_INCS)"

    for ngx_src in $NGX_ADDON_SRCS
    do
        ngx_obj="addon/`basename \`dirname $ngx_src\``"

        ngx_obj=`echo $ngx_obj/\`basename $ngx_src\` \
            | sed -e "s/\//$ngx_regex_dirsep/g"`

        ngx_obj=`echo $ngx_obj \
            | sed -e 
              "s#^\(.*\.\)cpp\\$#$ngx_objs_dir\1$ngx_objext#g" \
                  -e 
              "s#^\(.*\.\)cc\\$#$ngx_objs_dir\1$ngx_objext#g" \
                  -e 
              "s#^\(.*\.\)c\\$#$ngx_objs_dir\1$ngx_objext#g" \
                  -e 
              "s#^\(.*\.\)S\\$#$ngx_objs_dir\1$ngx_objext#g"`

        ngx_src=`echo $ngx_src | sed -e "s/\//$ngx_regex_dirsep/g"`

        cat << END                                            >> $NGX_MAKEFILE

$ngx_obj:    \$(ADDON_DEPS)$ngx_cont$ngx_src
    $ngx_cc$ngx_tab$ngx_objout$ngx_obj$ngx_tab$ngx_src$NGX_AUX

END
     done

fi

下面这段代码用于将各个模块的目标文件设置到ngx_obj变量中,紧接着会生成Makefile里的链接代码,并将所有的目标文件、库文件链接成二进制程序。

for ngx_src in $NGX_ADDON_SRCS
do
    ngx_obj="addon/`basename \`dirname $ngx_src\``"

    test -d $NGX_OBJS/$ngx_obj || mkdir -p $NGX_OBJS/$ngx_obj

    ngx_obj=`echo $ngx_obj/\`basename $ngx_src\` \
        | sed -e "s/\//$ngx_regex_dirsep/g"`

    ngx_all_srcs="$ngx_all_srcs $ngx_obj"
done

…

cat << END                                                    >> $NGX_MAKEFILE

$NGX_OBJS${ngx_dirsep}nginx${ngx_binext}:    
    $ngx_deps$ngx_spacer \$(LINK) 
    ${ngx_long_start}${ngx_binout}$NGX_OBJS${ngx_dirsep}nginx$ngx_long_cont$ngx_objs$ngx_libs$ngx_link
    $ngx_rcc
${ngx_long_end}
END

综上可知,第三方模块就是这样嵌入到Nginx程序中的。

3.3.3 直接修改Makefile文件

3.3.2节中介绍的方法毫无疑问是最方便的,因为大量的工作已由Nginx中的configure脚本帮我们做好了。在使用其他第三方模块时,一般也推荐使用该方法。
我们有时可能需要更灵活的方式,比如重新决定ngx_module_t *ngx_modules[]数组中各个模块的顺序,或者在编译源代码时需要加入一些独特的编译选项,那么可以在执行完configure后,对生成的objs/ngx_modules.c和objs/Makefile文件直接进行修改。
在修改objs/ngx_modules.c时,首先要添加新增的第三方模块的声明,如下所示。

extern ngx_module_t  ngx_http_mytest_module;

其次,在合适的地方将模块加入到ngx_modules数组中。

ngx_module_t *ngx_modules[] = {
  …
  &ngx_http_upstream_ip_hash_module,
  &ngx_http_mytest_module,
  &ngx_http_write_filter_module,
    …
    NULL
};

注意,模块的顺序很重要。如果同时有两个模块表示对同一个请求感兴趣,那么只有顺序在前的模块会被调用。
修改objs/Makefile时需要增加编译源代码的部分,例如:

objs/addon/httpmodule/ngx_http_mytest_module.o: $(ADDON_DEPS) \
        ../sample/httpmodule//ngx_http_mytest_module.c
        $(CC) -c $(CFLAGS)  $(ALL_INCS) \
                -o objs/addon/httpmodule/ngx_http_mytest_module.o \
                ../sample/httpmodule//ngx_http_mytest_module.c

还需要把目标文件链接到Nginx中,例如:

objs/nginx:     objs/src/core/nginx.o \
...
     objs/addon/httpmodule/ngx_http_mytest_module.o \
  objs/ngx_modules.o

    $(LINK) -o objs/nginx \
    objs/src/core/nginx.o \
    ...
    objs/addon/httpmodule/ngx_http_mytest_module.o \
  objs/ngx_modules.o \
  -lpthread -lcrypt -lpcre -lcrypto -lcrypto -lz

请慎用这种直接修改Makefile和ngx_modules.c的方法,不正确的修改可能导致Nginx工作不正常。