您的位置:首页 > 运维架构 > Nginx

[转]Nginx模块开发入门

2011-10-06 16:23 549 查看
文章来源:http://www.cnblogs.com/leoo2sk/archive/2011/04/19/nginx-module-develop-guide.html

前言

Nginx是当前最流行的HTTPServer之一,根据W3Techs的统计,目前世界排名(根据Alexa)前100万的网站中,Nginx的占有率为6.8%。与Apache相比,Nginx在高并发情况下具有巨大的性能优势。

Nginx属于典型的微内核设计,其内核非常简洁和优雅,同时具有非常高的可扩展性。Nginx最初仅仅主要被用于做反向代理,后来随着HTTP核心的成熟和各种HTTP扩展模块的丰富,Nginx越来越多被用来取代Apache而单独承担HTTPServer的责任,例如目前淘宝内各个部门正越来越多使用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'sGuideToNginxModuleDevelopment》一文,此文十分经典,但是由于Nginx版本的演进,其中少许内容可能有点过时。本文是笔者在研读这篇文章和Nginx源代码的基础上,对自己学习Nginx模块开发的一个总结。本文将通过一个完整的模块开发实例讲解Nginx模块开发的入门内容。

本文将基于Nginx最新的1.0.0版本,操作系统环境为Linux(Ubuntu10.10)。

前言

Nginx提要


Nginx在Linux下的安装与运行


Nginx配置文件基本结构


Nginx模块工作原理概述

Nginx模块开发实战


定义模块配置结构


定义指令


创建合并配置信息


编写Handler


组合NginxModule

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下,则执行如下命令:

viewsourceprint?

1
./configure--prefix=/usr/
local
/nginx
2
make
3
make
install
安装完成后可以直接使用下面命令启动Nginx:

viewsourceprint?

1
/usr/
local
/nginx/sbin/nginx
Nginx默认以Deamon进程启动,输入下列命令:

viewsourceprint?

'target='_blank'>http://localhost/[/code]
1
curl-i
就可以检测Nginx是否已经成功运行。或者也可以在浏览器中输入http://localhost/,应该可以看到Nginx的欢迎页面了。启动后如果想停止Nginx可以使用:

viewsourceprint?

1
/usr/
local
/nginx/sbin/nginx-sstop

Nginx配置文件基本结构

配置文件可以看做是Nginx的灵魂,Nginx服务在启动时会读入配置文件,而后续几乎一切动作行为都是按照配置文件中的指令进行的,因此如果将Nginx本身看做一个计算机,那么Nginx的配置文件可以看成是全部的程序指令。

下面是一个Nginx配置文件的实例:

viewsourceprint?

01
#usernobody;
02
worker_processes8;
03
error_loglogs/error.log;
04
pidlogs/nginx.pid;
05
events{
06
worker_connections1024;
07
}
08
http{
09
includemime.types;
10
default_typeapplication/octet-stream;
11
sendfileon;
12
#tcp_nopushon;
13
keepalive_timeout65;
14
#gzipon;
15
server{
16
listen80;
17
server_namelocalhost;
18
location/{
19
root/home/yefeng/www;
20
indexindex.htmlindex.htm;
21
}
22
#error_page404/404.html;
23
#redirectservererrorpagestothestaticpage/50x.html
24
#
25
error_page500502503504/50x.html;
26
location=/50x.html{
27
roothtml;
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中又会有serverblock,serverblock中可以包含locationblock。

每个层级可以有自己的指令(Directive),例如worker_processes是一个main层级指令,它指定Nginx服务的Worker进程数量。有的指令只能在一个层级中配置,如worker_processes只能存在于main中,而有的指令可以存在于多个层级,在这种情况下,子block会继承父block的配置,同时如果子block配置了与父block不同的指令,则会覆盖掉父block的配置。指令的格式是“指令名参数1参数2…参数N;”,注意参数间可用任意数量空格分隔,最后要加分号。

在开发NginxHTTP扩展模块过程中,需要特别注意的是main、server和location三个层级,因为扩展模块通常允许指定新的配置指令在这三个层级中。

最后要提到的是配置文件是可以包含的,如上面配置文件中“includemime.types”就包含了mine.types这个配置文件,此文件指定了各种HTTPContent-type。

一般来说,一个serverblock表示一个Host,而里面的一个location则代表一个路由映射规则,这两个block可以说是HTTP配置的核心。

下图是Nginx配置文件通常结构图示。





关于Nginx配置的更多内容请参看Nginx官方文档。

Nginx模块工作原理概述

(Nginx本身支持多种模块,如HTTP模块、EVENT模块和MAIL模块,本文只讨论HTTP模块)

Nginx本身做的工作实际很少,当它接到一个HTTP请求时,它仅仅是通过查找配置文件将此次请求映射到一个locationblock,而此location中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做Nginx真正的劳动工作者。通常一个location中的指令会涉及一个handler模块和多个filter模块(当然,多个location可以复用同一个模块)。handler模块负责处理请求,完成响应内容的生成,而filter模块对响应内容进行处理。因此Nginx模块开发分为handler开发和filter开发(本文不考虑load-balancer模块)。下图展示了一次常规请求和响应的过程。





Nginx模块开发实战

下面本文展示一个简单的Nginx模块开发全过程,我们开发一个叫echo的handler模块,这个模块功能非常简单,它接收“echo”指令,指令可指定一个字符串参数,模块会输出这个字符串作为HTTP响应。例如,做如下配置:

viewsourceprint?

1
location/
echo

{
2
echo

"hellonginx"
;
3
}
则访问http://hostname/echo时会输出hellonginx。

直观来看,要实现这个功能需要三步:1、读入配置文件中echo指令及其参数;2、进行HTTP包装(添加HTTP头等工作);3、将结果返回给客户端。下面本文将分部介绍整个模块的开发过程。

定义模块配置结构

首先我们需要一个结构用于存储从配置文件中读进来的相关指令参数,即模块配置信息结构。根据Nginx模块开发规则,这个结构的命名规则为ngx_http_[module-name]_[main|srv|loc]_conf_t。其中main、srv和loc分别用于表示同一模块在三层block中的配置信息。这里我们的echo模块只需要运行在loc层级下,需要存储一个字符串参数,因此我们可以定义如下的模块配置:

viewsourceprint?

1
typedef
struct
{
2
ngx_str_ted;
3
}ngx_http_echo_loc_conf_t;
其中字段ed用于存储echo指令指定的需要输出的字符串。注意这里ed的类型,在Nginx模块开发中使用ngx_str_t类型表示字符串,这个类型定义在core/ngx_string中:

viewsourceprint?

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外,其它三个常用的nginxtype分别为:

viewsourceprint?

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中:

viewsourceprint?

1
struct

ngx_command_s{
2
ngx_str_tname;
3
ngx_uint_ttype;
4
char

*(*set)(ngx_conf_t*cf,ngx_command_t*cmd,
void

*conf);
5
ngx_uint_tconf;
6
ngx_uint_toffset;
7
void

*post;
8
};
其中name是词条指令的名称,type使用掩码标志位方式配置指令参数,相关可用type定义在core/ngx_config_file.h中:

viewsourceprint?

01
#defineNGX_CONF_NOARGS0x00000001
02
#defineNGX_CONF_TAKE10x00000002
03
#defineNGX_CONF_TAKE20x00000004
04
#defineNGX_CONF_TAKE30x00000008
05
#defineNGX_CONF_TAKE40x00000010
06
#defineNGX_CONF_TAKE50x00000020
07
#defineNGX_CONF_TAKE60x00000040
08
#defineNGX_CONF_TAKE70x00000080
09
10
#defineNGX_CONF_MAX_ARGS8
11
12
#defineNGX_CONF_TAKE12(NGX_CONF_TAKE1|NGX_CONF_TAKE2)
13
#defineNGX_CONF_TAKE13(NGX_CONF_TAKE1|NGX_CONF_TAKE3)
14
15
#defineNGX_CONF_TAKE23(NGX_CONF_TAKE2|NGX_CONF_TAKE3)
16
17
#defineNGX_CONF_TAKE123(NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)
18
#defineNGX_CONF_TAKE1234(NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3\
19
|NGX_CONF_TAKE4)
20
21
#defineNGX_CONF_ARGS_NUMBER0x000000ff
22
#defineNGX_CONF_BLOCK0x00000100
23
#defineNGX_CONF_FLAG0x00000200
24
#defineNGX_CONF_ANY0x00000400
25
#defineNGX_CONF_1MORE0x00000800
26
#defineNGX_CONF_2MORE0x00001000
27
#defineNGX_CONF_MULTI0x00002000
其中NGX_CONF_NOARGS表示此指令不接受参数,NGX_CONF_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模块的指令定义:

viewsourceprint?

01
static

ngx_command_tngx_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结束。

参数转化函数的代码为:

viewsourceprint?

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结构:

viewsourceprint?

01
static

ngx_http_module_tngx_http_echo_module_ctx={
02
NULL,
/*preconfiguration*/
03
NULL,
/*postconfiguration*/
04
05
NULL,
/*createmainconfiguration*/
06
NULL,
/*initmainconfiguration*/
07
08
NULL,
/*createserverconfiguration*/
09
NULL,
/*mergeserverconfiguration*/
10
11
ngx_http_echo_create_loc_conf,
/*createlocationconfigration*/
12
ngx_http_echo_merge_loc_conf
/*mergelocationconfigration*/
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模块这个两个函数的代码:

viewsourceprint?

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中:

viewsourceprint?

01
#definengx_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还定义了很多mergevalue的宏用于merge各种数据。它们的行为比较相似:使用prev填充conf,如果prev的数据为空则使用default填充。

编写Handler

下面的工作是编写handler。handler可以说是模块中真正干活的代码,它主要有以下四项职责:

读入模块配置。

处理功能业务。

产生HTTPheader。

产生HTTPbody。

下面先贴出echo模块的代码,然后通过分析代码的方式介绍如何实现这四步。这一块的代码比较复杂:

viewsourceprint?

01
static

ngx_int_t
02
ngx_http_echo_handler(ngx_http_request_t*r)
03
{
04
ngx_int_trc;
05
ngx_buf_t*b;
06
ngx_chain_tout;
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,
"Failedtoallocateresponsebuffer."
);
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中:

viewsourceprint?

01
struct

ngx_http_request_s{
02
uint32_tsignature;
/*"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_ptread_event_handler;
12
ngx_http_event_handler_ptwrite_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
/*ofngx_http_upstream_state_t*/
21
22
ngx_pool_t*pool;
23
ngx_buf_t*header_in;
24
25
ngx_http_headers_in_theaders_in;
26
ngx_http_headers_out_theaders_out;
27
28
ngx_http_request_body_t*request_body;
29
30
time_t

lingering_time;
31
time_t

start_sec;
32
ngx_msec_tstart_msec;
33
34
ngx_uint_tmethod;
35
ngx_uint_thttp_version;
36
37
ngx_str_trequest_line;
38
ngx_str_turi;
39
ngx_str_targs;
40
ngx_str_texten;
41
ngx_str_tunparsed_uri;
42
43
ngx_str_tmethod_name;
44
ngx_str_thttp_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_tphase_handler;
56
ngx_http_handler_ptcontent_handler;
57
ngx_uint_taccess_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,它们分别表示requestheader、responseheader和输出数据缓冲区链表(缓冲区链表是NginxI/O中的重要内容,后面会单独介绍)。

第一步是获取模块配置信息,这一块只要简单使用ngx_http_get_module_loc_conf就可以了。

第二步是功能逻辑,因为echo模块非常简单,只是简单输出一个字符串,所以这里没有功能逻辑代码。

第三步是设置responseheader。Header内容可以通过填充headers_out实现,我们这里只设置了Content-type和Content-length等基本内容,ngx_http_headers_out_t定义了所有可以设置的HTTPResponseHeader信息:

viewsourceprint?

01
typedef
struct
{
02
ngx_list_theaders;
03
04
ngx_uint_tstatus;
05
ngx_str_tstatus_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_tcontent_type;
24
ngx_str_tcharset;
25
u_char*content_type_lowcase;
26
ngx_uint_tcontent_type_hash;
27
28
ngx_array_tcache_control;
29
30
off_tcontent_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类型的参数。

第四步也是最重要的一步是输出Responsebody。这里首先要了解Nginx的I/O机制,Nginx允许handler一次产生一组输出,可以产生多次,Nginx将输出组织成一个单链表结构,链表中的每个节点是一个chain_t,定义在core/ngx_buf.h:

viewsourceprint?

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代码。

组合NginxModule

上面完成了Nginx模块各种组件的开发下面就是将这些组合起来了。一个Nginx模块被定义为一个ngx_module_t结构,这个结构的字段很多,不过开头和结尾若干字段一般可以通过Nginx内置的宏去填充,下面是我们echo模块的模块主体定义:

viewsourceprint?

01
ngx_module_tngx_http_echo_module={
02
NGX_MODULE_V1,
03
&ngx_http_echo_module_ctx,
/*modulecontext*/
04
ngx_http_echo_commands,
/*moduledirectives*/
05
NGX_HTTP_MODULE,
/*moduletype*/
06
NULL,
/*initmaster*/
07
NULL,
/*initmodule*/
08
NULL,
/*initprocess*/
09
NULL,
/*initthread*/
10
NULL,
/*exitthread*/
11
NULL,
/*exitprocess*/
12
NULL,
/*exitmaster*/
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模块的完整代码:

viewsourceprint?

001
/*
002
*Copyright(C)EricZhang
003
*/
004
005
#include<ngx_config.h>

006
#include<ngx_core.h>
007
#include<ngx_http.h>
008
009
/*Moduleconfig*/
010
typedef
struct
{
011
ngx_str_ted;
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_tngx_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
/*Httpcontextofthemodule*/
031
static

ngx_http_module_tngx_http_echo_module_ctx={
032
NULL,
/*preconfiguration*/
033
NULL,
/*postconfiguration*/
034
035
NULL,
/*createmainconfiguration*/
036
NULL,
/*initmainconfiguration*/
037
038
NULL,
/*createserverconfiguration*/
039
NULL,
/*mergeserverconfiguration*/
040
041
ngx_http_echo_create_loc_conf,
/*createlocationconfigration*/
042
ngx_http_echo_merge_loc_conf
/*mergelocationconfigration*/
043
};
044
045
/*Module*/
046
ngx_module_tngx_http_echo_module={
047
NGX_MODULE_V1,
048
&ngx_http_echo_module_ctx,
/*modulecontext*/
049
ngx_http_echo_commands,
/*moduledirectives*/
050
NGX_HTTP_MODULE,
/*moduletype*/
051
NULL,
/*initmaster*/
052
NULL,
/*initmodule*/
053
NULL,
/*initprocess*/
054
NULL,
/*initthread*/
055
NULL,
/*exitthread*/
056
NULL,
/*exitprocess*/
057
NULL,
/*exitmaster*/
058
NGX_MODULE_V1_PADDING
059
};
060
061
/*Handlerfunction*/
062
static

ngx_int_t
063
ngx_http_echo_handler(ngx_http_request_t*r)
064
{
065
ngx_int_trc;
066
ngx_buf_t*b;
067
ngx_chain_tout;
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,
"Failedtoallocateresponsebuffer."
);
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文件,这个文件需要放在和模块源代码文件放在同一目录下。文件内容如下:

viewsourceprint?

1
ngx_addon_name=模块完整名称
2
HTTP_MODULES=
"$HTTP_MODULES模块完整名称"
3
NGX_ADDON_SRCS=
"$NGX_ADDON_SRCS$ngx_addon_dir/源代码文件名"
2、进入Nginx源代码,使用下面命令编译安装

viewsourceprint?

1
./configure--prefix=安装目录--add-module=模块源代码文件目录
2
make
3
makeinstall
这样就完成安装了,例如,我的源代码文件放在/home/yefeng/ngxdev/ngx_http_echo下,我的config文件为:

viewsourceprint?

1
ngx_addon_name=ngx_http_echo_module

2
HTTP_MODULES=
"$HTTP_MODULESngx_http_echo_module"
3
NGX_ADDON_SRCS=
"$NGX_ADDON_SRCS$ngx_addon_dir/ngx_http_echo_module.c"
编译安装命令为:

viewsourceprint?

1
./configure--prefix=/usr/local/nginx--add-module=/home/yefeng/ngxdev/ngx_http_echo
2
make
3
sudomakeinstall
这样echo模块就被安装在我的Nginx上了,下面测试一下,修改配置文件,增加以下一项配置:

viewsourceprint?

1
location/
echo

{
2
echo

"Thisismyfirstnginxmodule!!!"
;
3
}
然后用curl测试一下:

viewsourceprint?

'target='_blank'>http://localhost/[/code]
echo
1
curl-i
echo
结果如下:





可以看到模块已经正常工作了,也可以在浏览器中打开网址,就可以看到结果:





更深入的学习

本文只是简要介绍了Nginx模块的开发过程,由于篇幅的原因,不能面面俱到。因为目前Nginx的学习资料很少,如果读者希望更深入学习Nginx的原理及模块开发,那么阅读源代码是最好的办法。在Nginx源代码的core/下放有Nginx的核心代码,对理解Nginx的内部工作机制非常有帮助,http/目录下有NginxHTTP相关的实现,http/module下放有大量内置http模块,可供读者学习模块的开发,另外在http://wiki.nginx.org/3rdPartyModules上有大量优秀的第三方模块,也是非常好的学习资料。

如有意见建议或疑问欢迎发送邮件至ericzhang.buaa@gmail.com。希望本文对您有所帮助!!!

参考文献

[1]EvanMiller,Emiller'sGuideToNginxModuleDevelopment.http://www.evanmiller.org/nginx-modules-guide.html,2009

[2]http://wiki.nginx.org/Configuration
[3]ClémentNedelcu,NginxHttpServer.PacktPublishing,2010
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: