IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    nginx module Hello world

    yzprofile发表于 2011-10-26 00:00:00
    love 0

    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. :)



沪ICP备19023445号-2号
友情链接