Nginx模块开发指南中文版.docx
- 文档编号:29651681
- 上传时间:2023-07-25
- 格式:DOCX
- 页数:37
- 大小:36.14KB
Nginx模块开发指南中文版.docx
《Nginx模块开发指南中文版.docx》由会员分享,可在线阅读,更多相关《Nginx模块开发指南中文版.docx(37页珍藏版)》请在冰豆网上搜索。
Nginx模块开发指南中文版
Nginx模块开发指南中文版
1.预备知识
你应当比较熟悉C语言。
不光是“C-语法",你起码还得知道结构体和预处理指令,同时保证看到指针和函数引用出现时心里不会发毛。
否则的话,就算信春哥也是没用的,看看K&R吧。
你得对HTTP协议有一定的了解,毕竟你是在和一个webserver打交道。
如果你熟悉Nginx的配置文件就太好不过了。
如果不熟悉,也没关系,这里简单介绍一下,知道概念先:
Nginx配置文件主要分成四部分:
main(全局设置)、server(主机设置)、upstream(上游服务器设置)和location(URL匹配特定位置后的设置)。
每部分包含若干个指令。
main部分设置的指令将影响其它所有设置;server部分的指令主要用于指定主机和端口;upstream的指令用于设置一系列的后端服务器;location部分用于匹配网页位置(比如,根目录“/”,“/images”,等等)。
他们之间的关系式:
server继承main,location继承server;upstream既不会继承指令也不会被继承。
它有自己的特殊指令,不需要在其他地方的应用。
在下面很多地方都会涉及这四个部分,切记。
好了,让我们开始吧。
2.Nginx模块委派概述
Nginx的模块有三种角色:
*handlers处理http请求并构造输出
*filters处理handler产生的输出
*load-balancers当有多于一个的后端服务器时,选择一台将http请求发送过去
许多可能你认为是webserver的工作,实际上都是由模块来完成的:
任何时候,Nginx提供文件或者转发请求到另一个server,都是通过handler来实现的;而当需要Nginx用gzip压缩输出或者在服务端加一些东东的话,filter就派上用场了;Nginx的core模块主要管理网络层和应用层协议,并启动针对特定请求的一系列后续模块。
这种分散式的体系结构使得由你自己来实现强大的内部单元成为了可能。
注意:
不像Apache的模块那样,Nginx的模块都_不是动态链接的。
(换句话说,Nginx的模块都是静态编译的)模块是如何被调用的呢?
典型地说,当server启动时,每一个handler都有机会去处理配置文件中的location定义,如果有多个handler被配置成需要处理某一特定的location时,只有其中一个handler能够“获胜”(掌握正确配置规则的你当然不会让这样的冲突发生啦)。
一个handler有三种返回方式:
正常;错误;放弃处理转由默认的handler来处理(典型地如处理静态文件的时候)。
如果handler的作用是把请求反向代理到后端服务器,那么就是刚才说的模块的第三种角色load-balancer了。
load-balancer主要是负责决定将请求发送给哪个后端服务器。
Nginx目前支持两种load-balancer模块:
round-robin(轮询,处理请求就像打扑克时发牌那样)和IPhash(众多请求时,保证来自同一ip的请求被分发的同一个后端服务器)。
如果handler返回(译者注:
就是http响应,即filter的输入)正确无误,那么fileter就被调用了。
每个location配置里都可以添加多个filter,所以说(比如)响应可以被压缩和分块。
多个filter的执行顺序是编译时就确定了的。
filter采用了经典的“接力链表(CHAINOFRESPONSIBILITY)”模式:
一个filter被调用并处理,接下来调用下一个filter,直到最后一个filter被调用完成,Nginx才真正完成响应流程。
最帅的部分是在filter链中,每个filter不会等待之前的filter完全完工,它可以处理之前filter正在输出的内容,这有一点像Unix中的管道。
Filter的操作都基于buffers_,buffer通常情况下等于一个页的大小(4k),你也可以在nginx.conf里改变它的大小。
这意味着,比如说,模块可以在从后端服务器收到全部的响应之前,就开始压缩这个响应并流化(streamto)给客户端了。
好牛逼啊~总结一下上面的内容,一个典型的周期应当是这样的:
客户端发送HTTPrequest→Nginx基于location的配置选择一个合适的handler→(如果有)load-balancer选择一个后端服务器→Handler处理请求并顺序将每一个响应buffer发送给第一个filter→第一个filter讲输出交给第二个filter→第二个给第三个→第三个给第四个→以此类推→最终响应发送给客户端我之所以说“典型地”是因为Ngingx的模块具有很强的定制性。
模块开发者需要花很多精力精确定义模块在何时如何产生作用(我认为是件不容易的事)。
模块调用实际上是通过一系列的回调函数做到的,很多很多。
名义上来说,你的函数可以在以下时候被执行:
*server读取配置文件之前
*读取location和server的每一条配置指令
*当Nginx初始化main配置段时
*当Nginx初始化server配置段时(例如:
host/port)
*当Nginx合并server配置和main配置时
*当Nginx初始化location配置时
*当Nginx合并location配置和它的父server配置时
*当Nginx的主进程启动时
*当一个新的worker进程启动时
*当一个worker进程退出时
*当主进程退出时
*handle一个请求
*Filter响应头
*Filter响应体
*选择一个后端服务器
*初始化一个将发往后端服务器的请求
*重新-初始化一个将发往后端服务器的请求
*处理来自后端服务器的响应
*完成与后端服务器的交互
难以置信!
有这么多的功能任你处置,而你只需仅仅通过多组有用的钩子(由函数指针组成的结构体)和相应的实现函数。
让我们开始接触一些模块吧。
3.Nginx模块的组成
我说过,Nginx模块的构建是很灵活的。
这一节讲描述的东西会经常出现。
它可以帮助你理解模块,也可以作为开发模块的手册。
3.1.模块配置Struct(s)
模块的配置struct有三种,分别是main,server和location。
绝大多数模块仅需要一个location配置。
名称约定如下:
ngx_http_
typedefstruct{
ngx_uint_t methods;
ngx_flag_t create_full_put_path;
ngx_uint_t access;
}ngx_http_dav_loc_conf_t;
注意到上面展示了Nginx的一些特殊类型:
(ngx_uint_t和ngx_flag_t);这些只是基本类型的别名而已。
(如果想知道具体是什么的别名,可以参考core/ngx_config.h).这些类型用在配置结构体中的情形很多。
3.2.模块指令
模块的指令是定义在一个叫做ngx_command_t的静态数组中的。
下面举个我自己写的小模块中的例子,来告诉你模块指令是如何声明的:
staticngx_command_t ngx_http_circle_gif_commands[]={
{ngx_string("circle_gif"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_circle_gif,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL},
{ngx_string("circle_gif_min_radius"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_conf_set_num_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_circle_gif_loc_conf_t,min_radius),
NULL},
...
ngx_null_command
};
下面是结构体ngx_command_t(静态数组里的每一个元素)的定义,你可以在core/ngx_conf_file.h找到它:
structngx_command_t{
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是指令的字符串(顾名思义就是指令名称),不能包含空格.它的类型是ngx_str_t,通常都是以像(e.g.)ngx_str("proxy_pass")这样的方式来实例化.注意:
ngx_str_t包含一个存放字符串内容的data字段,和一个存放字符串长度的len字段。
Nginx广泛地使用这个类型来存放字符串。
结构体成员type是标识的集合,表明这个指令在哪里出现是合法的、指令的参数有几个。
应用中,标识一般是下面多个值的BIT或:
*NGX_HTTP_MAIN_CONF:
指令出现在main配置部分是合法的
*NGX_HTTP_SRV_CONF:
指令在server配置部分出现是合法的config
*NGX_HTTP_LOC_CONF:
指令在location配置部分出现是合法的
*NGX_HTTP_UPS_CONF:
指令在upstream配置部分出现是合法的
*NGX_CONF_NOARGS:
指令没有参数
*NGX_CONF_TAKE1:
指令读入1个参数
*NGX_CONF_TAKE2:
指令读入2个参数
*...
*NGX_CONF_TAKE7:
指令读入7个参数
*NGX_CONF_FLAG:
指令读入1个布尔型数据("on"or"off")
*NGX_CONF_1MORE:
指令至少读入1个参数
*NGX_CONF_2MORE:
指令至少读入2个参数
这里还有很多其他的选项:
core/ngx_conf_file.h.结构体成员set是一个函数指针,它指向的函数用来进行模块配置;这个设定函数一般用来将配置文件中的参数传递给程序,并保存在配置结构体中。
设定函数有三个入参:
1.指向结构体ngx_conf_t的指针,这个结构体里包含需要传递给指令的参数
2.指向结构体ngx_command_t的指针
3.指向模块自定义配置结构体的指针
设定函数会在遇到指令时执行,Nginx提供了多个函数用来保存特定类型的数据,这些函数包含有:
*ngx_conf_set_flag_slot:
将"on"or"off"转换成1or0
*ngx_conf_set_str_slot:
将字符串保存为ngx_str_t
*ngx_conf_set_num_slot:
解析一个数字并保存为int
*ngx_conf_set_size_slot:
解析一个数据大小(如:
"8k","1m")并保存为size_t
当然还有其他的,在core/ngx_conf_file.h中很容易查到。
如果你觉得现有这些内置的函数还不能满足你,当然也可以传入自己的函数引用。
这些内置函数是如何知道把数据存放在哪里的呢?
这就是接下来两个结构体成员conf和offset要做的事了.conf告诉Nginx把数据存在模块的哪个配置中,是main配置、server配置,还是location配置?
(通过NGX_HTTP_MAIN_CONF_OFFSET,NGX_HTTP_SRV_CONF_OFFSET,或者NGX_HTTP_LOC_CONF_OFFSET).offset确定到底是保存在结构体的哪个位置。
最后,post指向模块在读配置的时候需要的一些零碎变量。
一般它是NULL。
ngx_command_t数组以ngx_null_command为终结符(就好像字符串以'\0'为终结符一样).
3.3.模块上下文
静态的ngx_http_module_t结构体,包含一大坨函数引用,用来创建和合并三段配置(main,server,location),命名方式一般是:
ngx_http_
*preconfiguration在读入配置前调用
*postconfiguration在读入配置后调用
*create_main_conf在创建main配置时调用(比如,用来分配空间和设置默认值)
*init_main_conf在初始化main配置时调用(比如,把原来的默认值用nginx.conf读到的值来覆盖)
*init_main_conf在创建server配置时调用
*merge_srv_conf合并server和main配置时调用
*create_loc_conf创建location配置时调用
*merge_loc_conf合并location和server配置时调用
函数的入参各不相同,取决于他们具体要做的事情。
这里http/ngx_http_config.h是结构体的具体定义:
typedefstruct{
ngx_int_t (*preconfiguration)(ngx_conf_t*cf);
ngx_int_t (*postconfiguration)(ngx_conf_t*cf);
void *(*create_main_conf)(ngx_conf_t*cf);
char *(*init_main_conf)(ngx_conf_t*cf,void*conf);
void *(*create_srv_conf)(ngx_conf_t*cf);
char *(*merge_srv_conf)(ngx_conf_t*cf,void*prev,void*conf);
void *(*create_loc_conf)(ngx_conf_t*cf);
char *(*merge_loc_conf)(ngx_conf_t*cf,void*prev,void*conf);
}ngx_http_module_t;
可以把你不需要的函数设置为NULL,Nginx会忽略掉他们。
绝大多数的handler只使用最后两个:
一个用来为特定location配置来分配内存,(叫做ngx_http_
合并函数同时还会检查配置的有效性,如果有错误,则server的启动将被挂起。
下面是一个使用模块上下文结构体的例子:
staticngx_http_module_t ngx_http_circle_gif_module_ctx={
NULL, /*preconfiguration*/
NULL, /*postconfiguration*/
NULL, /*createmainconfiguration*/
NULL, /*initmainconfiguration*/
NULL, /*createserverconfiguration*/
NULL, /*mergeserverconfiguration*/
ngx_http_circle_gif_create_loc_conf, /*createlocationconfiguration*/
ngx_http_circle_gif_merge_loc_conf/*mergelocationconfiguration*/
};
现在开始讲得更深一点。
这些配置回调函数看其来很像,所有模块都一样,而且Nginx的API都会用到这个部分,所以值得好好看看。
3.3.1.create_loc_conf
下面这段摘自我自己写的模块circle_gif(源代码),create_loc_conf的骨架大概就是这个样子.它的入参是(ngx_conf_t),返回值是更新了的模块配置结构体(在这里是ngx_http_circle_gif_loc_conf_t).
staticvoid*
ngx_http_circle_gif_create_loc_conf(ngx_conf_t*cf)
{
ngx_http_circle_gif_loc_conf_t *conf;
conf=ngx_pcalloc(cf->pool,sizeof(ngx_http_circle_gif_loc_conf_t));
if(conf==NULL){
returnNGX_CONF_ERROR;
}
conf->min_radius=NGX_CONF_UNSET_UINT;
conf->max_radius=NGX_CONF_UNSET_UINT;
returnconf;
}
首先需要指出的是Nginx的内存分配;只要使用了ngx_palloc(malloc的一个包装函数)或者ngx_pcalloc(calloc的包装函数),就不用担心内存的释放了。
(TODO:
toseewhy?
)UNSET可能的常量有NGX_CONF_UNSET_UINT,NGX_CONF_UNSET_PTR,NGX_CONF_UNSET_SIZE,NGX_CONF_UNSET_MSEC,以及无所不包的NGX_CONF_UNSET,UNSET让合并函数知道哪些变量是需要覆盖的。
3.3.2.merge_loc_conf
下面的例子是我的模块circle_gif中的合并函数:
staticchar*
ngx_http_circle_gif_merge_loc_conf(ngx_conf_t*cf,void*parent,void*child)
{
ngx_http_circle_gif_loc_conf_t*prev=parent;
ngx_http_circle_gif_loc_conf_t*conf=child;
ngx_conf_merge_uint_value(conf->min_radius,prev->min_radius,10);
ngx_conf_merge_uint_value(conf->max_radius,prev->max_radius,20);
if(conf->min_radius<1){
ngx_conf_log_error(NGX_LOG_EMERG,cf,0,
"min_radiusmustbeequalormorethan1");
returnNGX_CONF_ERROR;
}
if(conf->max_radius
ngx_conf_log_error(NGX_LOG_EMERG,cf,0,
"max_radiusmustbeequalormorethanmin_radius");
returnNGX_CONF_ERROR;
}
returnNGX_CONF_OK;
}
这里的需要注意的是Nginx提供了一些好用的合并函数用来合并不同类型的数据(ngx_conf_merge_
1.当前location的变量值
2.如果第一个参数没有被设置而采用的值
3.如果第一第二个参数都没有被设置而采用的值
结果会被保存在第一个参数中。
能用的合并函数包括ngx_conf_merge_size_value,ngx_conf_merge_msec_value等等.可参见core/ngx_conf_file.h.
问:
第一个参数是传值的,那如何能做到将结果保存到第一个参数中?
答:
这些函数都是由预处理命令定义的(在真正编译之前,它们会被扩展成一些if语句)
同时还需要注意的是错误的产生。
函数会往log文件写一些东
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Nginx 模块 开发 指南 中文版