这里主要通过一个简单的 hello world 示例学习一下 nginx 中 http 模块(Nginx 还支持 event 模块,mail模块等)开发。


我们下面就是开发一个名叫 mytest 的模块,该模块很简单,就是输出 hello world!,如下配置:


   location /mytest {

       ngx_mytest_module;

   }


当访问 curl -i http://localhost:38080/mytest 时输出如下:


[root@dev nginx]# curl -i http://localhost:38080/mytest

HTTP/1.1 200 OK

Server: nginx/1.4.1

Date: Thu, 04 Jul 2013 07:26:49 GMT

Content-Type: text/plain

Content-Length: 13

Connection: keep-alive


hello, world!


1,定义指令


/* Commands */

static ngx_command_t  ngx_http_mytest_commands[] = {

   { ngx_string("ngx_mytest_module"),

     NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,

     ngx_http_mytest,

     NGX_HTTP_LOC_CONF_OFFSET,

     0,

     NULL },


   ngx_null_command

};


这里定义了指令名为 ngx_mytest_module。其中 ngx_command_t 的结构定义如下:


struct ngx_command_s {

   ngx_str_t             name;

   ngx_uint_t            type;

   char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

   ngx_uint_t            conf;

   ngx_uint_t            offset;

   void                 *post;

};


   - name:指令名

   - type:使用掩码位方式配置指令参数,可用配置 types 定义在 core/ngx_conf_file.h 和 http/ngx_http_config.h 中,如下:


------------------------

core/ngx_conf_file.h


#define NGX_CONF_NOARGS      0x00000001

#define NGX_CONF_TAKE1       0x00000002

#define NGX_CONF_TAKE2       0x00000004

#define NGX_CONF_TAKE3       0x00000008

#define NGX_CONF_TAKE4       0x00000010

#define NGX_CONF_TAKE5       0x00000020

#define NGX_CONF_TAKE6       0x00000040

#define NGX_CONF_TAKE7       0x00000080


#define NGX_CONF_ARGS_NUMBER 0x000000ff

#define NGX_CONF_BLOCK       0x00000100

#define NGX_CONF_FLAG        0x00000200

#define NGX_CONF_ANY         0x00000400

#define NGX_CONF_1MORE       0x00000800

#define NGX_CONF_2MORE       0x00001000

#define NGX_CONF_MULTI       0x00000000  /* compatibility */


#define NGX_DIRECT_CONF      0x00010000


#define NGX_MAIN_CONF        0x01000000

#define NGX_ANY_CONF         0x0F000000


------------------------

http/ngx_http_config.h


#define NGX_HTTP_MAIN_CONF        0x02000000

#define NGX_HTTP_SRV_CONF         0x04000000

#define NGX_HTTP_LOC_CONF         0x08000000

#define NGX_HTTP_UPS_CONF         0x10000000

#define NGX_HTTP_SIF_CONF         0x20000000

#define NGX_HTTP_LIF_CONF         0x40000000

#define NGX_HTTP_LMT_CONF         0x80000000


   - set 函数指针,用于指定一个参数转化函数,一般主要用于将配置文件中的相关指令参数转化成需要的格式并存入配置结构体中,因我的示例里没有参数,只是简单的注册了 handler 函数指针


static char* ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

{

       ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, \

                                               ngx_http_core_module);

       fprintf(stderr, "ngx_http_mytest pid=%d\n", getpid());


       /* register hanlder */

       clcf->handler = ngx_http_mytest_handler;


       return NGX_CONF_OK;

}


2,定义模块 Context


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 */


       NULL,           /* create location configration */

       NULL            /* merge location configration */

};


3,编写 Handler


handler 一般主要有 4 个职责:


   - 读入模块配置

   - 处理业务功能

   - 生成 HTTP Header

   - 生成 HTTP Body


static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)

{

   if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))

   {

       return NGX_HTTP_NOT_ALLOWED;

   }


   ngx_int_t rc = ngx_http_discard_request_body(r);

   if (rc != NGX_OK)

   {

       return rc;

   }


   ngx_str_t type = ngx_string("text/plain");

   ngx_str_t response = ngx_string("hello, world!");

   r->headers_out.status = NGX_HTTP_OK;

   r->headers_out.content_length_n = response.len;

   r->headers_out.content_type = type;


   // 发送响应头

   rc = ngx_http_send_header(r);

   if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)

   {

       return rc;

   }


   // 生成响应体

   ngx_buf_t *b;

   b = ngx_create_temp_buf(r->pool, response.len);

   if (b == NULL)

   {

       return NGX_HTTP_INTERNAL_SERVER_ERROR;

   }


   ngx_memcpy(b->pos, response.data, response.len);

   b->last = b->pos + response.len;

   b->last_buf = 1;


   ngx_chain_t out;

   out.buf = b;

   out.next = NULL;


   // 通过 filter 输出(缓冲区中的)响应

   return ngx_http_output_filter(r, &out);

}


对上面用到的几个重要数据结构简单了解了一下:


   1) handler 接收到的 http 请求消息结构定义(ngx_http_request_t,定义于 http/ngx_http_request.h):


struct ngx_http_request_s {

   uint32_t                          signature;         /* "HTTP" */


   ngx_connection_t                 *connection;


   void                            **ctx;

   void                            **main_conf;

   void                            **srv_conf;

   void                            **loc_conf;


   ngx_http_event_handler_pt         read_event_handler;

   ngx_http_event_handler_pt         write_event_handler;


#if (NGX_HTTP_CACHE)

   ngx_http_cache_t                 *cache;

#endif


   ngx_http_upstream_t              *upstream;

   ngx_array_t                      *upstream_states;

                                        /* of ngx_http_upstream_state_t */


   ngx_pool_t                       *pool;

   ngx_buf_t                        *header_in;


   ngx_http_headers_in_t             headers_in;

   ngx_http_headers_out_t            headers_out;


   ngx_http_request_body_t          *request_body;


   time_t                            lingering_time;

   time_t                            start_sec;

   ngx_msec_t                        start_msec;


   ngx_uint_t                        method;

   ngx_uint_t                        http_version;


   ngx_str_t                         request_line;

   ngx_str_t                         uri;

   ngx_str_t                         args;

   ngx_str_t                         exten;

   ngx_str_t                         unparsed_uri;


   ngx_str_t                         method_name;

   ngx_str_t                         http_protocol;


   ngx_chain_t                      *out;

   ngx_http_request_t               *main;

   ngx_http_request_t               *parent;

   ngx_http_postponed_request_t     *postponed;

   ngx_http_post_subrequest_t       *post_subrequest;

   ngx_http_posted_request_t        *posted_requests;


   ngx_int_t                         phase_handler;

   ngx_http_handler_pt               content_handler;

   ngx_uint_t                        access_code;


   ngx_http_variable_value_t        *variables;


#if (NGX_PCRE)

   ngx_uint_t                        ncaptures;

   int                              *captures;

   u_char                           *captures_data;

#endif


   size_t                            limit_rate;


   /* used to learn the Apache compatible response length without a header */

   size_t                            header_size;


   off_t                             request_length;


   ngx_http_upstream_t              *upstream;

   ngx_array_t                      *upstream_states;

                                        /* of ngx_http_upstream_state_t */


   ngx_pool_t                       *pool;

   ngx_buf_t                        *header_in;


   ngx_http_headers_in_t             headers_in;

   ngx_http_headers_out_t            headers_out;


   ngx_http_request_body_t          *request_body;


   ... ...

}


   这个请求结构爆长(超过110个成员变量),只列出了部分,可以通过这个结构访问 url,args,request_body等等常见 HTTP 信息。而这里还需要注意 headers_in,headers_out 和 chain 等字段,它们分别用于表示 request_header,response header 以及 输出数据缓冲区链表。


   2) 在返回 HTTP 响应时主要是设置 headers_out 字段来实现,我们示例只设置了 Content-Type 和 Content-Length 等基本信息,headers_out 类型 ngx_http_headers_out_t 还定义了其他字段:


typedef struct {

   ngx_list_t                        headers;


   ngx_uint_t                        status;

   ngx_str_t                         status_line;


   ngx_table_elt_t                  *server;

   ngx_table_elt_t                  *date;

   ngx_table_elt_t                  *content_length;

   ngx_table_elt_t                  *content_encoding;

   ngx_table_elt_t                  *location;

   ngx_table_elt_t                  *refresh;

   ngx_table_elt_t                  *last_modified;

   ngx_table_elt_t                  *content_range;

   ngx_table_elt_t                  *accept_ranges;

   ngx_table_elt_t                  *www_authenticate;

   ngx_table_elt_t                  *expires;

   ngx_table_elt_t                  *etag;


   ngx_str_t                        *override_charset;


   size_t                            content_type_len;

   ngx_str_t                         content_type;

   ngx_str_t                         charset;

   u_char                           *content_type_lowcase;

   ngx_uint_t                        content_type_hash;


   ngx_array_t                       cache_control;


   off_t                             content_length_n;

   time_t                            date_time;

   time_t                            last_modified_time;

} ngx_http_headers_out_t;


   设置好响应头信息后,可以通过 ngx_http_send_header 来输出头信息。


   3) 下面就是要发送响应body,这里主要涉及了 ngx_chain_t 结构:


struct ngx_chain_s {

   ngx_buf_t    *buf;

   ngx_chain_t  *next;

};

   看结构,可以知道 ngx_chain_t 就是一个单链表,每个节点是 ngx_chain_t 类型,而 buf 为指向实际数据的缓冲区指针,ngx_buf_t 结构定义如下:


struct ngx_buf_s {

   u_char          *pos;

   u_char          *last;

   off_t            file_pos;

   off_t            file_last;


   u_char          *start;         /* start of buffer */

   u_char          *end;           /* end of buffer */

   ngx_buf_tag_t    tag;

   ngx_file_t      *file;

   ngx_buf_t       *shadow;


   ... ...


   /* STUB */ int   num;

};


   这里用到主要是 pos 和 last 指针,分别表示数据在缓冲区中的起始地址与结束地址。如例子中的数据 "hello, world!"。


   4) 下面就是通过 filter 遍历指定的链表(ngx_chain_t)来输出响应体内容


4,组合 Module


ngx_module_t ngx_http_mytest_module = {

   NGX_MODULE_V1,

   &ngx_http_mytest_module_ctx,    /* module context */

   ngx_http_mytest_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 process */

   NULL,                /* exit master */

   NGX_MODULE_V1_PADDING

};


5,模块的编译安装


   1) 模块 config 文件,该文件需要与模块源文件放在同一路径下,如下:


[root@dev ngx_http_mytest]# ll

总用量 8

-rw-r--r--. 1 root root  164 2013-07-04 10:39 config

-rw-r--r--. 1 root root 2521 2013-07-04 15:17 ngx_http_mytest_module.c

[root@dev ngx_http_mytest]#

[root@dev ngx_http_mytest]# more 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"


   2) 进入 Nginx 源码路径,使用下面命令编译安装:


   ./configure --prefix=/root/apps/nginx --add-module=/root/test/ngx_http_mytest

   make

   sudo make install


参考资料:


nginx 模块开发向导:http://www.evanmiller.org/nginx-modules-guide.html

可以参考学习的第三方模块:http://wiki.nginx.org/3rdPartyModules

Nginx 模块开发入门:http://kb.cnblogs.com/page/98352/