yzprofile's Notebook

All Posts| Note| Books| About
26 Oct 2011

nginx module Hello world

###ningx从写模块开始:

#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"
//ngx_http_mytest_module.c

#include <fcntl.h>

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


static char* ngx_http_mytest(ngx_conf_t *cf,ngx_command_t* cmd,void *conf);
static void* ngx_http_mytest_create_loc_conf(ngx_conf_t*cf);
static char* ngx_http_mytest_meger_loc_conf(ngx_conf_t*cf,void*parent,void*child);
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r);

typedef struct {
    ngx_str_t myteststr;
}ngx_http_mytest_loc_conf_t;

static ngx_command_t ngx_http_mytest_commands[]={
    { ngx_string("mytest"),
      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_http_mytest,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_mytest_loc_conf_t,myteststr),
      NULL },
    ngx_null_command
};

static ngx_http_module_t ngx_http_mytest_module_ctx = {
    NULL, /* preconfiguration */
    NULL, /* postconfiguration */

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

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

    ngx_http_mytest_create_loc_conf, /* create location configuration */
    ngx_http_mytest_meger_loc_conf /* merge location configuration */
};

ngx_module_t ngx_http_mytest_module = {
    NGX_MODULE_V1,
    &ngx_http_mytest_module_ctx,
    ngx_http_mytest_commands,
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};

static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
    char* handler_name = "ngx_http_mytest_handler\n";
    
    ngx_int_t rc;
    ngx_buf_t *b;
    ngx_chain_t out;
    ngx_http_mytest_loc_conf_t *mytest_conf;

    mytest_conf = ngx_http_get_module_loc_conf(r,ngx_http_mytest_module);
    
    if (!(r->method & (NGX_HTTP_GET))) {
        return NGX_HTTP_NOT_ALLOWED;
    }

    rc = ngx_http_discard_request_body(r);

    if (rc) {
        return rc;
    }

    r->headers_out.content_type.len = sizeof("text/html")-1;
    r->headers_out.content_type.data = (u_char*) "text/html";

    b = ngx_pcalloc(r->pool,sizeof(ngx_buf_t));
    if (b==NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    out.buf = b;
    out.next = NULL;

    b->pos = mytest_conf->myteststr.data;
    b->last = mytest_conf->myteststr.data + mytest_conf->myteststr.len;

    b->memory = 1;
    b->last_buf = 1;

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = mytest_conf->myteststr.len;

    rc = ngx_http_send_header(r);

    if (rc==NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }
    

    return ngx_http_output_filter(r,&out);
    
}

static char* ngx_http_mytest(ngx_conf_t* cf,ngx_command_t* cmd,void* conf)
{
    ngx_http_core_loc_conf_t *clcf;

    clcf = ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);
    clcf->handler = ngx_http_mytest_handler;

    ngx_conf_set_str_slot(cf,cmd,conf);

    return NGX_CONF_OK;
}

static void* ngx_http_mytest_create_loc_conf(ngx_conf_t* cf)
{
    ngx_http_mytest_loc_conf_t *mytest_conf;
    mytest_conf = ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_loc_conf_t));
    if (!mytest_conf) {
        return NGX_CONF_ERROR;
    }
    mytest_conf->myteststr.len = 0;
    mytest_conf->myteststr.data = NULL;
    return mytest_conf;
}

static char* ngx_http_mytest_meger_loc_conf(ngx_conf_t*cf,void* parent,void* child)
{
    ngx_http_mytest_loc_conf_t *prev = parent;
    ngx_http_mytest_loc_conf_t *mytest_conf = child;
    ngx_conf_merge_str_value(mytest_conf->myteststr,prev->myteststr,"");
    return NGX_CONF_OK;
}

一个hello world级别的模块,add-module之后nginx会把这些代码一起编译到整个应用里。但是对于一个初学的开发者就开始迷糊了,这些代码是如何被编译,如何被调用的呢?

下面就带着这个问题一起一步一步分析一下,这篇文章也是随着我自己的分析一点一点往下写的,一个肯定的结论也是由许多很2的猜想里逐渐排除出来的。所以有些分析和理解不到位的地方,随着深入应该会更加明确,文章尽可能保存我自己的思路,以便整理,也好留给和我一样的初学者一个可以寻觅的线索。并且一边记录一边分析也给自己提供了一个更加理性思考的条件,去回答自己提出的问题,往往不记录,不输出的情况比较容易以表面现象盲下定论,这也是我之前在学校一直在犯的错误,人太懒了又太急功近利。

我一直认为思路比结论要重要,大神们经常给出一个”这样做就对的”结论,但是从曾经的”我认为这样没错啊”到大神的”对”往往有千转百折也绕不过的弯,并且大神们羞涩于写些自己认为应该是众所周知的技巧,这又在一定程度上提高了入行的门槛,这里尽可能补充一些大神们漏掉的并且我又恰好知道的。:)

言归正传:

###configure

归根溯源,一切都从configure开始。以文本打开configure,”Copyright (C) Igor Sysoev” -_-!!

我们只关心关于add-module的选项,–add-module参数在auto/option中256行赋值了NGX_ADDONS变量。在auto/modules文件中有这么一段:

#auto/modules

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
            . $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

这里已经可以看出来模块开发的时候为什么会需要一个config文件了,它是属于configure的一部分,并且也解答了NGX_ADDON_SRCS,ngx_addon_name,HTTP_MODULES这些变量从何而来。

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

在auto/make脚本中有以上代码片段,并在configure中被调用,用来把模块目录中的config里涉及的源文件添加到生成的Makefile中.这样编译的时候就会被连带这nginx的基础框架一起编译进去了.

###nginx启动过程中的模块加载

在objs目录下为configure后生成的一些源文件,里面有些宏定义,以及包含所有模块的数组.这个数组在ngx_modules.c文件中.所有的模块加载,初始化也都是围绕这个数组来进行操作的.

第一次对ngx_modules进行初始化是在core/nginx.c文件main函数中:

//core/nginx.c:main

ngx_max_module = 0;
for (i = 0; ngx_modules[i]; i++) {
    ngx_modules[i]->index = ngx_max_module++;
}

对ngx_modules每个模块进行编号.

随后在core/ngx_cycle.c的init_cycle函数中:

//core/ngx_cycle.c:ngx_init_cycle

for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->type != NGX_CORE_MODULE) {
        continue;
    }

    module = ngx_modules[i]->ctx;

    if (module->create_conf) {
        rv = module->create_conf(cycle);
        if (rv == NULL) {
            ngx_destroy_pool(pool);
            return NULL;
        }
        cycle->conf_ctx[ngx_modules[i]->index] = rv;
    }
}

这里只对nginx的核心级模块ngx_core_module进行调用create_conf()函数的调用. 而create_conf函数的返回值类型是void,我们可以参考”void ngx_core_module_create_conf(ngx_cycle_t cycle)”这个函数,其返回的值类型为”ngx_core_conf_t“,即一个关乎自己模块的自定义的存放配置的结构体,通过create_conf对其进行一些内存分配和初始化的操作.从这里也可以看出来nginx模块设计的一些端倪.

核心级的模块都有:ngx_core_module,ngx_http_module,ngx_openssl_module,ngx_events_module,ngx_errlog_module,ngx_google_perftools_module.

这里执行create_conf的模块有: ngx_core_module,ngx_openssl_module,ngx_google_perftools_module.

init_cycle中随后又调用了ngx_conf_parse函数,解析指定的配置文件, 并调用ngx_conf_handler去执行cmd->set的回调函数,对比上边的源码也就是”ngx_command_t ngx_http_mytest_commands”结构体中的”char*ngx_http_mytest”函数.

这里ngx_conf_parse函数也是相当重要的一个阶段,对各个模块通过解析配置文件进行设置.下面在进行详细的介绍.

随后还是init_cycle函数中:

//ngx_cycle.c:ngx_init_cycle

for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->type != NGX_CORE_MODULE) {
       continue;
    }

    module = ngx_modules[i]->ctx;

    if (module->init_conf) {
        if (module->init_conf(cycle, cycle->conf_ctx[ngx_modules[i]->index])
            == NGX_CONF_ERROR)
        {
            environ = senv;
            ngx_destroy_cycle_pools(&conf);
            return NULL;
        }
    }
}

之后再调用各个核心级模块的ctx->init_conf函数和module->init_module函数.

//ngx_cycle.c:ngx_init_cycle

for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->init_module) {
        if (ngx_modules[i]->init_module(cycle) != NGX_OK) {
            /* fatal */
            exit(1);
        }
    }
}

之上是各个模块在初始化的时候执行的一些函数.下面看下针对于每个要启动的worker进程的一些模块加载.

在ngx_processes_cycle.c:ngx_worker_process_init()函数中:

 
//ngx_processes_cycle.c:ngx_worker_process_init

for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->init_process) {
        if (ngx_modules[i]->init_process(cycle) == NGX_ERROR) {
            /* fatal */
            exit(2);
        }
    }
}

调用每个模块的ngx_modules->init_process函数

以及在进程退出的时候执行exit_process函数和exit_master函数

 
//ngx_process_cycle.c:ngx_worker_process_exit

for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->exit_process) {
        ngx_modules[i]->exit_process(cycle);
    }
}
 
//ngx_process_cycle.c:ngx_master_process_exit

for (i = 0; ngx_modules[i]; i++) {
    if (ngx_modules[i]->exit_master) {
        ngx_modules[i]->exit_master(cycle);
    }
}

在以上的启动过程中并未看到http module和event module的各个子模块的加载,这是因为这些子模块是单独由各个核心模块自己负责的.下面我们再深入的跟踪一下各个子模块是如何被加载的.特别是http module.

###tobe continued…

coding…

#back:

从上次留了这半截儿的文章到现在,一直在淘宝继续着实习.相对专业一点点的从事着nginx的开发工作.从代码风格到一些细节又学习到了很多.之前的代码难免现在看来就有些山寨了.下文继续:

每个core模块大致的加载流程也就是入上文所描述的.但是作为开发,往往是开发core模块下的一些子模块.这些子模块的各种配置与执行过程又由core模块分别负责.这也是nginx在设计上的一个亮点.并且相当的亮.

在ngx_init_cycle中从配置文件的解析开始,下面这段代码已经将所有的配置文件解析完毕了.

/* core/ngx_cycle.c:ngx_init_cycle */

if (ngx_conf_parse(&conf, &cycle->conf_file) != NGX_CONF_OK) {
    environ = senv;
    ngx_destroy_cycle_pools(&conf);
    return NULL;
}

实事上在ngx_conf_parse中调用了ngx_command_t的回调函数,在一些核心模块中往往都是通过这些回调函数再去执行ngx_conf_parse再结合自己的模块结构来解析自己的模块的相关配置的.这里就是诞生了ngx_http_module_t.这个是http core module再次向外提供模块式接口的一个统一数据结构.对于初次做nginx开发时,会认为这些是理所当然的,nginx就是这么提供的.而再去深入一下实现也就会发现这些是由各个core模块独立管理的.并非nginx统筹管理的.绕清楚了这一点,就会觉察到作为开发者也是可以通过nginx的这个机制来为其他开发者来开发基础模块的.

回到http module.在src/http/ngx_http.c文件中, 可以看到:

static ngx_command_t  ngx_http_commands[] = {

    { ngx_string("http"),
      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
      ngx_http_block,
      0,
      0,
      NULL },

      ngx_null_command
};

对于http core模块来讲,它单纯到只有一条配置指令, 该指令的回调函数是ngx_http_block.它同样是会在ngx_conf_parse时被执行到的.也可以看到它的性质是NGX_CONF_BLOCK-这是一个精妙的地方.它是不支持参数的,并且是个大括号.这也就说明了由它来负责”http”指令后面那一个大括号里面的内容.这点也可以在ngx_http_block中得到证明.大段的代码这里就不贴了,也就是在ngx_http_block中又通过ngx_conf_parse解析了http大括号中的内容,同时调用ngx_http_module_t的各个回调函数来创建配置结构体,初始化结构体以及等等的.这里的一切都是http模块的世界.将ngx_http_block这段代码看完基本上问题也都得到了解答了.

最需要强调的一点就是ngx_http_module_t和ngx_http_module不是一个东西.最初草草看代码时心中有太多的疑惑.实事上看下event模块的加载或许更加简捷清楚一些.毕竟http模块有些许复杂.

之前写这篇文章的时候是想带着自己深入的看一下,后来忙于实习,工作也就只看没写.现在回头来补这篇文章而又发现有些无力叙述了,代码就在那里,看吧.这里点出了入口,或许能帮到有需要的人.

hava fun. :)