Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5639993
  • 博文数量: 291
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 7924
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-06 14:28
个人简介

阿里巴巴是个快乐的青年

文章分类

全部博文(291)

文章存档

2018年(21)

2017年(4)

2016年(5)

2015年(17)

2014年(68)

2013年(174)

2012年(2)

分类: Web开发

2013-11-05 22:38:25

一、模块简介
        关于Nginx的整体模块化体系结构可以参看这里,本文将详细介绍handler模块。
        handler模块接收来自客户端的请求并产生输出。在配置文件中使用location指令可以配置content handler模块,当Nginx启动时,每个handler模块都有一次机会把自己关联到对应的location上。
        handler模块处理的结果通常有3中情况:处理成功、处理失败或者拒绝处理。在拒绝处理的情况下,这个location的处理就会由默认的handler模块来进行处理。例如:当请求一个静态文件时,如果关联到这个location上的一个handler模块拒绝处理,就会由默认的ngx_http_static_module模块进行处理,该模块是一个典型的handler模块。

二、模块编写
        这里以一个简单的handle模块——hello handler module为例说明如何编写模块,一个handler模块的编写通常包括如下三个步骤:
1、基本结构
        模块基本结构的编写,包括模块的定义、模块上下文结构、模块的配置结构等。
        (1)配置结构
        每个模块都会提供一些配置指令,以便于用户可以通过配置来控制该模块的行为。配置信息通过定义模块的配置结构来进行存储。
                (A)配置信息
                Nginx的配置信息分成几个作用域或上下文,即main、server、location等,每个配置信息对应不同的数据结构。对于模块配置信息的定义,命名习惯是ngx_http__(main|srv|loc)_conf_t,比如:
                typedef struct
                {
                    ngx_str_t hello_string;
                    ngx_int_t hello_counter;
                } ngx_http_hello_loc_conf_t;
                当然不是每个模块都会在这几个作用域中都提供配置指令,也就不一定都要定义这几个结构了,具体视需求而定。
                (B)配置指令
                一个模块的配置指令是定义在一个静态数组ngx_http_hello_commands中的,该数组的每个元素都是一个5元组,用来描述一个配置项的所有情况,hello handler module的配置指令定义如下:
                static ngx_command_t ngx_http_hello_commands[] = {
                    {
                        ngx_string("hello_string"),
                        NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TALE1,
                        ngx_http_hello_string,
                        NGX_HTTP_LOC_CONF_OFFSET,
                        offsetof(ngx_http_hello_loc_conf_t, hello_string),
                        NULL},

                    {
                        ngx_string("hello_counter"),
                        NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
                        ngx_http_hello_counter,
                        NGX_HTTP_LOC_CONF_OFFSET,
                        offsetof(ngx_http_hello_loc_conf_t, hello_counter),
                        NULL},

                    ngx_null_command
                };
                hello handler module提供2个配置指令,仅可以出现在location指令的作用域中,其中指令hello_string接受一个参数来设置显示的字符串,如果没有参数,那么就用默认的字符串作为响应字符串,指令hello_counter,如果设置为on,则会在响应的字符串后面追加Visted Times字样以统计请求的次数。
        (2)上下文结构
        模块上下文结构是一个ngx_http_module_t类型的静态变量,这个变量实际上是提供了一组回调函数指针,这些函数都将被Nginx在合适的时间进行调用,hello handler module的上下文结构定义如下
        static ngx_http_module_t ngx_http_hello_module_ctx = {
            NULL,                                            /* preconfiguration */
            ngx_http_hello_init,                         /* postconfiguration */

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

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

            ngx_http_hello_create_loc_conf,        /* create location configuration */
            NULL                                              /* merge location configuration */
        };
        hello handler module调用ngx_http_hello_create_loc_conf函数创建本模块位于location block的配置信息存储结构,该函数执行成功后会返回创建的配置对象,失败则返回NULL。在创建和读取该模块的配置信息之后调用ngx_http_hello_init函数。 
        Nginx里面的配置信息都是上下一层层的嵌套的,对于具体某个location的同一个配置,如果当前层次没有定义,那么就使用上层的配置,否则使用当前层次的配置。
        (3)模块定义
        对于开发一个模块来说,我们都需要定义一个ngx_module_t类型的变量来说明这个模块本身的信息,从某种意义上来说,这是这个模块最重要的一个信息,它告诉了Nginx这个模块的一些信息,上面定义的配置信息和模块上下文信息,都是通过这个结构来告诉Nginx的,也就是加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息,hello handler module的模块定义如下:
        ngx_module_t ngx_http_hello_module = {
            NGX_MODULE_V1,
            &ngx_http_hello_module_ctx,        /* module context */
            ngx_http_hello_commands,            /* module directives */
            NGX_HTTP_MODULE,                    /* module type */
            NULL,                                          /* init master */
            NULL,                                          /* init module */
            NULL,                                          /* init process */
            NULL,                                          /* init thread */
            NULL,                                          /* exit thread */
            NULL,                                          /* exit thread */
            NULL,                                          /* exit process */
            NULL,                                          /* exit master */
            NGX_MODULE_V1_PADDING
        };
        模块可以提供一些回调函数给Nginx,当Nginx在创建进程/线程或者结束进程/线程时进行调用,但大多数模块在这些时刻并不需要做什么,所以都简单赋值为NULL。

2、处理函数
        除了创建模块的基本结构外,handler模块还必须提供一个真正的处理函数,这个函数负责对来自客户端请求的真正处理。这个函数的处理,即可以选择自己直接生成内容,也可以选择拒绝处理,由后续的handler去进行处理,或者是选择丢给后续的filter进行处理,这个函数的原型申明如下:
        typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
        其中,参数r是http请求,里面包含请求所有的信息。 该函数处理成功返回NGX_OK,处理发生错误返回NGX_ERROR,拒绝处理(留给后续的handler进行处理)返回NGX_DECLINE。返回NGX_OK也就代表给客户端的响应已经生成好了,否则返回NGX_ERROR就发生错误了。这里处理函数定义如下:
        static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r)
        {
            ...
            ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
            ...
            /* discard request body, since we don't need it here */
            ngx_http_discard_request_body(r);
            ...
            /* allocate a buffer for your response body */
            ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
            ...
            /* send the headers of your response */
            ngx_http_send_header(r);
            ...

            /* send the buffer chain of your response */
            return ngx_http_output_filter(r, &out);
        }
3、挂载函数
        handler模块真正的处理函数通过两种方式挂载到处理过程中,一种方式就是按处理阶段挂载;另外一种挂载方式就是按需挂载,用户可以根据需要选择合适的挂载方式。
        (1)按处理阶段挂载
        为了更精细地控制对客户端请求的处理过程,Nginx把这个处理过程划分成了11个阶段,它们从前到后依次如下:
        NGX_HTTP_POST_READ_PHASE:读取请求内容阶段
        NGX_HTTP_SERVER_REWRITE_PHASE:Server请求地址重写阶段
        NGX_HTTP_FIND_CONFIG_PHASE:配置查找阶段:
        NGX_HTTP_REWRITE_PHASE:Location请求地址重写阶段
        NGX_HTTP_POST_REWRITE_PHASE:请求地址重写提交阶段
        NGX_HTTP_PREACCESS_PHASE:访问权限检查准备阶段
        NGX_HTTP_ACCESS_PHASE:访问权限检查阶段
        NGX_HTTP_POST_ACCESS_PHASE:访问权限检查提交阶段
        NGX_HTTP_TRY_FILES_PHASE:配置项try_files处理阶段
        NGX_HTTP_CONTENT_PHASE:内容产生阶段
        NGX_HTTP_LOG_PHASE:日志模块处理阶段
        一般情况下,我们自定义的模块,大多数是挂载在NGX_HTTP_CONTENT_PHASE阶段的。挂载的动作一般是在模块上下文调用的postconfiguration函数中,即前文的函数ngx_http_hello_init,挂载的代码如下:
        static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf)
        {
            ngx_http_handler_pt *h;
            ngx_http_core_main_conf_t *cmcf;

            cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

            h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
            if (h == NULL) {
                reeturn NGX_ERROR;
            }

            *h = ngx_http_hello_handler;

            return NGX_OK;
        }
        使用这种方式挂载的handler也被称为content phase handlers。

        (2)按需挂载
        当一个请求进来以后,Nginx从NGX_HTTP_POST_READ_PHASE阶段开始依次执行每个阶段中所有handler。执行到 NGX_HTTP_CONTENT_PHASE阶段的时候,如果这个location有一个对应的content handler模块,那么就去执行这个content handler模块真正的处理函数。否则继续依次执行NGX_HTTP_CONTENT_PHASE阶段中所有content phase handlers,直到某个函数处理返回NGX_OK或者NGX_ERROR。
        换句话说,当某个location处理到NGX_HTTP_CONTENT_PHASE阶段时,如果有content handler模块,那么NGX_HTTP_CONTENT_PHASE挂载的所有content phase handlers都不会被执行了。
        但是使用这个方法挂载上去的handler有一个特点是必须在NGX_HTTP_CONTENT_PHASE阶段才能执行到。如果你想自己的handler在更早的阶段执行,那就不要使用这种挂载方式。
        一般情况下,某个模块对某个location进行了处理以后,发现符合自己处理的逻辑,而且也没有必要再调用NGX_HTTP_CONTENT_PHASE阶段的其它handler进行处理的时候,就动态挂载上这个handler。
        下面来看一下使用这种挂载方式的具体例子(摘自Emiller’s Guide To Nginx Module Development)。
        static char *ngx_http_circle_gif(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_circle_gif_handler;

            return NGX_CONF_OK;
        }
        以这种方式挂载的handler也被称为content handler。

三、模块编译
        模块的功能开发完了之后,模块的使用还需要编译才能够执行,下面我们来看下模块的编译和使用。
1、配置
        我们需要把模块的C代码组织到一个目录里,同时需要编写一个config文件,这个config文件的内容就是告诉Nginx的编译脚本,该如何进行编译。hello handler module的config文件的内容如下:
        ngx_addon_name=ngx_http_hello_module
        HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
        NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hello_module.c"
        需要注意的是,如果这个模块的实现有多个源文件,那么依次将这些文件写进变量NGX_ADDON_SRCS里即可。
2、编译
        对于模块的编译,Nginx没有像Apache那样,提供了单独的编译工具,可以在没有Nginx源代码的情况下来单独编译一个模块的代码。Nginx必须去到Nginx的源代码目录里,通过configure指令的参数,来进行编译。下面看一下hello module的configure指令:
        ./configure –prefix=/usr/local/nginx-1.4.0 –add-module=/home/scq/open_source/test_module
        这个示例模块的代码和config文件都放在/home/scq/open_source/test_module这个目录下。
3、使用
        使用一个模块需要根据这个模块定义的配置指令来做,比如在配置文件中http的默认server里面加入如下的配置:
        location /test {
            hello_string scq;
            hello_counter on;
        }
        当我们访问这个地址的时候,就可以看到返回的结果:
        scq Visited Times:1
        如果你访问多次,这个次数是会增加的。


阅读(5802) | 评论(1) | 转发(1) |
给主人留下些什么吧!~~

scq2099yt2013-11-05 22:38:51

文明上网,理性发言...