Nginx是当前最流行的HTTP Server之一,根据W3Techs的统计,目前世界排名(根据Alexa)前100万的网站中,Nginx的占有率为6.8%。与Apache相比,Nginx在高并发情况下具有巨大的性能优势。
Nginx属于典型的微内核设计,其内核非常简洁和优雅,同时具有非常高的可扩展性。Nginx最初仅仅主要被用于做反向代理,后来随着HTTP核心的成熟和各种HTTP扩展模块的丰富,Nginx越来越多被用来取代Apache而单独承担HTTP Server的责任,例如目前淘宝内各个部门正越来越多使用Nginx取代Apache,据笔者了解,在腾讯和新浪等公司也存在类似情况。
同时,大量的第三方扩展模块也令Nginx越来越强大。例如,由淘宝的工程师清无(王晓哲)和春来(章亦春)所开发的nginx_lua_module可以将Lua语言嵌入到Nginx配置中,从而利用Lua极大增强了Nginx本身的编程能力,甚至可以不用配合其它脚本语言(如PHP或Python等),只靠Nginx本身就可以实现复杂业务的处理。而春来所开发的ngx_openresty更是通过集成LuaJIT等组件,将Nginx本身变成了一个完全的应用开发平台。目前淘宝数据平台与产品部量子统计的产品都是基于ngx_openresty所开发。对ngxin_lua_module或ngx_openresty感兴趣的朋友可以参考我在关键词上给出的链接,后续我也可能会写一些与其有关的文章。
本文将会重点关注Nginx模块开发入门及基础。目前Nginx的学习资料非常少,而扩展模块开发相关的资料几乎只有《Emiller's Guide To Nginx Module Development》一文,此文十分经典,但是由于Nginx版本的演进,其中少许内容可能有点过时。本文是笔者在研读这篇文章和Nginx源代码的基础上,对自己学习Nginx模块开发的一个总结。本文将通过一个完整的模块开发实例讲解Nginx模块开发的入门内容。
本文将基于Nginx最新的1.0.0版本,操作系统环境为Linux(Ubuntu10.10)。
前言
Nginx提要
        Nginx在Linux下的安装与运行
        Nginx配置文件基本结构
        Nginx模块工作原理概述
Nginx模块开发实战
        定义模块配置结构
        定义指令
        创建合并配置信息
        编写Handler
        组合Nginx Module
Nginx模块的安装
Nginx更深入的学习
Nginx参考文献
Nginx提要
开发Nginx扩展当然首要前提是对Nginx有一定的了解,然而本文并不打算详细阐述Nginx的方方面面,诸如Nginx的安装和各种详细配置等内容都可以在Nginx官网的Document中找到,本文在这里只会概括性描述一些后面可能会用到的原理和概念。
Nginx在Linux下的安装与运行
使用Nginx的第一步是下载Nginx源码包,例如1.0.0的下载地址为http://nginx.org/download/nginx-1.0.0.tar.gz。下载完后用tar命令解压缩,进入目录后安装过程与Linux下通常步骤无异,例如我想讲Nginx安装到/usr/local/nginx下,则执行如下命令:
1
./configure --prefix=/usr/local/nginx
2
make
3
make install
安装完成后可以直接使用下面命令启动Nginx:
1
/usr/local/nginx/sbin/nginx
Nginx默认以Deamon进程启动,输入下列命令:
1
curl -i http://localhost/
就可以检测Nginx是否已经成功运行。或者也可以在浏览器中输入http://localhost/,应该可以看到Nginx的欢迎页面了。启动后如果想停止Nginx可以使用:
1
/usr/local/nginx/sbin/nginx -s stop
Nginx配置文件基本结构
配置文件可以看做是Nginx的灵魂,Nginx服务在启动时会读入配置文件,而后续几乎一切动作行为都是按照配置文件中的指令进行的,因此如果将Nginx本身看做一个计算机,那么Nginx的配置文件可以看成是全部的程序指令。
下面是一个Nginx配置文件的实例:
01
#user  nobody;
02
worker_processes  8;
03
error_log  logs/error.log;
04
pid        logs/nginx.pid;
05
events {
06
    worker_connections  1024;
07
}
08
http {
09
    include       mime.types;
10
    default_type  application/octet-stream;
11
    sendfile        on;
12
    #tcp_nopush     on;
13
    keepalive_timeout  65;
14
    #gzip  on;
15
    server {
16
        listen       80;
17
        server_name  localhost;
18
        location / {
19
            root   /home/yefeng/www;
20
            index  index.html index.htm;
21
        }
22
        #error_page  404              /404.html;
23
        # redirect server error pages to the static page /50x.html
24
        #
25
        error_page   500 502 503 504  /50x.html;
26
        location = /50x.html {
27
            root   html;
28
        }
29
    }
30
}
Nginx配置文件是纯文本文件,你可以用任何文本编辑器如vim或emacs打开它,通常它会在nginx安装目录的conf下,如我的nginx安装在/usr/local/nginx,主配置文件默认放在/usr/local/nginx/conf/nginx.conf。
其中“#”表示此行是注释,由于笔者为了学习扩展开发安装了一个纯净的Nginx,因此配置文件没有经过太多改动。
Nginx的配置文件是以block的形式组织的,一个block通常使用大括号“{}”表示。block分为几个层级,整个配置文件为main层级,这是最大的层级;在main层级下可以有event、http等层级,而http中又会有server block,server block中可以包含location block。
每个层级可以有自己的指令(Directive),例如worker_processes是一个main层级指令,它指定Nginx服务的Worker进程数量。有的指令只能在一个层级中配置,如worker_processes只能存在于main中,而有的指令可以存在于多个层级,在这种情况下,子block会继承父block的配置,同时如果子block配置了与父block不同的指令,则会覆盖掉父block的配置。指令的格式是“指令名 参数1  参数2 … 参数N;”,注意参数间可用任意数量空格分隔,最后要加分号。
在开发Nginx HTTP扩展模块过程中,需要特别注意的是main、server和location三个层级,因为扩展模块通常允许指定新的配置指令在这三个层级中。
最后要提到的是配置文件是可以包含的,如上面配置文件中“include mime.types”就包含了mine.types这个配置文件,此文件指定了各种HTTP Content-type。
一般来说,一个server block表示一个Host,而里面的一个location则代表一个路由映射规则,这两个block可以说是HTTP配置的核心。
下图是Nginx配置文件通常结构图示。
关于Nginx配置的更多内容请参看Nginx官方文档。
Nginx模块工作原理概述
(Nginx本身支持多种模块,如HTTP模块、EVENT模块和MAIL模块,本文只讨论HTTP模块)
Nginx本身做的工作实际很少,当它接到一个HTTP请求时,它仅仅是通过查找配置文件将此次请求映射到一个location block,而此location中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做Nginx真正的劳动工作者。通常一个location中的指令会涉及一个handler模块和多个filter模块(当然,多个location可以复用同一个模块)。handler模块负责处理请求,完成响应内容的生成,而filter模块对响应内容进行处理。因此Nginx模块开发分为handler开发和filter开发(本文不考虑load-balancer模块)。下图展示了一次常规请求和响应的过程。
Nginx模块开发实战
下面本文展示一个简单的Nginx模块开发全过程,我们开发一个叫echo的handler模块,这个模块功能非常简单,它接收“echo”指令,指令可指定一个字符串参数,模块会输出这个字符串作为HTTP响应。例如,做如下配置:
1
location /echo {
2
    echo "hello nginx";
3
}
则访问http://hostname/echo时会输出hello nginx。
直观来看,要实现这个功能需要三步:1、读入配置文件中echo指令及其参数;2、进行HTTP包装(添加HTTP头等工作);3、将结果返回给客户端。下面本文将分部介绍整个模块的开发过程。
定义模块配置结构
首先我们需要一个结构用于存储从配置文件中读进来的相关指令参数,即模块配置信息结构。根据Nginx模块开发规则,这个结构的命名规则为ngx_http_[module-name]_[main|srv|loc]_conf_t。其中main、srv和loc分别用于表示同一模块在三层block中的配置信息。这里我们的echo模块只需要运行在loc层级下,需要存储一个字符串参数,因此我们可以定义如下的模块配置:
1
typedef struct {
2
    ngx_str_t  ed;
3
} ngx_http_echo_loc_conf_t;
其中字段ed用于存储echo指令指定的需要输出的字符串。注意这里ed的类型,在Nginx模块开发中使用ngx_str_t类型表示字符串,这个类型定义在core/ngx_string中:
1
typedef struct {
2
    size_t      len;
3
    u_char     *data;
4
} ngx_str_t;
其中两个字段分别表示字符串的长度和数据起始地址。注意在Nginx源代码中对数据类型进行了别称定义,如ngx_int_t为intptr_t的别称,为了保持一致,在开发Nginx模块时也应该使用这些Nginx源码定义的类型而不要使用C原生类型。除了ngx_str_t外,其它三个常用的nginx type分别为:
1
typedef intptr_t        ngx_int_t;
2
typedef uintptr_t       ngx_uint_t;
3
typedef intptr_t        ngx_flag_t;
具体定义请参看core/ngx_config.h。关于intptr_t和uintptr_t请参考C99中的stdint.h或http://linux.die.net/man/3/intptr_t。
定义指令
一个Nginx模块往往接收一至多个指令,echo模块接收一个指令“echo”。Nginx模块使用一个ngx_command_t数组表示模块所能接收的所有模块,其中每一个元素表示一个条指令。ngx_command_t是ngx_command_s的一个别称(Nginx习惯于使用“_s”后缀命名结构体,然后typedef一个同名“_t”后缀名称作为此结构体的类型名),ngx_command_s定义在core/ngx_config_file.h中:
1
struct ngx_command_s {
2
    ngx_str_t             name;
3
    ngx_uint_t            type;
4
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
5
    ngx_uint_t            conf;
6
    ngx_uint_t            offset;
7
    void                 *post;
8
};
其中name是词条指令的名称,type使用掩码标志位方式配置指令参数,相关可用type定义在core/ngx_config_file.h中:
01
#define NGX_CONF_NOARGS      0x00000001
02
#define NGX_CONF_TAKE1       0x00000002
03
#define NGX_CONF_TAKE2       0x00000004
04
#define NGX_CONF_TAKE3       0x00000008
05
#define NGX_CONF_TAKE4       0x00000010
06
#define NGX_CONF_TAKE5       0x00000020
07
#define NGX_CONF_TAKE6       0x00000040
08
#define NGX_CONF_TAKE7       0x00000080
09
 
10
#define NGX_CONF_MAX_ARGS    8
11
 
12
#define NGX_CONF_TAKE12      (NGX_CONF_TAKE1|NGX_CONF_TAKE2)
13
#define NGX_CONF_TAKE13      (NGX_CONF_TAKE1|NGX_CONF_TAKE3)
14
 
15
#define NGX_CONF_TAKE23      (NGX_CONF_TAKE2|NGX_CONF_TAKE3)
16
 
17
#define NGX_CONF_TAKE123     (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)
18
#define NGX_CONF_TAKE1234    (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3   \
19
                              |NGX_CONF_TAKE4)
20
 
21
#define NGX_CONF_ARGS_NUMBER 0x000000ff
22
#define NGX_CONF_BLOCK       0x00000100
23
#define NGX_CONF_FLAG        0x00000200
24
#define NGX_CONF_ANY         0x00000400
25
#define NGX_CONF_1MORE       0x00000800
26
#define NGX_CONF_2MORE       0x00001000
27
#define NGX_CONF_MULTI       0x00002000
其中NGX_CONF_NOARGS表示此指令不接受参数,NGX_CON F_TAKE1-7表示精确接收1-7个,NGX_CONF_TAKE12表示接受1或2个参数,NGX_CONF_1MORE表示至少一个参数,NGX_CONF_FLAG表示接受“on|off”……
set是一个函数指针,用于指定一个参数转化函数,这个函数一般是将配置文件中相关指令的参数转化成需要的格式并存入配置结构体。Nginx预定义了一些转换函数,可以方便我们调用,这些函数定义在core/ngx_conf_file.h中,一般以“_slot”结尾,例如ngx_conf_set_flag_slot将“on或off”转换为“1或0”,再如ngx_conf_set_str_slot将裸字符串转化为ngx_str_t。
conf用于指定Nginx相应配置文件内存其实地址,一般可以通过内置常量指定,如NGX_HTTP_LOC_CONF_OFFSET,offset指定此条指令的参数的偏移量。
下面是echo模块的指令定义:
01
static ngx_command_t  ngx_http_echo_commands[] = {
02
    { ngx_string("echo"),
03
      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
04
      ngx_http_echo,
05
      NGX_HTTP_LOC_CONF_OFFSET,
06
      offsetof(ngx_http_echo_loc_conf_t, ed),
07
      NULL },
08
 
09
      ngx_null_command
10
};
指令数组的命名规则为ngx_http_[module-name]_commands,注意数组最后一个元素要是ngx_null_command结束。
参数转化函数的代码为:
01
static char *
02
ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
03
{
04
    ngx_http_core_loc_conf_t  *clcf;
05
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
06
    clcf->handler = ngx_http_echo_handler;
07
    ngx_conf_set_str_slot(cf,cmd,conf);
08
     
09
    return NGX_CONF_OK;
10
}
这个函数除了调用ngx_conf_set_str_slot转化echo指令的参数外,还将修改了核心模块配置(也就是这个location的配置),将其handler替换为我们编写的handler:ngx_http_echo_handler。这样就屏蔽了此location的默认handler,使用ngx_http_echo_handler产生HTTP响应。
创建合并配置信息
下一步是定义模块Context。
这里首先需要定义一个ngx_http_module_t类型的结构体变量,命名规则为ngx_http_[module-name]_module_ctx,这个结构主要用于定义各个Hook函数。下面是echo模块的context结构:
01
static ngx_http_module_t  ngx_http_echo_module_ctx = {
02
    NULL,                                  /* preconfiguration */
03
    NULL,                                  /* postconfiguration */
04
 
05
    NULL,                                  /* create main configuration */
06
    NULL,                                  /* init main configuration */
07
 
08
    NULL,                                  /* create server configuration */
09
    NULL,                                  /* merge server configuration */
10
 
11
    ngx_http_echo_create_loc_conf,         /* create location configration */
12
    ngx_http_echo_merge_loc_conf           /* merge location configration */
13
};
可以看到一共有8个Hook注入点,分别会在不同时刻被Nginx调用,由于我们的模块仅仅用于location域,这里将不需要的注入点设为NULL即可。其中create_loc_conf用于初始化一个配置结构体,如为配置结构体分配内存等工作;merge_loc_conf用于将其父block的配置信息合并到此结构体中,也就是实现配置的继承。这两个函数会被Nginx自动调用。注意这里的命名规则:ngx_http_[module-name]_[create|merge]_[main|srv|loc]_conf。
下面是echo模块这个两个函数的代码:
01
static void *
02
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
03
{
04
    ngx_http_echo_loc_conf_t  *conf;
05
 
06
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
07
    if (conf == NULL) {
08
        return NGX_CONF_ERROR;
09
    }
10
    conf->ed.len = 0;
11
    conf->ed.data = NULL;
12
 
13
    return conf;
14
}
15
 
16
static char *
17
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
18
{
19
    ngx_http_echo_loc_conf_t *prev = parent;
20
    ngx_http_echo_loc_conf_t *conf = child;
21
 
22
    ngx_conf_merge_str_value(conf->ed, prev->ed, "");
23
 
24
    return NGX_CONF_OK;
25
}
其中ngx_pcalloc用于在Nginx内存池中分配一块空间,是pcalloc的一个包装。使用ngx_pcalloc分配的内存空间不必手工free,Nginx会自行管理,在适当是否释放。
create_loc_conf新建一个ngx_http_echo_loc_conf_t,分配内存,并初始化其中的数据,然后返回这个结构的指针;merge_loc_conf将父block域的配置信息合并到create_loc_conf新建的配置结构体中。
其中ngx_conf_merge_str_value不是一个函数,而是一个宏,其定义在core/ngx_conf_file.h中:
01
#define ngx_conf_merge_str_value(conf, prev, default)                        \
02
    if (conf.data == NULL) {                                                 \
03
        if (prev.data) {                                                     \
04
            conf.len = prev.len;                                             \
05
            conf.data = prev.data;                                           \
06
        } else {                                                             \
07
            conf.len = sizeof(default) - 1;                                  \
08
            conf.data = (u_char *) default;                                  \
09
        }                                                                    \
10
    }
同时可以看到,core/ngx_conf_file.h还定义了很多merge value的宏用于merge各种数据。它们的行为比较相似:使用prev填充conf,如果prev的数据为空则使用default填充。
编写Handler
下面的工作是编写handler。handler可以说是模块中真正干活的代码,它主要有以下四项职责:
读入模块配置。
处理功能业务。
产生HTTP header。
产生HTTP body。
下面先贴出echo模块的代码,然后通过分析代码的方式介绍如何实现这四步。这一块的代码比较复杂:
01
static ngx_int_t
02
ngx_http_echo_handler(ngx_http_request_t *r)
03
{
04
    ngx_int_t rc;
05
    ngx_buf_t *b;
06
    ngx_chain_t out;
07
 
08
    ngx_http_echo_loc_conf_t *elcf;
09
    elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
10
 
11
    if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
12
    {
13
        return NGX_HTTP_NOT_ALLOWED;
14
    }
15
     
16
    r->headers_out.content_type.len = sizeof("text/html") - 1;
17
    r->headers_out.content_type.data = (u_char *) "text/html";
18
 
19
    r->headers_out.status = NGX_HTTP_OK;
20
    r->headers_out.content_length_n = elcf->ed.len;
21
 
22
    if(r->method == NGX_HTTP_HEAD)
23
    {
24
        rc = ngx_http_send_header(r);
25
        if(rc != NGX_OK)
26
        {
27
            return rc;
28
        }
29
    }
30
 
31
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
32
    if(b == NULL)
33
    {
34
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
35
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
36
    }
37
    out.buf = b;
38
    out.next = NULL;
39
 
40
    b->pos = elcf->ed.data;
41
    b->last = elcf->ed.data + (elcf->ed.len);
42
    b->memory = 1;
43
    b->last_buf = 1;
44
    rc = ngx_http_send_header(r);
45
    if(rc != NGX_OK)
46
    {
47
        return rc;
48
    }
49
    return ngx_http_output_filter(r, &out);
50
}
handler会接收一个ngx_http_request_t指针类型的参数,这个参数指向一个ngx_http_request_t结构体,此结构体存储了这次HTTP请求的一些信息,这个结构定义在http/ngx_http_request.h中:
01
struct ngx_http_request_s {
02
    uint32_t                          signature;         /* "HTTP" */
03
 
04
    ngx_connection_t                 *connection;
05
 
06
    void                            **ctx;
07
    void                            **main_conf;
08
    void                            **srv_conf;
09
    void                            **loc_conf;
10
 
11
    ngx_http_event_handler_pt         read_event_handler;
12
    ngx_http_event_handler_pt         write_event_handler;
13
 
14
#if (NGX_HTTP_CACHE)
15
    ngx_http_cache_t                 *cache;
16
#endif
17
 
18
    ngx_http_upstream_t              *upstream;
19
    ngx_array_t                      *upstream_states;
20
                                         /* of ngx_http_upstream_state_t */
21
 
22
    ngx_pool_t                       *pool;
23
    ngx_buf_t                        *header_in;
24
 
25
    ngx_http_headers_in_t             headers_in;
26
    ngx_http_headers_out_t            headers_out;
27
 
28
    ngx_http_request_body_t          *request_body;
29
 
30
    time_t                            lingering_time;
31
    time_t                            start_sec;
32
    ngx_msec_t                        start_msec;
33
 
34
    ngx_uint_t                        method;
35
    ngx_uint_t                        http_version;
36
 
37
    ngx_str_t                         request_line;
38
    ngx_str_t                         uri;
39
    ngx_str_t                         args;
40
    ngx_str_t                         exten;
41
    ngx_str_t                         unparsed_uri;
42
 
43
    ngx_str_t                         method_name;
44
    ngx_str_t                         http_protocol;
45
 
46
    ngx_chain_t                      *out;
47
    ngx_http_request_t               *main;
48
    ngx_http_request_t               *parent;
49
    ngx_http_postponed_request_t     *postponed;
50
    ngx_http_post_subrequest_t       *post_subrequest;
51
    ngx_http_posted_request_t        *posted_requests;
52
 
53
    ngx_http_virtual_names_t         *virtual_names;
54
 
55
    ngx_int_t                         phase_handler;
56
    ngx_http_handler_pt               content_handler;
57
    ngx_uint_t                        access_code;
58
 
59
    ngx_http_variable_value_t        *variables;
60
     
61
    /* ... */
62
}
由于ngx_http_request_s定义比较长,这里我只截取了一部分。可以看到里面有诸如uri,args和request_body等HTTP常用信息。这里需要特别注意的几个字段是headers_in、headers_out和chain,它们分别表示request header、response header和输出数据缓冲区链表(缓冲区链表是Nginx I/O中的重要内容,后面会单独介绍)。
第一步是获取模块配置信息,这一块只要简单使用ngx_http_get_module_loc_conf就可以了。
第二步是功能逻辑,因为echo模块非常简单,只是简单输出一个字符串,所以这里没有功能逻辑代码。
第三步是设置response header。Header内容可以通过填充headers_out实现,我们这里只设置了Content-type和Content-length等基本内容,ngx_http_headers_out_t定义了所有可以设置的HTTP Response Header信息:
01
typedef struct {
02
    ngx_list_t                        headers;
03
 
04
    ngx_uint_t                        status;
05
    ngx_str_t                         status_line;
06
 
07
    ngx_table_elt_t                  *server;
08
    ngx_table_elt_t                  *date;
09
    ngx_table_elt_t                  *content_length;
10
    ngx_table_elt_t                  *content_encoding;
11
    ngx_table_elt_t                  *location;
12
    ngx_table_elt_t                  *refresh;
13
    ngx_table_elt_t                  *last_modified;
14
    ngx_table_elt_t                  *content_range;
15
    ngx_table_elt_t                  *accept_ranges;
16
    ngx_table_elt_t                  *www_authenticate;
17
    ngx_table_elt_t                  *expires;
18
    ngx_table_elt_t                  *etag;
19
 
20
    ngx_str_t                        *override_charset;
21
 
22
    size_t                            content_type_len;
23
    ngx_str_t                         content_type;
24
    ngx_str_t                         charset;
25
    u_char                           *content_type_lowcase;
26
    ngx_uint_t                        content_type_hash;
27
 
28
    ngx_array_t                       cache_control;
29
 
30
    off_t                             content_length_n;
31
    time_t                            date_time;
32
    time_t                            last_modified_time;
33
} ngx_http_headers_out_t;
这里并不包含所有HTTP头信息,如果需要可以使用agentzh(春来)开发的Nginx模块HttpHeadersMore在指令中指定更多的Header头信息。
设置好头信息后使用ngx_http_send_header就可以将头信息输出,ngx_http_send_header接受一个ngx_http_request_t类型的参数。
第四步也是最重要的一步是输出Response body。这里首先要了解Nginx的I/O机制,Nginx允许handler一次产生一组输出,可以产生多次,Nginx将输出组织成一个单链表结构,链表中的每个节点是一个chain_t,定义在core/ngx_buf.h:
1
struct ngx_chain_s {
2
    ngx_buf_t    *buf;
3
    ngx_chain_t  *next;
4
};
其中ngx_chain_t是ngx_chain_s的别名,buf为某个数据缓冲区的指针,next指向下一个链表节点,可以看到这是一个非常简单的链表。ngx_buf_t的定义比较长而且很复杂,这里就不贴出来了,请自行参考core/ngx_buf.h。ngx_but_t中比较重要的是pos和last,分别表示要缓冲区数据在内存中的起始地址和结尾地址,这里我们将配置中字符串传进去,last_buf是一个位域,设为1表示此缓冲区是链表中最后一个元素,为0表示后面还有元素。因为我们只有一组数据,所以缓冲区链表中只有一个节点,如果需要输入多组数据可将各组数据放入不同缓冲区后插入到链表。下图展示了Nginx缓冲链表的结构:
缓冲数据准备好后,用ngx_http_output_filter就可以输出了(会送到filter进行各种过滤处理)。ngx_http_output_filter的第一个参数为ngx_http_request_t结构,第二个为输出链表的起始地址&out。ngx_http_out_put_filter会遍历链表,输出所有数据。
以上就是handler的所有工作,请对照描述理解上面贴出的handler代码。
组合Nginx Module
上面完成了Nginx模块各种组件的开发下面就是将这些组合起来了。一个Nginx模块被定义为一个ngx_module_t结构,这个结构的字段很多,不过开头和结尾若干字段一般可以通过Nginx内置的宏去填充,下面是我们echo模块的模块主体定义:
01
ngx_module_t  ngx_http_echo_module = {
02
    NGX_MODULE_V1,
03
    &ngx_http_echo_module_ctx,             /* module context */
04
    ngx_http_echo_commands,                /* module directives */
05
    NGX_HTTP_MODULE,                       /* module type */
06
    NULL,                                  /* init master */
07
    NULL,                                  /* init module */
08
    NULL,                                  /* init process */
09
    NULL,                                  /* init thread */
10
    NULL,                                  /* exit thread */
11
    NULL,                                  /* exit process */
12
    NULL,                                  /* exit master */
13
    NGX_MODULE_V1_PADDING
14
};
开头和结尾分别用NGX_MODULE_V1和NGX_MODULE_V1_PADDING 填充了若干字段,就不去深究了。这里主要需要填入的信息从上到下以依次为context、指令数组、模块类型以及若干特定事件的回调处理函数(不需要可以置为NULL),其中内容还是比较好理解的,注意我们的echo是一个HTTP模块,所以这里类型是NGX_HTTP_MODULE,其它可用类型还有NGX_EVENT_MODULE(事件处理模块)和NGX_MAIL_MODULE(邮件模块)。
这样,整个echo模块就写好了,下面给出echo模块的完整代码:
001
/*
002
 * Copyright (C) Eric Zhang
003
 */
004
 
005
#include <ngx_config.h>
006
#include <ngx_core.h>
007
#include <ngx_http.h>
008
 
009
/* Module config */
010
typedef struct {
011
    ngx_str_t  ed;
012
} ngx_http_echo_loc_conf_t;
013
 
014
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
015
static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
016
static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
017
 
018
/* Directives */
019
static ngx_command_t  ngx_http_echo_commands[] = {
020
    { ngx_string("echo"),
021
      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
022
      ngx_http_echo,
023
      NGX_HTTP_LOC_CONF_OFFSET,
024
      offsetof(ngx_http_echo_loc_conf_t, ed),
025
      NULL },
026
 
027
      ngx_null_command
028
};
029
 
030
/* Http context of the module */
031
static ngx_http_module_t  ngx_http_echo_module_ctx = {
032
    NULL,                                  /* preconfiguration */
033
    NULL,                                  /* postconfiguration */
034
 
035
    NULL,                                  /* create main configuration */
036
    NULL,                                  /* init main configuration */
037
 
038
    NULL,                                  /* create server configuration */
039
    NULL,                                  /* merge server configuration */
040
 
041
    ngx_http_echo_create_loc_conf,         /* create location configration */
042
    ngx_http_echo_merge_loc_conf           /* merge location configration */
043
};
044
 
045
/* Module */
046
ngx_module_t  ngx_http_echo_module = {
047
    NGX_MODULE_V1,
048
    &ngx_http_echo_module_ctx,             /* module context */
049
    ngx_http_echo_commands,                /* module directives */
050
    NGX_HTTP_MODULE,                       /* module type */
051
    NULL,                                  /* init master */
052
    NULL,                                  /* init module */
053
    NULL,                                  /* init process */
054
    NULL,                                  /* init thread */
055
    NULL,                                  /* exit thread */
056
    NULL,                                  /* exit process */
057
    NULL,                                  /* exit master */
058
    NGX_MODULE_V1_PADDING
059
};
060
 
061
/* Handler function */
062
static ngx_int_t
063
ngx_http_echo_handler(ngx_http_request_t *r)
064
{
065
    ngx_int_t rc;
066
    ngx_buf_t *b;
067
    ngx_chain_t out;
068
 
069
    ngx_http_echo_loc_conf_t *elcf;
070
    elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
071
 
072
    if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
073
    {
074
        return NGX_HTTP_NOT_ALLOWED;
075
    }
076
     
077
    r->headers_out.content_type.len = sizeof("text/html") - 1;
078
    r->headers_out.content_type.data = (u_char *) "text/html";
079
 
080
    r->headers_out.status = NGX_HTTP_OK;
081
    r->headers_out.content_length_n = elcf->ed.len;
082
 
083
    if(r->method == NGX_HTTP_HEAD)
084
    {
085
        rc = ngx_http_send_header(r);
086
        if(rc != NGX_OK)
087
        {
088
            return rc;
089
        }
090
    }
091
 
092
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
093
    if(b == NULL)
094
    {
095
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
096
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
097
    }
098
    out.buf = b;
099
    out.next = NULL;
100
 
101
    b->pos = elcf->ed.data;
102
    b->last = elcf->ed.data + (elcf->ed.len);
103
    b->memory = 1;
104
    b->last_buf = 1;
105
    rc = ngx_http_send_header(r);
106
    if(rc != NGX_OK)
107
    {
108
        return rc;
109
    }
110
    return ngx_http_output_filter(r, &out);
111
}
112
 
113
static char *
114
ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
115
{
116
    ngx_http_core_loc_conf_t  *clcf;
117
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
118
    clcf->handler = ngx_http_echo_handler;
119
    ngx_conf_set_str_slot(cf,cmd,conf);
120
     
121
    return NGX_CONF_OK;
122
}
123
 
124
static void *
125
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
126
{
127
    ngx_http_echo_loc_conf_t  *conf;
128
 
129
    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
130
    if (conf == NULL) {
131
        return NGX_CONF_ERROR;
132
    }
133
    conf->ed.len = 0;
134
    conf->ed.data = NULL;
135
 
136
    return conf;
137
}
138
 
139
static char *
140
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
141
{
142
    ngx_http_echo_loc_conf_t *prev = parent;
143
    ngx_http_echo_loc_conf_t *conf = child;
144
 
145
    ngx_conf_merge_str_value(conf->ed, prev->ed, "");
146
 
147
    return NGX_CONF_OK;
148
}
Nginx模块的安装
Nginx不支持动态链接模块,所以安装模块需要将模块代码与Nginx源代码进行重新编译。安装模块的步骤如下:
1、编写模块config文件,这个文件需要放在和模块源代码文件放在同一目录下。文件内容如下:
1
ngx_addon_name=模块完整名称
2
HTTP_MODULES="$HTTP_MODULES 模块完整名称"
3
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/源代码文件名"
2、进入Nginx源代码,使用下面命令编译安装
1
./configure --prefix=安装目录 --add-module=模块源代码文件目录
2
make
3
make install
这样就完成安装了,例如,我的源代码文件放在/home/yefeng/ngxdev/ngx_http_echo下,我的config文件为:
1
ngx_addon_name=ngx_http_echo_module
2
HTTP_MODULES="$HTTP_MODULES ngx_http_echo_module"
3
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_echo_module.c"
编译安装命令为:
1
./configure --prefix=/usr/local/nginx --add-module=/home/yefeng/ngxdev/ngx_http_echo
2
make
3
sudo make install
这样echo模块就被安装在我的Nginx上了,下面测试一下,修改配置文件,增加以下一项配置:
1
location /echo {
2
    echo "This is my first nginx module!!!";
3
}
然后用curl测试一下:
1
curl -i http://localhost/echo
结果如下:
可以看到模块已经正常工作了,也可以在浏览器中打开网址,就可以看到结果:
更深入的学习
本文只是简要介绍了Nginx模块的开发过程,由于篇幅的原因,不能面面俱到。因为目前Nginx的学习资料很少,如果读者希望更深入学习Nginx的原理及模块开发,那么阅读源代码是最好的办法。在Nginx源代码的core/下放有Nginx的核心代码,对理解Nginx的内部工作机制非常有帮助,http/目录下有Nginx HTTP相关的实现,http/module下放有大量内置http模块,可供读者学习模块的开发,另外在http://wiki.nginx.org/3rdPartyModules上有大量优秀的第三方模块,也是非常好的学习资料。
如有意见建议或疑问欢迎发送邮件至ericzhang.buaa@gmail.com。希望本文对您有所帮助!!!
参考文献
[1] Evan Miller, Emiller's Guide To Nginx Module Development. http://www.evanmiller.org/nginx-modules-guide.html, 2009
[2] http://wiki.nginx.org/Configuration
[3] Clément Nedelcu, Nginx Http Server. Packt Publishing, 2010
本文基于署名-非商业性使用 3.0许可协议发布,欢迎转载,演绎,但是必须保留本文的署名张洋(包含链接),且不得用于商业目的。如您有任何疑问或者授权方面的协商,请与我联系。
分类: [06]unix&linux
标签: nginx, http, http server
绿色通道:好文要顶关注我收藏该文与我联系
T2噬菌体
关注 - 13
粉丝 - 534
荣誉:微软社区精英,推荐博客
+加关注
20 0
(请您对文章做出评价)
? 上一篇:程序设计中的计算复用(Computational Reuse)
? 下一篇:使用SeaJS实现模块化JavaScript开发
Categories: [06]unix&linux
Tags: nginx, http, http server
ADD YOUR COMMENT
32 条回复
#1楼 贺臣      
2011-04-19 13:19
恩 不错。大哥最好写出一系列文章来,这个对于做互联网的来说非常实用
 回复 引用 查看   
#2楼 12ddae4[未注册用户]
2011-04-19 13:35
终于更新了...博主涉及的东西真多..膜拜ing
 回复 引用   
#3楼 joylee      
2011-04-19 16:04
最近几天正好在研究这个,使用很简单,配置方便,功能强大的好东西。
 回复 引用 查看   
#4楼 yjf512      
2011-04-19 17:38
虽然没有研究这个,但是早听过nginx的大名~
 回复 引用 查看   
#5楼 Lanisle      
2011-04-19 17:59
T2的文章总是老长老长精雕细琢的,Mark留位再看内容!
 回复 引用 查看   
#6楼 假正经哥哥      
2011-04-19 19:53
先mark着,后面再看
 回复 引用 查看   
#7楼 testzhangsan      
2011-04-19 20:44
高材生又发博了,不发则已,一发惊人!
 回复 引用 查看   
#8楼 guoqiao      
2011-04-20 08:46
不错
 回复 引用 查看   
#9楼 李锡远      
2011-04-20 09:27
这个确实不错,灵活方便又强大!
 回复 引用 查看   
#10楼 Leepy      
2011-04-20 10:58
非常好,拜读下!最近也在研究Nginx
 回复 引用 查看   
#11楼 s3      
2011-04-20 11:48
赞!
可不可以写一些对客户端进行验证的,
我是想做一个验证模块(具体验证信息是在后端的PHP系统内),
比如:用户1,向 nginx请求,nginx接到请求后,验证用户1是否有权限(权限验证可以存放在nginx中),验证后跳转到后端的PHP系统中。
ngx_http_auth_pam_module模块可以实现,不过这个模块没有与后端的PHP系统进行交互。
 回复 引用 查看   
#12楼 Likwo      
2011-04-20 12:37
虽然现在用不到,留着,收藏下,以后看。
比较喜欢博住把导航也写上,方便找!
 回复 引用 查看   
#13楼 GUO Xingwang      
2011-04-20 13:30
@s3
那就改改这个模块呗
 回复 引用 查看   
#14楼 s3      
2011-04-20 13:32
@GUO Xingwang
nginx内置的变量不清楚,数据跟踪调试不方便。
有没比较方便的数据跟踪调整(比如咋个把变量数据输出到日志文件内)
 回复 引用 查看   
#15楼[楼主] T2噬菌体      
2011-04-20 13:37
@s3
你说的功能,春来的ngx_lua模块支持access_by_lua可以用lua实现acl,不过PHP的暂时没有模块支持,你的想法挺不错,等我有时间可能会实践一下。另外吧变量输出到日志用ngx_log_error就可以了,可以用DEBUG级别。
 回复 引用 查看   
#16楼 嘉兴网站推广[未注册用户]
2011-04-20 16:02
虽然知道博主分享的是好东西,可以还是看的有点晕。在多多学习中
 回复 引用   
#17楼 护肤品品牌排行榜[未注册用户]
2011-04-20 16:02
感谢博主热心分享。
 回复 引用   
#18楼 锅炉软化水设备[未注册用户]
2011-04-20 16:03
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 
015 static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf); 
016 static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 
这一段不是很明白?
 回复 引用   
#19楼 小狗吸尘器[未注册用户]
2011-04-20 16:04
我跟15楼的朋友一样的问题、。
 回复 引用   
#20楼 s3      
2011-04-20 17:57
@T2噬菌体
那我先了解下,以后就关注你了,这个问题难了我半年了,没解决。
多交流!
 回复 引用 查看   
#21楼 tb-hz[未注册用户]
2011-04-22 15:58
很好很强大。顶起来。
 回复 引用   
#22楼 紫竹郎      
2011-04-24 21:14
perfix=/usr/local/nginx
这里应该为prefix 一点小错误。
 回复 引用 查看   
#23楼[楼主] T2噬菌体      
2011-04-24 22:37
@紫竹郎
改了,谢谢。
 回复 引用 查看   
#24楼 yangjun      
2011-04-25 09:48
cool,先收藏,有机会用得上再参考,谢谢。话说,您的文章大部分对我都很帮助,再次感谢。
 回复 引用 查看   
#25楼 Leo      
2011-04-25 10:23
文章真长!
 回复 引用 查看   
#26楼 Quains      
2011-04-26 22:04
从楼主的各个系列的文章中学到很多东西,很多都是刚好感兴趣的方面.thx
 回复 引用 查看   
#27楼 banana.totolv      
2011-05-05 19:14
good
 回复 引用 查看   
#28楼 Raj23[未注册用户]
2011-05-12 12:46
文字写得很好,学习大大了。图也画的很准确,而且美观
请问LZ用什么工具画那几张结构图
 回复 引用   
#29楼 liuhh      
2011-05-14 15:50
呵呵,关注先。。。一直在听说Nginx大名
 回复 引用 查看   
#30楼 Jonathan.yang      
2011-05-21 11:04
看楼主的文章很过隐啊
 回复 引用 查看   
#31楼 heyjude[未注册用户]
2011-06-26 11:50
在什么场景下要用这个,这个又麻烦又不方便测试,一般情况下用php、java、python、ruby等加负载均衡不就够了吗?
 回复 引用   
#32楼[楼主] T2噬菌体      
2011-06-26 14:04
引用
heyjude:在什么场景下要用这个,这个又麻烦又不方便测试,一般情况下用php、java、python、ruby等加负载均衡不就够了吗?
你的问题很难一言两语回答清楚,总之需要用到扩展nginx的地方还是很多的。另外推荐个东西看看吧:openresty.org
 回复 引用 查看