《nginx模块开发指南(中文).pdf》由会员分享,可在线阅读,更多相关《nginx模块开发指南(中文).pdf(39页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、Emiller的的Nginx模块开发指南模块开发指南作者:Evan Miller 草稿:2009年8月 13日 译者:姚伟斌 草稿:2009 年9月21日内容目录内容目录0.预备知识.11.Nginx模块任务委派的主要轮廓.12.Nginx 模块的组成.32.1.模块的配置结构体.32.2.模块的指令.42.3.模块的上下文.62.3.1.创建位置结构体(create_loc_conf).82.3.2.初始化结构体(merge_loc_conf).82.4.模块定义.92.5.模块注册.103.处理模块、过滤模块和 负载均衡模块.103.1.剖析处理模块(非代理).103.1.1.获得位置配置
2、结构体.103.1.2.产生回复.113.1.3.发送HTTP头部.123.1.4.发送HTTP主体.133.2.上游模块剖析(又称代理模块).14i3.2.1.代理模块回调函数的概要.143.2.2.create_request 回调函数.163.2.3.process_header 回调函数.173.2.4.状态保持.183.3.处理模块的注册.194 过滤模块.194.1.剖析头部过滤函数.194.2.剖析主体过滤函数.204.3 过滤函数的注册.225.剖析负载均衡模块.245.1.激活指令.245.2.注册函数.255.3.上游主机初始化函数.275.4.同伴初始化函数.285.5.
3、负载均衡函数.305.6.同伴释放函数.316.完成并编译自定义模块.327.高级话题.33中文版本修改日志.33iiiii翻译说明:在 nginx 的模块编写过程中,时常苦于文档的不足,而源代码中又没多少注释。只有Emiller的这篇英文文档带我入门,在自己研读的过程中,就想将其翻译出来,让其他人能快速的浏览,但是如果你想更深入的进入nginx的代码开发,最好是多读nginx的代码。因其内部的代码是经常改变的,所以本文有可能已经过时。由于本人英语水平一般,接触Nginx时间不多,翻译中碰到的错误在所难免,如果您觉得哪里翻译得不对,请跟我联系:翻译词汇对照表:Backend:后端服务器。Buf
4、fer:缓冲区。Callback:回调函数,一般来说是将某个回调函数赋值给某个函数指针CHAIN OF RESPONSIBILITY:接力链表。Context:上下文,有前后工作环境的意思,主要是前期配置值初始化。Filter:过滤模块/函数,模块和函数的概念似乎有点模糊不清。Handler:处理模块/函数,另外也有指向具体的处理函数的指针或句柄的意思。Installation:原意为安装,我觉得还是译作注册好点。Load-balancer:负载均衡模块/函数。Location:指目录位置,比如http:/ backend。Worker:工作进程,模块真正发挥作用的地方。0.预备知识预备知识对
5、于 C 语言,你应该十分熟悉,对于结构体和预处理命令应有深入的理解,不至于见到大量的指针和函数引用就惊慌失措。如果觉得需要补习,就多看看K&R(C 语言的语法书)。如果你对于HTTP协议已经有了基本概念,那是很有好处的。毕竟你正在Web 服务器上做开发。你应该熟悉Nginx的配置文件。如果不熟悉,也没关系,这里有一些基本理解:Nginx 配置文件主要分成四部分:main(全局设置)、server(主机设置)、upstream(上游服务器设置)和location(URL匹配特定位置后的设置)。每部分包含若干个指令。main部分设置的指令将影响其它所有部分;server部分的指令主要用于指定主机和
6、端口;upstream的指令用于设置后端服务器;location部分用于匹配网页位置(比如,根目录“/”,“/images”等等)。Location部分会继承server 部分的指令,而 server部分的指令会继承main部分;upstream既不继承指令也不会影响其他部分。它有自己的特殊指令,不需要在其他地方的应用。在下面很多地方都会涉及这四个部分,不要忘记哟。让我们开始吧。1.Nginx模块任务委派的模块任务委派的主要轮廓主要轮廓Nginx 模块主要有3 种角色:handlers(处理模块)用于处理HTTP请求,然后产生输出 filters(过滤模块)过滤handler产生的输出 loa
7、d-balancers(负载均衡模块)当有多于一台的后端备选服务器时,选择一台转发HTTP请求模块可以做任何你分配给web服务器的实际工作:当Nginx 发送文件或者转发请求到其他服务器,有处理模块处理模块为其服务;当需要Nginx把输出压缩或者在服务端加一些东西,可以用过滤模块过滤模块;还有一些Nginx的核心模块主要负责管理网络层和应用层协议,以及针对特定应用的一系列模块。Nginx集中式的体系结构让你可以随心所欲地实现一些功能强大的内部单元。注意:Nginx不像apache,模块不是动态添加的(换句话就是说,所有的模块都要预先编译进Nginx的二进制可执行文件)。模块是如何被调用的?典型
8、的讲,当服务器启动,每个处理模块都有机会映射到配置文件中定义的特定位置;如果有多个处理模块映射到同一个位置时,只1有一个会“赢”(聪明如你,当然不会让这些冲突产生)。处理模块主要以三种形式返回:正常、错误、或者放弃处理这个请求而让默认处理模块来处理。如果处理模块把请求反向代理到后端的服务器,就变成另外一类的模块:负载负载均衡模块均衡模块。负载均衡模块的配置中有一组后端服务器,当一个HTTP请求过来时,它决定哪台服务器应当获得这个请求。Nginx的负载均衡模块默认采用两种方法:轮转法,它处理请求的方式就像纸牌游戏一样从头到尾分发;IP哈希法,在众多请求的情况下,它确保来自同一个IP的请求会分发到
9、相同的后端服务器。当然还有一些第三方的负载均衡方法,你可以从这里找到。如果处理模块没有产生错误,过滤模块将被调用。过滤模块能映射到每个位置,而且过滤模块是有先后顺序的,它们的执行顺序在编译时决定。过滤模块是经典的“接力链表(CHAIN OF RESPONSIBILITY)”模型:一个过滤模块被调用,完成其工作,然后调用下一个过滤模块,直到最后一个过滤模块。一般来说压缩模块是比较靠后的,不然压缩以后的内容是很难用来读的。最后,Nginx发出回复。真正 cool的地方是在过滤模块链中,每个过滤模块不会等上一个过滤模块全部完成;它能把前一个过滤模块的输出作为其处理内容;有点像Unix中的流水线。过滤
10、模块能以buffer(缓冲区)为单位进行操作,这些buffer一般都是一页(4K)大小,当然你也可以在nginx.conf文件中进行配置。这意味着,比如模块可以压缩来自后端服务器的回复,然后像流一样的到达客户端,直到整个回复发送完成。所以总结下上面的内容,一个典型的处理周期是这样的:客户端发送 HTTP 请求 -Nginx 根据配置选择一个合适的处理模块 -(如果有)负载均衡模块选择一台后端服务器,并负责完成后端的发送接收过程 -处理模 块进行处理并把输出缓冲放到第一个过滤模块上 -第一个过滤模块处理后输出 给第二个过滤模块 -然后第二个过滤模块又到第三个 -依此类推 -最后把回复 发给客户端
11、。我说“典型”这个词是因为Nginx的模块调用是具有很强的定制性的。模块开发者需要花很多精力精确定义模块在何时如何产生作用(我认为是件不容易的事)。模块的调用事实上通过一系列的回调函数来实现,很多很多。名义上来说,你的函数可在以下时段执行某些功能:当服务读配置文件之前读存在location和 server 或其他任何部分的每一个配置指令当Nginx初始化全局部分的配置时当Nginx初始化主机部分(比如主机/端口)的配置时当Nginx将全局部分的配置与主机部分的配置合并的时候当Nginx初始化位置部分配置的时候当Nginx将其上层主机配置与位置部分配置合并的时候当Nginx的主(master)进
12、程开始的时候当一个新的工作进程(worker)开始的时候2当一个工作进程退出的时候当主进程退出的时候处理请求过滤回复的头部过滤回复的主体选择一台后端服务器初始化到后端服务器的请求重新初始化到后端的服务器的请求处理来自后端服务器的回复完成与后端服务器的交互难以置信,有点应接不暇!有这么多的功能任你处置,而你只需通过多组有用的钩子(由函数指针组成的结构体)和相应的实现函数。现在让我们开始接触一些模块吧。2.Nginx模块的组成模块的组成我说过,Nginx模块的使用是很灵活的。本段描述的东西会经常出现。它引导你理解模块,也可以成为你开始写模块的参考。2.1.模块的配置结构体模块的配置结构体模块的配置
13、结构体的定义有三种,分别是全局,主机和位置的配置结构体。大多数HTTP模块仅仅需要一个位置的配置结构体。名称约定是这样的:ngx_http_(main|srv|loc)_conf_t。这里有一个来自 dav模块的例子:typedef struct 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_con
14、fig.h)这些类型用在配置结构体的情形很多。32.2.模块的指令模块的指令模块的指令出现在静态数组ngx_command_t。这里有一个例子,说明它们是如何被定义的,来自我写的一个小模块:static ngx_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_MAI
15、N_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:struct ngx_command_t ngx_str_t name;ngx_uint_t type;char *(
16、*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,经常用来进行字符串实例化(比如:ngx_str(proxy_pass))。注意:ngx_str_t结构体由包含有字符串的data成员和表示字符串长度的len成员组成。Nginx用这个数据类型来存放字符串。type 是标识的集合,表明这个指令在哪里出现是合法的
17、、指令的参数个数。应用4中,标识一般是下面多个值的位或:NGX_HTTP_MAIN_CONF:指令出现在全局配置部分是合法的NGX_HTTP_SRV_CONF:指令在主机配置部分出现是合法的 NGX_HTTP_LOC_CONF:指令在位置配置部分出现是合法的 NGX_HTTP_UPS_CONF:指令在上游服务器配置部分出现是合法的 NGX_CONF_NOARGS:指令没有参数 NGX_CONF_TAKE1:指令读入一个参数 NGX_CONF_TAKE2:指令读入两个参数.NGX_CONF_TAKE7:指令读入七个参数NGX_CONF_FLAG:指令读入一个布尔型数据 NGX_CONF_1MOR
18、E:指令至少读入1个参数NGX_CONF_2MORE:指令至少读入2个参数 这里有很多另外的选项:core/ngx_conf_file.h。结构体成员set是一个函数指针,用来设定模块的配置;典型地,这个函数会转化读入指令传进来的参数,然后将合适的值保存到配置结构体。这个设定函数有三个参数:1指向 ngx_conf_t结构体的指针,包含从配置文件中指令传过来的参数。2指向当前 ngx_command_t结构体的指针3指向自定义模块配置结构体的指针这个设定函数在指令被遇到的时候就会调用。在自定义的配置结构体中,Nginx提供了多个函数用来保存特定类型的数据,这些函数包含有:5ngx_conf_s
19、et_flag_slot:将on或off转化为 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)。模块也可以把它们自己函数的引用放在这里,但这样内嵌的类型不是很好。那这些内嵌函数怎么知道要把值保存在哪里呢?ngx_command_t接下来的两个成员 conf和 offset正好可用。conf告诉 Nginx把这个值
20、是放在全局配置部分、主机配置部分还是位置配置部分(用NGX_HTTP_MAIN_CONF_OFFSET,NGX_HTTP_SRV_CONF_OFFSET或NGX_HTTP_LOC_CONF_OFFSET)。然后offset确定到底是保存在结构体的哪个位置。最后,post指向模块在读配置的时候需要的一些零碎变量。一般它是NULL。这个 ngx_command_t 数组在读入ngx_null_command 后停止。2.3.模块的上下文模块的上下文静态的 ngx_http_module_t 结构体,包含一大把函数引用。用来创建三个部分的配置和合并配置。一般结构体命名为ngx_http_module
21、_ctx。以此,这些函数引用包括:在读入配置文件前调用在读入配置文件后调用在创建全局部分配置时调用(比如,用来分配空间和设置默认值)在初始化全局部分的配置时调用(比如,把原来的默认值用nginx.conf读到的值来覆盖)在创建主机部分的配置时调用与全局部分配置合并时调用创建位置部分的配置时掉用与主机部分配置合并时调用这些函数参数不同,依赖于它们的功能。这里有这个结构体的定义,摘自http/ngx_http_config.h,你可以看到属性各不同的回调函数:typedef struct 6 ngx_int_t (*preconfiguration)(ngx_conf_t*cf);ngx_int_
22、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_
23、t*cf,void*prev,void*conf);ngx_http_module_t;如果某些函数不用,可以设定为NULL,Nginx会剔除它。大多数处理模块只使用最后两个:一个函数用来为特定的位置部分的配置结构体分配内存(称为ngx_http_create_loc_conf),另外一个函数用来设定默认值和与继承过来的配置合并(称为ngx_http_merge_loc_conf)。这个合并函数负责检验读入的数值是否有效,并设定一些默认值。这里有一个模块上下文结构体的例子:static ngx_http_module_t ngx_http_circle_gif_module_ctx=NULL,
24、/*preconfiguration*/NULL,/*postconfiguration*/NULL,/*create main configuration*/NULL,/*init main configuration*/NULL,/*create server configuration*/NULL,/*merge server configuration*/ngx_http_circle_gif_create_loc_conf,/*create location configuration*/ngx_http_circle_gif_merge_loc_conf/*merge locati
25、on configuration*/;现在开始讲得更深一点。这些配置回调函数看其来很相似,几乎所有模块都一样,而且 Nginx的 API都会用到这个部分,所以值得了解。72.3.1.创建位置结构体(创建位置结构体(create_loc_conf)create_loc_conf函数骨架看起来像这个样子,摘自我写的circle_gif模块(看源代码)。它的参数是结构体ngx_conf_t,返回更新的模块配置结构体(例子中是ngx_http_circle_gif_loc_conf_t)。static void*ngx_http_circle_gif_create_loc_conf(ngx_conf_
26、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)return NGX_CONF_ERROR;conf-min_radius=NGX_CONF_UNSET_UINT;conf-max_radius=NGX_CONF_UNSET_UINT;return conf;第一个需要注意的是Nginx的内存分配,只要使用ngx_palloc(malloc 的包装函数)或ngx_pcalloc(calloc的包装函数)就不
27、需要关心内存的释放。UNSET常量有:NGX_CONF_UNSET_UINT,NGX_CONF_UNSET_PTR,NGX_CONF_UNSET_SIZE,NGX_CONF_UNSET_MSEC,和一起的NGX_CONF_UNSET。UNSET告诉合并函数这些值还没有被初始化过,需要设定其参数。2.3.2.初始化结构体(初始化结构体(merge_loc_conf)这是用在circle_gif模块的合并函数:static char*ngx_http_circle_gif_merge_loc_conf(ngx_conf_t*cf,void*parent,void*child)ngx_http_ci
28、rcle_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);8 if(conf-min_radius max_radius min_radius)ngx_conf_log_error(NGX_LOG_EMERG,cf,0,max_radius must be eq
29、ual or more than min_radius);return NGX_CONF_ERROR;return NGX_CONF_OK;首先需要注意的是Nginx为不同的类型提供了良好的合并函数(ngx_conf_merge_value);这些参数含义是:1、当前参数变量2、如果第一个参数没有被设置3、如果第一个和第二个参数都没有设置时的默认值结果会保存在第一个参数中。有效的合并函数包括ngx_conf_merge_size_value,ngx_conf_merge_msec_value,在core/ngx_conf_file.h 里有完整的列出。有个问题:因为第一个参数是传值的,这些值是
30、如何写到第一个参数中?回答:这些函数其实都是预处理命令(在真正编译之前,它们会被扩展成一些if 语句)。注意错误是如何产生的;这些函数把错误信息写到log文件中,然后返回NGX_CONF_ERROR。返回代码会中止服务的启动。(因为被标识为NGX_LOG_EMERG,这些消息也会被会输出到标准输出;core/ngx_log.h 有完整的日志级别。)2.4.模块定义模块定义第二步,我们间接的介绍更多一层,结构体ngx_module_t。这个结构体变量命名为 ngx_http_module。它包含有模块的主要内容和指令的执行部分,也有一些回调函数(退出线程,退出进程,等等)。这些函数的定义是把数据
31、处理关联到特定模块的关键。模块定义像这个样子:9ngx_module_t ngx_http_module=NGX_MODULE_V1,&ngx_http_module_ctx,/*module context*/ngx_http_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*/NU
32、LL,/*exit master*/NGX_MODULE_V1_PADDING;仅仅替换掉合适的模块名称就可以了。在进程/线程退出的时候,模块可以添加一些回调函数来运行,但大多数模块用不到。(这些回调函数的参数,可以参考core/ngx_conf_file.h.)2.5.模块注册模块注册有两种途径来注册(Installation)模块:处理模块经常是通过指令的回调函数来注册,过滤模块通过模块上下文结构体中的postconfigration回调函数来注册。具体你可以下面具体模块中会提到。3.处理模块、过滤模块和 负载均衡模块处理模块、过滤模块和 负载均衡模块接下来我们把模块的细节放到显微镜下,看
33、它们到底怎么运行的。3.1.剖析处理模块剖析处理模块(非代理非代理)处理模块一般做四样东西:获得位置配置结构体,产生合适的回复,发送HTTP头部和发送HTTP主体。它只有一个变量-请求结构体。这个结构体有很多关于客户端请求的有用信息,比如请求方法(request method),URI和请求头部。我们会一步一步分析整个过程。3.1.1.获得位置配置结构体获得位置配置结构体这部分很简单。所有你需要做的是根据当前的请求结构体和模块定义,调用10ngx_http_get_module_loc_conf,获得当前的配置结构体。这是我写的circle gif hanlder函数的相关部分。static
34、ngx_int_tngx_http_circle_gif_handler(ngx_http_request_t*r)ngx_http_circle_gif_loc_conf_t *circle_gif_config;circle_gif_config=ngx_http_get_module_loc_conf(r,ngx_http_circle_gif_module);.现在我们就能访问在合并函数中初始化的各个变量。3.1.2.产生回复产生回复这部分很有趣,也是模块真正起作用的地方。请求结构体在这里也很有用,特别是这些成员变量:typedef struct./*the memory pool,u
35、sed in the ngx_palloc functions*/ngx_pool_t *pool;ngx_str_t uri;ngx_str_t args;ngx_http_headers_in_t headers_in;.ngx_http_request_t;uri 是请求的路径,比如:/query.cgi。args 是在问号之后请求的参数(比如 name=john)。headers_in 有很多有用的东西,如cookie和浏览器信息。如果你感兴趣,请看这里http/ngx_http_request.h。这里应该有足够的信息来产生有用的输出。ngx_http_request_t结构体的完整
36、定义在http/ngx_http_request.h。113.1.3.发送发送HTTP 头部头部回复头部存在于被称为headers_out的结构体中。它包含在请求结构体中。这个处理函数生成头部变量,然后调用ngx_http_send_header(r)函数,下面列出些有用的部分:typedef stuct.ngx_uint_t status;size_t content_type_len;ngx_str_t content_type;ngx_table_elt_t *content_encoding;off_t content_length_n;time_t date_time;time_t
37、last_modified_time;.ngx_http_headers_out_t;(另外一些你可以在这里找到:http/ngx_http_request.h.)举个例子,如果一个模块把Content-Type需要设定为“image/gif”,Content-Length为100,然后返回200 OK 的回复,代码将是这样的:r-headers_out.status=NGX_HTTP_OK;r-headers_out.content_length_n=100;r-headers_out.content_type.len=sizeof(image/gif)-1;r-headers_out.co
38、ntent_type.data=(u_char*)image/gif;ngx_http_send_header(r);上面的设定方式针对大多数参数都是有效的。但一些头部的变量设定要比上面的例子要麻烦;比如,content_encoding含有类型(ngx_table_elt_t*),这时模块必须为它分配内存。可以用一个叫ngx_list_push的函数来做。它需要传入一个 ngx_list_t变量(与数组类似),然后返回一个list 中的新成员(类型是ngx_table_elt_t)。下面的代码把Content-Encoding设定为“deflate”,然后把头部发出。r-headers_ou
39、t.content_encoding=ngx_list_push(&r-headers_out.headers);if(r-headers_out.content_encoding=NULL)return NGX_ERROR;12 r-headers_out.content_encoding-hash=1;r-headers_out.content_encoding-key.len=sizeof(Content-Encoding)-1;r-headers_out.content_encoding-key.data=(u_char*)Content-Encoding;r-headers_out.
40、content_encoding-value.len=sizeof(deflate)-1;r-headers_out.content_encoding-value.data=(u_char*)deflate;ngx_http_send_header(r);当头部有多个值的时候,这个机制会经常用到;它(理论上讲)使得过滤模块添加、删除某个值而保留其他值的时候更加容易,在操纵字符串的时候,不需要把字符串重新排序。3.1.4.发送发送HTTP 主体主体现在模块已经产生了一个回复,把它放到内存中。需要为回复分配一块特别的buffer,并把这个buffer连接到一个链表,然后调用“send body”函
41、数发送。这些链表有什么用?在Nginx中,处理模块和过滤模块在处理完成后产生的回复都包含在缓冲中,每次产生一个buffer;每个链表成员保存指向下一个成员的指针,如果是最后的buffer,就置为NULL。这里我们简单地假定只有一个buffer 成员。首先,模块声明一块buffer和一条链表:ngx_buf_t *b;ngx_chain_t out;第二步是分配缓冲,然后指向我们的回复数据:b=ngx_pcalloc(r-pool,sizeof(ngx_buf_t);if(b=NULL)ngx_log_error(NGX_LOG_ERR,r-connection-log,0,Failed to
42、allocate response buffer.);return NGX_HTTP_INTERNAL_SERVER_ERROR;b-pos=some_bytes;/*first position in memory of the data*/b-last=some_bytes+some_bytes_length;/*last position*/13 b-memory=1;/*content is in read-only memory*/*(i.e.,filters should copy it rather than rewrite in place)*/b-last_buf=1;/*t
43、here will be no more buffers in the request*/现在模块buffer添加到了链表上:out.buf=b;out.next=NULL;最后,我们把主体发送出去,返回值是output_filter函数对整个链表的返回状态。return ngx_http_output_filter(r,&out);缓冲链表是一个典型的Nginx IO模型,你必须清楚它们是如何工作的。问题:为什么会有变量last_buf,什么时候我们才能说这条链表结束了,并把next 设为NULL?回答:链表可能不完全的,比如,有多个buffer的时候,但是不是所有的buffer 都在这个请
44、求或者回复中。所以一些buffer是链表的结尾,而不是请求的结尾。这意味着模块判断是否是请求的结尾,并设置相应的值。3.2.上游模块剖析(又称代理模块)上游模块剖析(又称代理模块)我已经帮你了解了如何用处理模块来产生回复。你可能用一小块C代码就可以完成,但有时你想和另外一台服务器通信(比如,你写一个模块来实现另一种协议)。你可能需要自己做所有的网络编程。如果收到部分的回复,需要等待余下的回复数据,你会怎么办?你不会想阻塞整个事件处理循环吧?这样会毁掉Nginx 的良好性能!幸运的是,Nginx可以使用某种机制将回复映射到一台后端服务器(叫做“upstream”),你的模块可以与另外的服务器通信
45、而不需要任何请求。这个小节将会描述模块是如何与上游服务器通信,比如Memcached,FastCGI或其它HTTP 服务器。3.2.1.代理模块回调函数的概要代理模块回调函数的概要与其他模块的处理函数不一样,代理模块的处理函数仅作少量的实际工作。它也不会调用ngx_http_output_filter,仅仅设定一些回调函数。这些函数在代理服务等待接受和发送的时候被调用。总共有6个:14create_request 生成发送到上游服务器的请求(或者一条缓冲链)reinit_request 在后端服务器被重置的情况下(在create_request被第二次调用之前)被调用process_heade
46、r 处理上游服务器的回复abort_request 在客户端放弃请求的时候被调用finalize_request 在Nginx完成从上游服务器读入回复并解析完成以后被调用input_filter 这是一个主体过滤函数,在产生回复主体时调用(比如把尾部删掉)这些函数是怎么附加上去的?下面是一个例子,简单版本的代理模块处理函数:static ngx_int_tngx_http_proxy_handler(ngx_http_request_t*r)ngx_int_t rc;ngx_http_upstream_t *u;ngx_http_proxy_loc_conf_t *plcf;plcf=ngx_
47、http_get_module_loc_conf(r,ngx_http_proxy_module);/*set up our upstream struct*/u=ngx_pcalloc(r-pool,sizeof(ngx_http_upstream_t);if(u=NULL)return NGX_HTTP_INTERNAL_SERVER_ERROR;u-peer.log=r-connection-log;u-peer.log_error=NGX_ERROR_ERR;u-output.tag=(ngx_buf_tag_t)&ngx_http_proxy_module;u-conf=&plcf-
48、upstream;/*attach the callback functions*/u-create_request=ngx_http_proxy_create_request;u-reinit_request=ngx_http_proxy_reinit_request;u-process_header=ngx_http_proxy_process_status_line;u-abort_request=ngx_http_proxy_abort_request;u-finalize_request=ngx_http_proxy_finalize_request;r-upstream=u;rc=
49、ngx_http_read_client_request_body(r,15ngx_http_upstream_init);if(rc=NGX_HTTP_SPECIAL_RESPONSE)return rc;return NGX_DONE;这个函数只是些打扫工作,重要的部分在回调函数,另外对于ngx_http_read_client_request_body也要注意。当结束从客户端读以后,这个函数用来设定另外一个回调函数。每个回调函数都是怎么做的?一般来说reinit_request,abort_request,和 finalize_request 会设定或者重新设定一些内部状态,可能只有几行
50、代码。真正的工作是在create_request 和 process_header完成。3.2.2.create_request 回调函数回调函数为了简单起见,我们假定有个上游服务器读入一个字符,然后打印出两个字符。我们的函数会怎么写呢?create_request需要为每个字符分配缓冲区,然后为缓冲分配一条链表,最后把链表挂到upstream结构体。看起来像这样:static ngx_int_tngx_http_character_server_create_request(ngx_http_request_t*r)/*make a buffer and chain*/ngx_buf_t*b