请教nginx模块无法设置nginx 修改responsee

yangji008 的BLOG
用户名:yangji008
文章数:1075
评论数:22
访问量:621077
注册日期:
阅读量:5863
阅读量:12276
阅读量:320804
阅读量:1031729
51CTO推荐博文
&&&&在Nginx中提供了一个简单的负载均衡模块,它就是&ngx_http_upstream_module 模块,它的原理是基于客户端IP的轮询。因此对于要代理多台后台服务器来说是一个不错的选作。&&&&upstream模块,将使nginx跨越单机的限制,完成网络数据的接收、处理和转发。配置示例upstream&backend&{
&&&&server&&&&&&&&weight=5;
&&&&server&:8080;
&&&&server&unix:/tmp/backend3;
&&&&server&:8080&&&
&&&&server&:8080&&&
&&&&location&/&{
&&&&&&&&proxy_pass&http://
}指令:upstream语法:&upstream&name&{&...&}
默认值:&&&&―
使用环境:&&&&http该指令是一个组合性指令,它定义了一组服务器,这组服务器会被指令proxy_pass 和 fastcgi_pass 作为单独的实体来调用,他们可以将server监听在不同端口的端口,而且还可以同时使用tcp unix套接字监听。举例:upstream&backend&{
&&&&server&&weight=5;
&&&&server&127.0.0.1:8080&&&&&&&max_fails=3&fail_timeout=30s;
&&&&server&unix:/tmp/backend3;
&&&&server&&&
}对于这个配置,请求将会按照轮询的方式将其分配,当然也会根据服务器的权重分配,例如:假设我们现在有7个请求,那么5个请求将会被发送到 上,剩余的2个请求 127.0.0.1:8080 和&unix:/tmp/backend3 各承担一个。当然这是在理想状况下,即使我们这些server都是健康的,如果有一个出现无法连接,那么就会按照顺序重新选择,指导请求完成。如果这三台server 都宕机,那么这个配置也就无法提供访问了。server 指令语法:&server&address&[parameters];
默认值:&&&&―
使用环境:&upstream该指令用于设置服务器的name ,对于name的格式 可以使用域名、ip地址、端口或者是unix套接字,如果一个域名被解析到多个IP地址,那么所有的IP地址都将会被使用。对于parameters 参数如下:&&&&weight=NUMBER:用于设置服务器的权重,如果没有设置,那么默认值为1 。&&&&max_fails=NUMBER:该参数用于设置在一定时间内客户端对同一后台服务器可以进行尝试连接的次数,如果在这个次数后仍然没连接成功,那么该服务器将被看做无效。如果没有设置该参数,那么默认值为1 ,如果设置次数为0 ,那么将会关闭检测。 &那么什么被看做失败,也就是判断失败的标准,是由proxy_next_upstream 或者 fastcgi_next_upstream 的设置来决定的,因此对于客户端访问后端服务器出现404错误,将不算为连接失败。&&&&fail_timeout=TIME:该参数是max_fails 的后续参数,定义客户端尝试连接后端服务器的全部最大超时时间,在这个时间内将会完成max_fails设置的最多次数,如果没有设置该参数,默认值为10s 。&&&&down:如果某一个server设置了该参数,那么就相当于标记了这台服务器将永久离线。通常该参数搭配ip_hash一起使用。&&&&backup:该参数在0.6.7版本中提供,它是一个备用功能参数,如果出现所有的非备用服务器宕机或者繁忙无法接受连接时,那么才会使用本服务器。需要注意的是,该参数无法和ip_hash 指令一起使用。&&&&max_conns=NUMBER:表示指定的服务器最大连接数限制(nginx1.5.9以上版本才有的参数),默认值为0,代表没有限制。&&&&resolve:对于server 使用的域名格式,自动检测域名解析记录更改,自动应用上游IP配置。(ngixn 1.5.12版本添加)&&&&route=string:指定服务器的 fqdn ? &不太清楚&&&&slow_start=time:设置一台不健康主机到健康主机的切换时间。默认值为0,也就是禁用慢启动。zone 指令语法:&zone&name&
默认值:&&&&―
使用环境:&upstream使群动态可配。定义持有群的配置和工作进程之间共享的运行时状态的共享内存区域的名字和大小。这样的群允许在运行时添加、删除和修改服务器。这个配置通过 upstream_conf 进行访问。&这一指令仅作为我们商业订购的一部分。hash 指令语法:&hash&key&[consistent];
默认值:&&&&―
使用环境:&&&&upstream
该指令在1.7.2版本中添加。指定的负载平衡的方法,其中所述 客户端 - 服务器 的映射是基于散列键值的服务器组。key可以包含文本,变量,和它们的组合。需要注意的是从下组添加或删除一个服务器可能导致重新映射的大部分按键,以不同的服务器。该方法是用缓存:: Memcached的Perl库兼容。如果指定了一致的参数是ketama一致的散列法将被用来代替。该方法可确保只有几个键将被重新映射到不同的服务器上时,服务器被添加到或从组中删除。这有助于实现较高的高速缓存命中率来用于高速缓存服务器。该方法是用缓存兼容:: Memcached的快速:: Perl库与ketama_points参数设置为160。ip_hash指令语法:&ip_
默认值:&―
使用环境:&upstream如果使用了该指令,那么将会导致客户端的请求以客户端的IP地址分布在upstream中的server之间。它的关键技术在于对这个请求客户端IP地址进行hash计算,这种方法保证了客户端请求总是能够传递到同一台后台服务器,但是如果该服务器被认定为无效,那么这个客户端的请求将会被传递到其他服务器,因此,这种机制是一个高概率将客户端请求总是连接到同一台服务器。另外,如果使用这个指令,那么就不能使用权重 weight 方法,如果一个在upstream中指定的一台server,这个服务器需要移除(比如服务器下线维修),那么需要在该ip或者机器名后添加down参数 。例如;upstream&backend&{
&&&&server&;
&&&&server&;
&&&&server&&
&&&&server&;
}keepalive语法:&keepalive&
默认值:&&&&―
使用环境:&upstream
该指令在1.1.4版本中添加。该指令设定连接到后端服务器的时候是否使用keepalive 长连接,减少创建连接的消耗,提升效率。但是,nginx默认采用http1.0协议,如果后端没有返回Connection:keepalive的header,设置的长连接是不生效的,大并发下会出现大量time_wait的连接。两种解决方案:1、后台服务在response header中加Connetion:keepalive2、在nginx配置中显示指明http1.1协议(默认长连接),并且设置header Connnction=""例子:location&~&/XXX&{&
&&&&&&&....
&&&&&&&proxy_http_version&1.1;
&&&&&&&proxy_set_header&Connection&"";
}(很明显方案2成本更低)least_conn语法:&least_
默认值:&&&&―
使用环境:&&upstream
该指令在1.3.1&&和&1.2.2版本中被添加。upstream&最少连接负载均衡算法,简单来说就是每次选择的都是当前最少连接的一个server(这个最少连接不是全局的,是每个进程都有自己的一个统计列表)。least_time语法:&least_time&header&|&last_
默认值:&&&&―
使用环境:&upstream
该指令在1.7.10版本中被添加。指定一组应当使用的负载平衡方法,其中请求被传递到服务器的至少平均响应时间和最少数量的活动连接,考虑到服务器的帐户的权重。如果有几个这样的服务器,它们依次尝试使用加权循环平衡的方法。如果所指定的报头参数,时间以接收响应标头被使用。如果指定了last_byte参数,及时接收完整的响应被使用。这个指令可以作为我们的商业订阅的一部分。health_check语法:&health_check&[parameters];
默认值:&&―
使用环境:&location激活定期健康性检查。支持以下参数:interval=time:两次连续性健康检查的间隔时间,默认为 5 秒;fails=number:设置连续性健康检查失败的次数,超过这个次数的话这台服务器就被认为是不健康的,默认为 1;passes=number:设置连续性健康检查通过的次数,超过这个次数的话这台服务器就被认为是健康的,默认为 1;uri=uri:定义健康检查请求用的 URI,默认为 "/";match=name:定义健康检查返回通过的匹配块的配置;默认情况下,返回应该具有状态码 2XX 或者 3XX。举例:location&/&{&&
&&&&proxy_pass&http://&&
&&&&health_&&
}将会每隔五秒钟发送 "/" 到 backend 群的每个服务器。如果有任何通信错误或者超时发生,或者代理服务器返回为 2XX 或者 3XX 之外的状态码,健康检查失败,这台服务器就被认为是不健康的了。来自客户端的请求不会被传递给不健康的服务器。健康检查可以被配置成测试返回的状态码,头字段以及其值,还有正文内容。测试单独地使用 match 参数引用到的 match 指令进行配置,例如:http&{&&
&&&&server&{&&
&&&&&&&&location&/&{&&
&&&&&&&&&&&&proxy_pass&http://&&
&&&&&&&&&&&&health_check&match=&&
&&&&&&&&}&&
&&&&match&welcome&{&&
&&&&&&&&status&200;&&
&&&&&&&&header&Content-Type&=&text/&&
&&&&&&&&body&~&"Welcome&to&nginx!";&&
}这一配置说明了健康检查通过的条件是,健康检查请求应该成功返回,拥有状态码 200,Content-Type 是为 "text/html",并且在正文中包含 "Welcome to nginx!"。服务器群必须属于共享内存。如果一些健康检查是为同一群服务器而定义,一个失败的任何检查就会使相关服务器被认为是不健康的。match&语法:&match&name&{&...&}
默认值:&&&&―
使用环境:&&http定义已命名测试设置,用于核对健康检查请求的返回。可以在一个返回中进行以下项目测试:status 200;状态码为 200。status ! 500;状态码不是 500。status 200 204;状态码为 200 或者 400。status ! 301 302;状态码既不是 301 也不是 302。status 200-399;状态码在 200 - 399 之间。status ! 400-599;状态码不在 400 - 599 之间。status 301-303 307;状态码是 301,302,303,或者 307。header Content-Type = text/头包含字段 "Content-Type" 值为 text/html。header Content-Type != text/头包含字段 "Content-Type" 值不是 text/html。header Connection ~头包含字段 "Connection" 值匹配正则表达式 close。header Connection !~头包含字段 "Connection" 值不匹配正则表达式 close。header H头包含字段 "Host"。header ! X-Accel-R头没有 "X-Accel-Redirect" 字段。body ~ "Welcome to nginx!";正文匹配正则表达式 "Welcome to nginx!"。body !~ "Welcome to nginx!";正文不匹配正则表达式 "Welcome to nginx!"。如果以上有些被定义,那么返回必须全都匹配时才能说明它测试通过。举例:#&status&is&200,&content&type&is&"text/html",&&
#&and&body&contains&"Welcome&to&nginx!"&&
match&welcome&{&&
&&&&status&200;&&
&&&&header&Content-Type&=&text/&&
&&&&body&~&"Welcome&to&nginx!";&&
#&status&is&not&one&of&301,&302,&303,&or&307,&and&header&does&not&have&"Refresh:"&&
match&not_redirect&{&&
&&&&status&!&301-303&307;&&
&&&&header&!&R&&
#&status&ok&and&not&in&maintenance&mode&&
match&server_ok&{&&
&&&&status&200-399;&&
&&&&body&!~&"maintenance&mode";&&
}queue语法:&queue&number&[timeout=time];
默认值:&&&&―
使用环境:&&upstream
该指令首次在1.5.12版本中被添加。规定了一个等待队列,当后端服务器达到最大连接数的时候,后续请求将被放置在本地等待队列中,该指令规定可以在队列中的最大请求数,如果超过最大超时时间请求不能被传递到后端服务器,那么就将返回给客户端一个错误消息。如果超时时间未指定,默认值为60秒。sticky & 基于cooike的负载均衡语法:&sticky&cookie&name&[expires=time]&[domain=domain]&[httponly]&[secure]&[path=path];
sticky&route&$variable&...;
sticky&learn&create=$variable&lookup=$variable&zone=name:size&[timeout=time];
默认值:&&―
使用环境:&upstream
该指令首次出现在1.5.7版本中被添加。注意:sticky模块不能与ip_hash同时使用使用这个方法,Nginx添加一个会话cookie给第一个来自这个上游分组的响应,并且识别谁发送一个响应的服务器。当一个客户端发出下一个请求,请求会带着这个cookie值,Nginx将把这个请求路由到相同的上游服务器:例如:upstream&backend&{&&
&&&&server&;&&
&&&&server&;&&
&&&&sticky_cookie_insert&srv_id&expires=1h&&path=/;&&
}没有绑定到特定服务器的客户端请求会被传递到由配置的负载均衡方法选中的服务器。这个客户端的而后的请求将被传递到同一台服务器。如果指定服务器无法处理请求,一台新的服务器会被选中绑定,就像这个客户端的这次请求前没有绑定到任何服务器一样。关于绑定服务器的信息保存在 HTTP cookie 中。第一个参数设置 cookie 名。其他参数如下:expires:设置浏览器保持 cookie 的时间。特别值 max 将会使 cookie 在 "31 Dec :55 GMT" 才过期。这个是老的浏览器所能知道最大日期值了。如果这个参数没有定义,cookie 会在浏览器会话结束时过期。domain:定义设置的 cookie 的域名。path:定义设置的 cookie 的路径。如果任何一个参数被遗漏掉,相应的 cookie 属性就不会被设置上。ruote 方法通过这个方法,Nginx在第一次接收到请求的时候给这个客户端分配一个“路由”。所有接下来的请求都会和server指令的route参数对比找出请求将被代理到的服务器。路由信息可以从cookie或URI中获取。路由方法的参数指定可包含路由信息的变量。第一个非空变量用于找到匹配服务器。举例:map&$cookie_jsessionid&$route_cookie&{
&&&&~.+\.(?P&route&\w+)$&$
map&$request_uri&$route_uri&{
&&&&~jsessionid=.+\.(?P&route&\w+)$&$
upstream&backend&{
&&&&server&&route=a;
&&&&server&&route=b;
&&&&sticky&route&$route_cookie&$route_
}learn 方法该功能在1.7.1版本中被添加,Nginx首先通过检查请求和响应来查找会话ID。接着Nginx就知道哪个上游服务器对应哪个会话ID。通常,这些ID是通过HTTP cookie传递的。如果一个请求包含一个已经识别过的会话ID,Nginx直接发送这个请求给相应的服务器:upstream&backend&{
&&&server&:8080;
&&&server&:8081;
&&&sticky&learn
&&&&&&&&&&create=$upstream_cookie_sessionid
&&&&&&&&&&lookup=$cookie_sessionid
&&&&&&&&&&zone=client_sessions:1m;
}本例中,其中一个上游服务器通过在响应中设置cookie “SESSIONID” 来创建一个会话。必填参数create指定一个变量表示一个新的会话是如何创建的。在我们的示例中,新的会话是通过上游服务器发送的cookie “SESSIONID”创建的。嵌入式变量ngx_http_upstream_module 模块支持以下嵌入式变量:$upstream_addr& & & & 为 UNIX-domain socket 保存服务器地址及端口号。如果请求处理时涉及多台服务器,使用逗号将他们的地址进行分隔,比如 "192.168.1.1:80, 192.168.1.2:80, unix:/tmp/sock"。如果一个由 "X-Accel-Redirect" 或者错误页面 发出的从一个服务器群组到另一个群组的重定向发生时,不同群组之间的服务器地址使用冒号分隔,比如 "192.168.1.1:80, 192.168.1.2:80, unix:/tmp/sock : 192.168.10.1:80, 192.168.10.2:80"。$upstream_cache_status& & & & 保存访问响应缓存的状态(版本 0.8.3)。这一状态可以是 "MISS","BYPASS","EXPIRED","STALE","UPDATING" 或者 "HIT"。$upstream_response_length& & & & 保存从 upstream 服务器获得的响应体长度(版本 0.7.27)字节的长度。几个响应长度的话使用逗号和冒号分隔,就像 $upstream_addr 中的地址那样。$upstream_response_time& & & & 保存从 upstream 服务器获得的响应次数,长度以毫秒的分辨率保存,单位是秒。几个响应次数的话使用逗号和冒号分隔,就像 $upstream_addr 中的地址那样。$upstream_status& & & & 保存从 upstream 服务器获得的响应码。几个响应码的话使用逗号和冒号分隔,就像 $upstream_addr 中的地址那样。$upstream_http_...& & & & 保存服务器响应头。比如,"Server" 响应头可以使用 $upstream_http_server 参数激活。将头信息转化为参数名字的规则和以 "$http_" 前缀开始的参数规则一样。只保存最后一个响应头。本文出自 “” 博客,请务必保留此出处
了这篇文章
类别:┆阅读(0)┆评论(0)请教nginx模块无法设置response_百度知道
请教nginx模块无法设置response
提问者采纳
或许可能实现,你搭建2个虚拟机测试一下就知道了!不过我觉得如果是动态的话!如果是静态网站,可能不行这个问题
来自团队:
其他类似问题
为您推荐:
nginx的相关知识
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁&&&&& 本文译自:
&&&&& 下载链接:&&&&
&&& 要非常感谢nginx,它帮助我更加了解蝙蝠侠这个喜剧角色。
&&&&蝙蝠侠很快,nginx也很快。蝙蝠侠在与罪恶斗争,nginx在与浪费CPU、内存泄漏等现象做斗争。蝙蝠侠在压力下能保持良好状态,nginx在强大的服务请求压力下表现出色。
但是,蝙蝠侠如果没有那根蝙蝠侠万能腰带(batman&utility&belt),那他就什么都不是。
在任何时候,蝙蝠侠的万能腰带都应该包括&一个锁扣、几个batarang(蝙蝠侠的特殊武器)、几个bat-cuff(护腕)、&夜视眼镜、bat-tracer(跟踪器?)、几个bat-darts(蝙蝠镖)...或者还包括一个apple&iphone。当蝙蝠侠需要使他的敌人失明、失聪、或者晕倒,或者当他需要跟踪他的敌人,或者给他的敌人发个短信,你最好相信他正在他的万能腰带上找一个合适的工具。这根腰带对蝙蝠侠的行动如此至关重要,所以,当蝙蝠侠在选择穿裤子还是穿腰带的时候,他肯定会选择穿腰带。事实上他确实选择了腰带,这就是为什么蝙蝠侠穿着紧绷的橡胶衣,而没有穿裤子。
虽然nginx没有这样一条万能腰带,但是nginx有一个模块链(module&chain),当nginx需要对应答进行gzip或chunked编码时,它会拿出一个模块来做这个工作。当nginx基于IP或http认证来阻拦对某些资源的访问时,也是由一个模块来做这些工作的。同样,当nginx需要和memcahed或者fastCGI交互时,也分别由对应的模块来做相应的工作。
尽管蝙蝠侠的万能腰带上有很多小玩意,但是有时候他还是需要一个新的小工具。也许他有新的敌人,而他现在的武器(如batarang或bat-cuff)都不足以对付这个敌人。也许他需要有新的技能,比如在水下呼吸。所以他会让Lucius&Fox来帮他设计这些小工具。
这个文档会告诉你nginx模块链表(module&chain)的一些细节,这样你就可以做到像Lucius&Fox一样。当你完成这个课程,你将可以设计并写出高质量的模块,来完成nginx以前做不了的事。Nginx的模块系统有很多需要注意的细节,所以你可能需要经常返回这篇文档阅读。我已尽力将这些内容梳理清楚,但我比较愚钝,写nginx模块仍然是很费力的一件事情。
0&前提条件(prerequisites)
你需要熟悉C。不仅仅是C语言语法;你需要熟悉C结构体相关的知识;不能被指针和函数引用所吓倒;对预编译、宏有一定的认识。如果你需要提高一下C语言方面的知识,没有什么比这更好了:K&R。
对HTTP协议的了解很有用,毕竟你是在一个webserver之上工作。
你也应该对nginx的配置文件非常熟悉。如果你不熟悉它,这里有一个简短的介绍:配置文件中有4种context(分别叫main,server,upstream,location)。每个context中可以包含数个拥有一个或多个参数的指令。在main&context中的指令对所有事情生效;在server&context中的指令对一个特定的host/port生效;在upstream&context中的指令对后端的一组server生效;在location&context中的指令仅对能匹配到的web地址(如"/","/images"等)生效。一个location&context会继承包含它的server&context的属性,同样,一个server&context会继承main&context的属性。Upstream&context不会继承任何属性,也不会把自己的属性传递给其它;它有自己的特殊的指令,这些指令不会在其它任何地方生效。我比较多地提到了这4个context,所以...别忘记它们。
让我们开始吧。
1&nginx模块概述
Nginx模块中有3个角色我们将要关注:
1.&Handler:处理请求并且产生输出。
2.&Filter:对handler产生的输出进行操作(比如gzip压缩,chunked编码等)
3.&Load-balancer:当有多个符合条件的后端server可供选择时,从中选择一个以便将请求转发过去。
模块承担着所有你能想到的和webserver相关的实际工作:当nginx寻找一个静态文件,或者将请求代理到另外一台server的时候,这时候有一个handler模块来做这些工作;当nginx对输出进行编码,或者执行有一个服务端的包含(server-side&include)操作,它将使用filter模块。Nginx的核心模块很简单,它关心网络通信、应用协议。另外,当处理一个请求时,将所有需要调用的合格模块排好序(以便在处理时依次调用)。这种没有中心控制节点的架构,让你可以写一个非常好的自包含模块来做你想做的事情。
&&&&注意:不像apache中的模块,nginx中的模块不是以动态链接库(so)的方式存在。(换句话说,它被编译进nginx这个可执行的bin文件)。
&&&&一个模块是如何被调用的?一般情况下,当nginx启动时,每个handler都有一个机会将自己和配置文件中的某个location关联起来;如果有2个以上的模块关联到同一个location,那么仅有其中一个能成功(当然,一个好的配置书写人员不会上这种冲突产生)。Handler能以3种方式返回:所有处理成功;有错误产生;拒绝处理请求并将它传给默认handler处理(默认handler通常返回一个静态文件)。
如果一个handler是一个到一些后端server组的反向代理,那么就需要用到load-balancer。一个load-balancer决定一个被接收到的请求将要被发送到后端server组中的哪一台。Nginx自带2个load-balancer模块:round-robin(轮询),它像我们在扑克牌游戏中发牌一样,(将请求轮流发送给后端server);iphash,它能保证一个客户端的多次请求可以被同一台后端server处理。
如果handler在处理过程没有产生错误,那么filter将被调用。每个location都可以被关联(hook)多个filter,所以,(举个例子),一个应答可以先被压缩,然后被chunked编码。它们的调用顺序在编译时就决定好了。Filter是一种典型的CHAIN&OF&RESPONSIBILITY(职责链)设计模式。一个filter被调用,做完它的工作,然后再调用下一个,直到最后一个filter被调用,nginx才完成它对response的处理。
Filter链表中最酷的设计在于,每个filter并不需要等待前一个filter完成后才能开始工作,它可以像unix系统管道一样工作,在前一个filter产生输出的同时处理。Filter在buffer之上操作。Buffer通常的大小为4K(系统页面大小),当然,你可以在nginx.conf中改变这个值。这就意味着,不必等到后端server上接收到整个应答,也就是说,只要接收到应答的一小部分,这个模块就可以开始压缩应答,并把这小部分先返回给客户端。
总结一下主要的概念,典型的处理流程如下:
客户端发送HTTP请求&-&&nginx根据配置文件中的location配置选择正确的模块&-&&(如果需要)load-balancer选择一个后端server&-&&handler处理完它的事情并把每个输出buffer传递给第一个filter&-&&第一个filter传递处理后的buffer给第二个filter&-&&第二个filter传递给第三个&-&&...&-&&最终应答发送给客户端
我用到了“典型”这个词,因为nginx的模块调用非常可定制化。模块何时被调用,以及如何被调用,都给模块开发人员带来很大的负担。调用通过一系列回调函数来实现,这些回调函数有很多。在以下情况下,你都可以指定一个函数来执行:
1.&在server读取配置文件之前
2.&当每一条配置指令出现在每一个loaction和server&context的时候
3.&当nginx初始化main配置
4.&当nginx初始化server配置
5.&当nginx将main配置与server配置合并
6.&当nginx初始化location配置
7.&当nginx将某个location父节点server的配置与这个location节点的配置合并
8.&当nginx&master进程启动
9.&当nginx&worker进程启动
10.&当nginx&master进程退出
11.&处理一个请求
12.&操作应答消息头
13.&操作应答消息体
14.&选择一个后端server
15.&初始化对后端server的一个请求
16.&重新初始化到后端server的请求
17.&处理从后端server接收到的应答
18.&完成与后端server的一次交互
通过这些回调函数你能做很多事情,现在是时候深入研究一下nginx的几个模块了。
2&nginx模块的组成部分
我说过,你有非常大的机动性来做一个nginx模块。这一节将描述一些常见的部分。它是一个帮助你读懂一个模块的指引,也是一本写模块时需要翻阅的手册。
2.1&模块配置结构(module&configuration&structures)
一个模块可以分别为main、server、location这3种context分别定义3种配置结构。事实上,大部分模块仅仅需要一个location配置结构。这些配置结构的命名习惯一般是这样的:&ngx_http_&module&name&_(main|srv|loc)_conf_t。这里有一个例子(从dav模块中拿出):
typedef&struct&{&
&&&&ngx_uint_t&&&
&&&&ngx_flag_t&&create_full_put_&
&&&&ngx_uint_t&&&
}&ngx_http_dav_loc_conf_t;&
注意,Nginx有一些特殊的数据类型(比如ngx_uin_t、ngx_flag_t)。这些仅仅只是一些元数据类型的别名。
这些结构中的元素由模块指令填充。
2.2&模块指令(module&directives)
&&&&一个模块的指令出现在一个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,&
&&&&&&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中找到它。
struct&ngx_command_t&{&
&&&&ngx_str_t&&&&&&&&&&&&&&
&&&&ngx_uint_t&&&&&&&&&&&&&
&&&&char&&&&&&&&&&&&&&&*(*set)(ngx_conf_t&*cf,&ngx_command_t&*cmd,&void&*conf);&
&&&&ngx_uint_t&&&&&&&&&&&&&
&&&&ngx_uint_t&&&&&&&&&&&&&
&&&&void&&&&&&&&&&&&&&&&&*&
看起来有点多,但是每个成员都有它的用途和目的。
Name是指令字符串,不含空格。数据类型是ngx_str_t。举个例子,它经常被这样初始化:ngx_str("proxy_pass")。注意:ngx_str_t是一个包含2个成员变量的结构,data和len,data为一个字符串,len为该字符串的长度。在大多数需要使用字符串的时候,nginx都使用该结构体。
Type是一组标志位。通过这个标志位,来说明这个指令带几个参数,配置在何处生效。这些标志位可以被按位或,它们包括:
NGX_HTTP_MAIN_CONF:指令在main配置中生效
NGX_HTTP_SRV_CONF:指令在server配置中生效
NGX_HTTP_LOC_CONF:指令在location配置中生效
NGX_CONF_NOARGS:指令不带参数
NGX_CONF_TAKE1:指令带一个参数
NGX_CONF_TAKE2:指令带二个参数
NGX_CONF_TAKE7:指令带7个参数
NGX_CONF_FLAG:指令带有一个bool变量参数("on"或"off")
NGX_CONF_1MORE:指令带有一个以上参数(至少一个)
NGX_CONF_2MORE:指令带有二个以上参数(至少二个)
还有一些其它选项,请参见core/ngx_conf_file.h
Set是函数指针,用来设置模块配置结构(module&configuration&structrue)中的某个部分(某个变量)。一般情况下,这个函数会把指令的参数传递进来,并在配置结构中设置一个合适的值。这个设置函数带有3个参数:
1.&一个指向ngx_conf_t结构体的指针。该结构体中包含了传递给这个指令的参数。
2.&一个指向当前的ngx_command_t结构体的指针
3.&一个执行模块自定义结构体(module&configure&structrues)的指针
当系统在解析配置文件时,如果遇到这个指令,则会调用这个设置函数(set所指函数)。Nginx提供了一系列的函数,来设置自定义结构体中不同的类型。这些函数包括:
Ngx_conf_set_flag_slot:将"on"或"off"转换成1或0
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数据将会被保存在main配置,还是server配置,还是location配置中(通过使用NGX_HTTP_MAIN_CONF_OFFSET、NGX_HTTP_SRV_CONF_OFFSET、NGX_HTTP_LOC_CONF_OFFSET)。接下来,offset说明了配置中的哪部分将被填充。
Post是模块在读取配置时可能会用到的一个指针,通常情况下,这个字段为NULL。
这个command数组以ngx_null_command结束。
2.3&模块上下文(module&context)
这是一个ngx_http_module_t类型的静态变量,它有一系列来创建这3种配置并合并它们。它的名字是ngx_http_&module_name&_module_ctx。下面,按顺序列出这些函数:
1.&Preconfiguration:配置初始化之前调用
2.&Postconfiguration:配置初始化之后调用
3.&Creating&the&main&conf:创建main配置(比如malloc,memset)。
4.&Initializing&the&main&conf:初始化main配置
5.&Creating&the&server&conf:创建server配置
6.&Merging&it&with&the&main&conf:将main配置与server配置合并
7.&Creating&the&location&conf:创建location配置
8.&Merging&it&with&the&server&conf:将server配置与location配置合并
这些函数因为完成的功能不同,所以都带有不同的参数。这里有ngx_http_module_t的定义(来自http/ngx_http_config.h):
typedef&struct&{&
&&&&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都只用到最后2个函数。&Create_loc_conf用来为location配置分配空间,merge_loc_conf用来为location配置初始化,并将它与继承而来的配置合并。如果配置是非法的,可以在merge_loc_conf中返回一个错误,server将因为这个错误而中止启动。
这里有一个模块上下文结构的例子:
static&ngx_http_module_t&&ngx_http_circle_gif_module_ctx&=&{&
&&&&NULL,&&&&&&&&&&&&&&&&&&&&&&&&&&/*&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&location&configuration&*/&
&&&&是时候来更深入的了解一下了。这些配置回调函数在所有的模块中都非常相似,并且它们使用几乎相同的ngix&api。了解它们非常有必要。
2.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_t&*cf)&
&&&&ngx_http_circle_gif_loc_conf_t&&*&
&&&&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&&
&首先需要注意的是nginx的内存分配。我们使用ngx_palloc(对malloc的包装)和ngx_pcalloc(对calloc的包装)来获得新的内存。(nginx使用内存池ngx_pool_t来管理内存,内存分配后由nginx系统在适当的时候回收)
表示未设置(UNSET)的常量包括:NGX_CONF_UNSET_UINT、NGX_CONF_UNSET_PTR、NGX_CONF_UNSET_SIZE、NGX_CONF_UNSET_MSEC,和一个针对所有类型的NGX_CONF_UNSET。未设置常量用来告诉合并函数这个值可以被覆盖。
2.3.2&merge_loc_conf
&&&&下面是我们在circle_gif函数中用到的merge_loc_conf函数:
static&char&*&
ngx_http_circle_gif_merge_loc_conf(ngx_conf_t&*cf,&void&*parent,&void&*child)&
&&&&ngx_http_circle_gif_loc_conf_t&*prev&=&&
&&&&ngx_http_circle_gif_loc_conf_t&*conf&=&&
&&&&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_radius&must&be&equal&or&more&than&1");&
&&&&&&&&return&NGX_CONF_ERROR;&
&&&&if&(conf-&max_radius&&&conf-&min_radius)&{&
&&&&&&&&ngx_conf_log_error(NGX_LOG_EMERG,&cf,&0,&&
&&&&&&&&&&&&"max_radius&must&be&equal&or&more&than&min_radius");&
&&&&&&&&return&NGX_CONF_ERROR;&
&&&&return&NGX_CONF_OK;&
Nginx为不同的数据类型都提供了很好的merge函数(通过宏定义实现,取名通常为ngx_conf_merge_&data_type&_value),这种函数包含3个参数:
1.&需要被设置的变量值
2.&如果#1未被设置(==NGX_CONF_UNSET),可继承的值
3.&默认值。如果#1和#2都未被设置,则取该值
&&&最终结果被保存到第一个参数。可用的merge函数包括ngx_conf_merge_size_value、ngx_conf_merge_msec_value等等。你可以到core/ngx_conf_file.h中去看一个完整的列表。
问:这些函数是如何将结果写入第一个参数的呢?第一个参数的传递方式是采用传值(pass&by&value),而不是传递指针。
答:事实上,这些“函数”仅仅是一些宏定义。(所以它们在编译之前,会被展开成一些if语句)
&&&&同样需要注意的是,该函数是如何产生错误的:它可以在日志文件中记录一些信息,然后返回NGX_CONF_ERROR。这个返回值将会中止nginx的启动。(因为日志记录级别是NGX_LOG_EMERG,这些被记录的信息同样会被打印到标准输出(屏幕)。core/ngx_log.h中定义了一系列日志级别可供参考)
2.4&模块定义(module&definition)
下面,我们加上一个间接关联的一层,ngx_module_t结构体。这个变量通常被命名为ngx_http_&module&name&_module。(通常,每个变量是一个全局变量)。这个结构体中包含了模块上下文(module&context)和模块指令(directives)的引用(reference,就是指针),还包括一些其它的回调函数(比如exit&thread,&exit&process等)。模块定义(module&definition)有时候就是为了用来查找跟模块相关的数据。模块定义通常看起来像这样:
ngx_module_t&&ngx_http_&module&name&_module&=&{&
&&&&NGX_MODULE_V1,&
&&&&&ngx_http_&module&name&_module_ctx,&/*&module&context&*/&
&&&&ngx_http_&module&name&_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&
...将&module&name&替换成模块名字。每个模块可以添加一些回调函数,以便在进程/线程创建/退出时调用。但是,大多数模块都保持简单,没有添加这些函数。(core/ngx_conf_file.h有这些函数的参数定义)
2.5&模块安装(module&installation)
一个模块的安装方法取决于该模块是一个handler,还是一个filter,或者是一个load-banlancer。细节将在对应的章节中说明。
&&&&现在,我们将拿一些简单的模块,仔细来看它们是如何工作的。
3.1&Handler解析(非proxy&handler)
Handler大概需要做4件事情:获取location配置;产生一个合适的应答;发送HTTP头部;发送HTTP消息体。模块中的handler函数有一个参数——request结构体。Request结构体有很多关于客户端请求的非常有用的信息,比如说HTTP方法、URI、HTTP&头部信息。我们将一个个来看这些4个步骤。
3.1.1&获取location配置
这部分比较简单。你需要做的是调用ngx_http_get_module_loc_conf,传入request结构体和模块定义(一个被全局定义的变量)。这里是在我的circle_gif模块的handler中的相关部分:
static&ngx_int_t&
ngx_http_circle_gif_handler(ngx_http_request_t&*r)&
&&&&ngx_http_circle_gif_loc_conf_t&&*circle_gif_&
&&&&circle_gif_config&=&ngx_http_get_module_loc_conf(r,&ngx_http_circle_gif_module);&
现在,我们可以访问我们在配置merge函数中所设置的变量了。
3.1.2&生成应答
这是模块真正做事的部分,也是非常有趣的部分。
Request结构体在这里将非常有用,特别是下面这些元素:
typedef&struct&{&
/*&the&memory&pool,&used&in&the&ngx_palloc&functions&*/&
&&&&ngx_pool_t&&&&&&&&&&&&&&&&&&&&&&&*&&
&&&&ngx_str_t&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&ngx_str_t&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&ngx_http_headers_in_t&&&&&&&&&&&&&headers_&
}&ngx_http_request_t;&
Uri是请求访问的路径。如"/query.cgi"。
Args请求中'?'后的部分。如"name=john"。
Headers_in中有很多有用的东西(请求的HTTP头信息)。比如cookie,浏览器信息。但是大多数模块都不需要其中的任何东西,如果你感兴趣,可以查阅http/ngx_http_request.h。
这些信息已经足够产生一个有用的输出。完整的ngx_http_request_t的定义能在http/ngx_http_request.h中找到。
3.1.3&发送HTTP头部
应答头部保存在一个叫headers_out的结构体中。Request结构体中有一个指针指向headers_out结构体。在handler函数中,设置headers_out中的相关变量,然后调用ngx_http_send_header(r)。Headers_out中一些有用的部分包括:
typedef&stuct&{&
&&&&ngx_uint_t&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&size_t&&&&&&&&&&&&&&&&&&&&&&&&&&&&content_type_&
&&&&ngx_str_t&&&&&&&&&&&&&&&&&&&&&&&&&content_&
&&&&ngx_table_elt_t&&&&&&&&&&&&&&&&&&*content_&
&&&&off_t&&&&&&&&&&&&&&&&&&&&&&&&&&&&&content_length_n;&
&&&&time_t&&&&&&&&&&&&&&&&&&&&&&&&&&&&date_&
&&&&time_t&&&&&&&&&&&&&&&&&&&&&&&&&&&&last_modified_&
}&ngx_http_headers_out_t;&
(其余的变量定义可以在http/http_request.h中找到)
举个例子,如果一个模块想把content_type设置成"image/gif",content_length设置成100,返回200OK,那么,下面的代码能做到这些事情:
&&&&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.content_type.data&=&(u_char&*)&"image/gif";&
&&&&ngx_http_send_header(r);&
大部分合法的HTTP头在这个结构体中都有,你可以很方便的设置它们。但是,有一些HTTP头的设置难度比你上面所看到的要稍大一点。举个例子,content-encoding这HTTP头,它的数据类型是(ngx_table_elt_t*),所以模块必须为它分配内存。我们通过ngx_list_push来分配内存,这个函数带有个ngx_list_t类型的参数,返回一个在链表中创建的新的元素。下面的代码将content-encoding设置为"deflate"并把HTTP头发送出去。
&&&&r-&headers_out.content_encoding&=&ngx_list_push(&r-&headers_out.headers);&
&&&&if&(r-&headers_out.content_encoding&==&NULL)&{&
&&&&&&&&return&NGX_ERROR;&
&&&&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.content_encoding-&value.len&=&sizeof("deflate")&-&1;&
&&&&r-&headers_out.content_encoding-&value.data&=&(u_char&*)&"deflate";&
&&&&ngx_http_send_header(r);&
&&&&当一个HTTP头需要同时有多个值的时候,我们通常使用这个机制。理论上,它可以方便地添加或删除某些值,而保持其余的不变,因为不需要返回到字符串操作(使用的是链表)。
3.1.4&发送HTTP消息体
&&&&现在,模块已经在内存中产生了一个应答,它需要把这个应答放到一个buffer中,然后把这个buffer放到一个链表中,最后调用"send&body"函数输出该链。
这个链表用来做什么?Nginx让handler每次产生一个buffer(这个buffer被放入一个链接节点中);每个链接节点都有一个指向下一节点的指针,当它是最后一个节点时,指针指向NULL。我们假设只有一个buffer,这样就很简单。
首先,模块需要声明buffer和链接节点:
&&&&ngx_buf_t&&&&*b;&
&&&&ngx_chain_t&&&&
下一步就是为buffer(ngx_buf_t)分配内存,并让它指向我们的应答数据:
&&&&b&=&ngx_pcalloc(r-&pool,&sizeof(ngx_buf_t));&
&&&&if&(b&==&NULL)&{&
&&&&&&&&ngx_log_error(NGX_LOG_ERR,&r-&connection-&log,&0,&&
&&&&&&&&&&&&"Failed&to&allocate&response&buffer.");&
&&&&&&&&return&NGX_HTTP_INTERNAL_SERVER_ERROR;&
&&&&b-&pos&=&some_&/*&first&position&in&memory&of&the&data&*/&
&&&&b-&last&=&some_bytes&+&some_bytes_&/*&last&position&*/&
&&&&b-&memory&=&1;&/*&content&is&in&read-only&memory&*/&
&&&&/*&(i.e.,&filters&should&copy&it&rather&than&rewrite&in&place)&*/&
&&&&b-&last_buf&=&1;&/*&there&will&be&no&more&buffers&in&the&request&*/&
现在,将buffer放到我们的链接节点中:
&&&&out.buf&=&b;&
&&&&out.next&=&NULL;&
最后,我们发送消息体,并返回真个输出过滤链的状态码:
&&&&return&ngx_http_output_filter(r,&&out);&
Buffer链是nginx的I/O模型中最关键的部分,所以你得非常熟悉它们是如何工作的。
问:为什么buffer中会有一个last_buf变量?我们可以通过buffer的next指针指向空来判断这个buffer为整个链表中的最后一个。
答:一个链有可能是不完全的,比如说,有多个buffer,但不是所有的buffer都在这个请求或应答中。有些buffer在链表的末尾,但并不是在整个请求的末尾。这就是我们为什么需要last_buf变量的原因。
3.2&upstream&handler(proxy)解析
我为你的handler能产生一个应答而小小的鼓掌。有些时候你可以通过一段C代码来生成那个应答,但是大部分时候你需要和另外一个server通信(比如说,你要写一个模块实现另外一种网络协议)。你可以自己实现网络通信方面的细节,但是,当你收到一个特定的应答时,发生了些什么?当你等待应答的时候,你不想用自己的事件循环(event&loop)来阻塞整个程序的主事件循环。你会使nginx的性能大大降低。幸运的是,nginx让你可以在它已有的机制中设置相应的hook函数(回调函数)来处理和后端server("upstreams")的通信。这样一来,你就可以在不妨碍nginx对其它请求的处理的基础上,实现和后端server通信。本节描述一个模块如何和后端server(upstream)进行通信,比如说,memcached、fastcgi,后者其它HTTP&server。
3.2.1&upstream&回调函数(callbacks)概要
&&&&不像其它模块的handler函数,upstream模块的handler函数仅做了一点点“实际工作”。它不调用ngx_http_output_filter(输出应答),而是仅仅设置了一些回调函数。这些回调函数在后端server可读或可写时会被调用。实际上,有6个这样的回调函数(hook):
Create_request&创建一个将要被发送到upstream(后端server)的请求(一个buffer或者一个buffer链)
Reinit_request&当到upstream(后端server)的连接被重置(reset)时,这个函数将会被调用(正好在第2次调用create_request之前)。
Process_header&处理upstream应答的头一小块,并且设置一个到upstream有效负载的指针。
Abort_request&&当客户端中断请求时,这个函数将被调用。
Finalize_request&当nginx完成从upstream读取应答时,会调用finalize_request。
Inpurt_filter&是一个可以在读取到的应答消息体上进行操作的filter。
&&&&这些函数是如何被设置的?下面是一个例子,一个简化版本的proxy模块的handler函数:
static&ngx_int_t&
ngx_http_proxy_handler(ngx_http_request_t&*r)&
&&&&ngx_int_t&&&&&&&&&&&&&&&&&&&&
&&&&ngx_http_upstream_t&&&&&&&&*u;&
&&&&ngx_http_proxy_loc_conf_t&&*&
&&&&plcf&=&ngx_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-&&
&&&&u-&peer.log_error&=&NGX_ERROR_ERR;&
&&&&u-&output.tag&=&(ngx_buf_tag_t)&&ngx_http_proxy_&
&&&&u-&conf&=&&plcf-&&
/*&attach&the&callback&functions&*/&
&&&&u-&create_request&=&ngx_http_proxy_create_&
&&&&u-&reinit_request&=&ngx_http_proxy_reinit_&
&&&&u-&process_header&=&ngx_http_proxy_process_status_&
&&&&u-&abort_request&=&ngx_http_proxy_abort_&
&&&&u-&finalize_request&=&ngx_http_proxy_finalize_&
&&&&r-&upstream&=&u;&
&&&&rc&=&ngx_http_read_client_request_body(r,&ngx_http_upstream_init);&
&&&&if&(rc&&=&NGX_HTTP_SPECIAL_RESPONSE)&{&
&&&&&&&&return&&
&&&&return&NGX_DONE;&
它做了一些通常handler该做的事情,但是重要的部分在于这些回调函数。同时,注意一下ngx_http_read_client_request_body这个函数。这里设置了另外一个回调函数。当nginx完成从客户端读取时,将调用该函数。
这些回调函数将做些什么呢?通常,reinit_request,abort_request和finalize_request会设置或者重置一些内部状态,一般只有几行。而真正有用的函数是create_request和process_header。
3.2.2&create_request回调函数
为了让问题变得简单,让我们假设我们有一个后端server,它读取一个字节,并且打印出2个字节。那么我们的函数将是什么样的呢?
Create_request函数需要为这个单字节的请求分配一个buffer,为这个buffer分配一个链表节点,然后把upstream结构体(中的request_buf指针)指向该链表节点。它看起来将像这样:
static&ngx_int_t&
ngx_http_character_server_create_request(ngx_http_request_t&*r)&
/*&make&a&buffer&and&chain&*/&
&&&&ngx_buf_t&*b;&
&&&&ngx_chain_t&*&
&&&&b&=&ngx_create_temp_buf(r-&pool,&sizeof("a")&-&1);&
&&&&if&(b&==&NULL)&
&&&&&&&&return&NGX_ERROR;&
&&&&cl&=&ngx_alloc_chain_link(r-&pool);&
&&&&if&(cl&==&NULL)&
&&&&&&&&return&NGX_ERROR;&
/*&hook&the&buffer&to&the&chain&*/&
&&&&cl-&buf&=&b;&
/*&chain&to&the&upstream&*/&
&&&&r-&upstream-&request_bufs&=&&
/*&now&write&to&the&buffer&*/&
&&&&b-&pos&=&"a";&
&&&&b-&last&=&b-&pos&+&sizeof("a")&-&1;&
&&&&return&NGX_OK;&
这并不像想象的那么糟糕,是吧?当然,在实际中,在一些应用场景下,你可能需要使用请求URI。它以ngx_str_t类型存在于r-&uri,GET参数在在r-&args中。不要忘了你能访问请求头和cookie。
3.2.3&process_header回调函数
现在,是时候来讲process_header这个函数了。就像create_request在request结构体中添加了一个指针一样,process_header将应答指针(response&pointer)移到客户端将要接收的部分。它从后端server(upstream)读取header,并且设置相应的客户端应答header。
这里有一个非常小的例子,读取那个2个字节的应答。让我们假设第一个字节是状态码("status"&character)。如果是一个疑问号,我们就不理会第二字节,返回客户端404文件未找到。如果是一个空格符,我们就以200&OK返回另外一个字符到客户端。确实,这不是一个很有用的协议,但它是一个很好的例证。我们如何来写这个process_header函数?
static&ngx_int_t&
ngx_http_character_server_process_header(ngx_http_request_t&*r)&
&&&&ngx_http_upstream_t&&&&&&&*u;&
&&&&u&=&r-&&
&&&&/*&read&the&first&character&*/&
&&&&switch(u-&buffer.pos[0])&{&
&&&&&&&&case&'?':&
&&&&&&&&&&&&r-&header_&/*&suppress&this&buffer&from&the&client&*/&
&&&&&&&&&&&&u-&headers_in.status_n&=&404;&
&&&&&&&&&&&&&
&&&&&&&&case&'&':&
&&&&&&&&&&&&u-&buffer.pos++;&/*&move&the&buffer&to&point&to&the&next&character&*/&
&&&&&&&&&&&&u-&headers_in.status_n&=&200;&
&&&&&&&&&&&&&
&&&&return&NGX_OK;&
就这么简单。处理头部,改变指针,就完成了。注意headers_in实际上是一个应答头部结构体,像我们之前所看到的(cf.http/ngx_http_request.h),但是它能被从后端server(upstream)接收到的header所填充。一个真正的代理模块(proxying&module)将做更多的头部处理,这里没有提及到错误处理,但是你有了一个大概的了解。
但是,当我们从后端server所接收到的包中没有包含整个头部,该怎么办?
3.2.4&保持状态(keeping&state)
还记得我说过,abort_request、reinit_request、reinit_request,能够被用来重置内部状态。这是因为很多upstream模块有内部状态。模块需要定义一个自定义context结构体来保存和跟踪此刻从后端server收到了多少数据。这里和上面说的"Module&Context"不一样。那是一个系统事先定义好的结构。而这个自定义context结构体则可以包含任何你所需要的元素和数据。这个context结构体可以在create_request函数中初始化,可能会像这样:
&&&&ngx_http_character_server_ctx_t&&&*p;&&&/*&my&custom&context&struct&*/&
&&&&p&=&ngx_pcalloc(r-&pool,&sizeof(ngx_http_character_server_ctx_t));&
&&&&if&(p&==&NULL)&{&
&&&&&&&&return&NGX_HTTP_INTERNAL_SERVER_ERROR;&
&&&&ngx_http_set_ctx(r,&p,&ngx_http_character_server_module);&
最后一行实际上是将自定义context结构体以一个模块名注册到一个特殊的请求之上,以便后续可以很方便地获得。任何时候,当你需要这个context结构体时,(很有可能在其它的回调函数中),只需要这样做:
&&&&ngx_http_proxy_ctx_t&&*p;&
&&&&p&=&ngx_http_get_module_ctx(r,&ngx_http_proxy_module);&
指针p将指向当前状态。设置、重设、在里面放任何数据,想怎么做就这么做。当一个后端server以块(chunk)的形式返回数据,我们使用一个持久的状态机来读取数据,而且不会阻塞程序的主循环,这是一个非常好的方法。
3.3&Handler&installation
Handler通过在指令结构体中所指定的回调函数中添加代码来完成安装(该指令能使这个模块生效)。举个例子,我的circle&gif指令结构体是这样:
&&&&&{&ngx_string("circle_gif"),&
&&&&&&NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,&
&&&&&&ngx_http_circle_gif,&
&&&&&&NULL&}&
在这个例子中,回调函数是第3个参数ngx_http_circle_gif。回忆一下,这个回调函数的参数包括:指令结构体(ngx_conf_t,包含用户参数),相关的ngx_command_t结构体,和一个到模块配置结构的指针。对于我的circle&gif模块来说,这个函数会像这样:
static&char&*&
ngx_http_circle_gif(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);&
&&&&clcf-&handler&=&ngx_http_circle_gif_&
&&&&return&NGX_CONF_OK;&
这里有2个步骤:首先,为这个location获取core模块的locale配置,然后为handler赋值。非常简单,是吧?
现在,我已经说了所有我知道的关于handler模块的一切。是时候转移到filter模块上了,以及在输出过滤链上的一些组件。
&&&&Filters对handlers产生的应答进行操作。Header&filter对HTTP头进行操作,body&filter对HTTP体进行操作。
4.1&Header&filter分析
&&&&一个header&filter包括3个基本步骤:
1.&决定是否需要对某个应答进行操作
2.&对某个应答进行操作
3.&调用下一个filter
举个例子,这里有一个"not&modified"header&filter的简化版本,如果客户端的If-Modified-Since头与服务端应答中的Last-Modified头相匹配的话,就把状态码设置为"304&Not&Modified"。注意,header&filter仅有一个参数,即ngx_http_request_t结构体。这样,我们就可以访问客户端头部和马上就可以发送的应答头部。
ngx_int_t&ngx_http_not_modified_header_filter(ngx_http_request_t&*r)&
&&&&time_t&&if_modified_&
&&&&if_modified_since&=&ngx_http_parse_time(r-&headers_in.if_modified_since-&value.data,&
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&r-&headers_in.if_modified_since-&value.len);&
/*&step&1:&decide&whether&to&operate&*/&
&&&&if&(if_modified_since&!=&NGX_ERROR&&&&&
&&&&&&&&if_modified_since&==&r-&headers_out.last_modified_time)&{&
/*&step&2:&operate&on&the&header&*/&
&&&&&&&&r-&headers_out.status&=&NGX_HTTP_NOT_MODIFIED;&
&&&&&&&&r-&headers_out.content_type.len&=&0;&
&&&&&&&&ngx_http_clear_content_length(r);&
&&&&&&&&ngx_http_clear_accept_ranges(r);&
/*&step&3:&call&the&next&filter&*/&
&&&&return&ngx_http_next_header_filter(r);&
Headers_out结构体和我们在handler那一节所看到的是一样的。我们可以操作它。
4.2&Body&filter&分析
&&&&Buffer链使得写body&filter有一点难度,因为body&filter在一次仅能操作其中一个buffer(链节点)。模块必须决定,是覆盖这个buffer,用一个新allocate的buffer替换它,还是在它前面或后面插入新的buffer。在一些复杂的情况下,模块仅收到几个buffer,所以它有一个不完整的buffer链,它必须在其上操作。不幸的是,nginx并没有提供一个高层次的API来操作这些buffer链,所以,body&filter可能很难被理解(也很难写)。但是,这里有一些操作你将会掌握。
&&&&一个body&filter的原型看起来将像这样(这些代码来自nginx源码中的chunked&filter):
static&ngx_int_t&ngx_http_chunked_body_filter(ngx_http_request_t&*r,&ngx_chain_t&*in);&
&&&&第一个参数是我们的老朋友了—request结构体。第二个参数是一个到当前部分链头部的指针(这个链可能包含0个、1个或者多个buffer)。
让我们举个简单的例子。假设我们想把"&I!--served&by&nginx&"这段文字插到每个请求的末尾。首先,我们需要明确是否这个请求的最后一个buffer包含在当前的buffer链当中。像我所说过的那样,这里没有这样一个奇特的API,所以我们需要自己使用一个for循环来做这件事情:
&&&&ngx_chain_t&*chain_&
&&&&int&chain_contains_last_buffer&=&0;&
&&&&for&(&chain_link&=&&chain_link&!=&NULL;&chain_link&=&chain_link-&next&)&{&
&&&&&&&&if&(chain_link-&buf-&last_buf)&
&&&&&&&&&&&&chain_contains_last_buffer&=&1;&
现在,如果还没有最后一个buffer,让我们退出:
&if&(!chain_contains_last_buffer)&
&&&&&&&&return&ngx_http_next_body_filter(r,&in);&
&&&&非常好,现在,最后一个buffer已经被保存到了链表当中。现在,我们分配一个新的buffer:
&&&&ngx_buf_t&&&&*b;&
&&&&b&=&ngx_calloc_buf(r-&pool);&
&&&&if&(b&==&NULL)&{&
&&&&&&&&return&NGX_ERROR;&
在里面放一些数据:
b-&pos&=&(u_char&*)&"&!--&Served&by&Nginx&--&";&
&&&&b-&last&=&b-&pos&+&sizeof("&!--&Served&by&Nginx&--&")&-&1;&
将这个buffer关联到一个新的链表节点:
&&&&ngx_chain_t&&&added_&
&&&&added_link.buf&=&b;&
&&&&added_link.next&=&NULL;&
最后,将这个链表节点添加到最后一个链表节点的后面:
&&&&chain_link-&next&=&added_&
重新设置last_buf变量以反映实际:
&&&&chain_link-&buf-&last_buf&=&0;&
&&&&added_link-&buf-&last_buf&=&1;&
将修改过的链传递给下一个filter:
return&ngx_http_next_body_filter(r,&in);&
&&&&最终的函数比我们想像的要做的多很多,但是,buffer链是一个功能非常强大的结构,它让程序员可以增量的处理数据,这样客户端就可以尽快的收到数据。但是,我认为buffer链迫切的需要一个很清晰的调用接口,以防止程序员把buffer链弄成一个不协调相矛盾的状态。现在,你只能小心的操作它。
4.3&Filter&installation
Filter一般在post-configuration这一步安装。我们在同一个地方安装header&filter和body&filter。
让我们拿chunked&filter模块来做一个简单的例子。它的模块context看起来会像这样:
static&ngx_http_module_t&&ngx_http_chunked_filter_module_ctx&=&{&
&&&&NULL,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&/*&preconfiguration&*/&
&&&&ngx_http_chunked_filter_init,&&&&&&&&&&/*&postconfiguration&*/&
下面是ngx_http_chunked_filter_init函数:
static&ngx_int_t&
ngx_http_chunked_filter_init(ngx_conf_t&*cf)&
&&&&ngx_http_next_header_filter&=&ngx_http_top_header_&
ngx_http_top_header_filter&=&ngx_http_chunked_header_&
&&&&ngx_http_next_body_filter&=&ngx_http_top_body_&
&&&&ngx_http_top_body_filter&=&ngx_http_chunked_body_&
&&&&return&NGX_OK;&
这里发生了什么?如果你还记得,filter使用了一种CHAIN&OF&RESPONSIBILITY(职责链)的设计模式。当一个handler产生了一个应答,它将调用2个函数:ngx_http_output_filter,它将调用全局函数ngx_http_top_body_filter;ngx_http_send_header,它将调用全局函数ngx_http_top_header_filter。
Ngx_http_top_body_filter和ngx_http_top_header_filter分别是body和header&filter链的头部。每一个链上的节点都有一个到下一个节点的函数引用(这个引用被叫做ngx_http_next_body_filter和ngx_http_next_header_filter)。当一个filter结束执行时,它将调用下一个filter,直到一个被特别定义的write&filter被调用,它将包装HTTP应答。在这个filter_init函数中,你看到的是,这个模块将自己的filter函数添加到filter链中,它将老的top&filter保存到自己的next变量中,同时声明自己的函数为新的top&filter。(这样,最后一个被安装的filter将第一个被执行)。
Note:这些是如何工作的呢?
每个filter返回一个错误码,或者使用下面的语句返回:
return&ngx_http_next_body_filter()
这样,如果执行到了filter链的末尾,一个“OK”应答将被返回,但是如果在这个过程中产生了错误,这个链将被截断,nginx将返回一个适当的错误信息。它是一个完全由函数引用实现的单向链表,有着快速的错误处理能力。
5&Load-balancers
一个load-balancer仅仅是决定哪个后端server将接收到某个特殊的请求。已经实现的方式包括采用轮询(round-robin)的方式将请求分发给后端server;hash请求中的某些信息将请求分发给后端server。这节将拿upstream_hash模块做一个例子,来讲load-balancer的安装和调用。Upstream_hash使用一个在nginx.conf中指定的变量来hash,选定一个后端server。
一个load-balancer模块有6个部分:
1.&使模块生效的配置指令(比如,hash),将调用一个注册函数。
2.&注册函数将定义一些合理的server参数,并且注册一个upstream&initialization函数。
3.&Upstream&initialization函数在配置被确认之后就会被调用。它的工作包括:
&&&&a.&解析server名字到特定的IP
&&&&b.&为socket分配空间
&&&&c.&设置peer&initialization回调函数
4.&Peer&initialization函数,每个请求都会被调用一次。设置load-balance函数将要访问或操作的数据。
5.&Load-balance函数决定请求将被路由到哪里。对于每一个客户端请求,它至少被调用一次(如果第一次调用后端server失败了,它将被调用多次)。
6.&最后,在与一个特殊的后端server进行通信之后,peer&release函数可以用来更新统计信息。
现在,我们逐个来讲。
5.1&生效指令(enabling&directive)
回忆一下,指令声明时会指定在什么时候这个指令是合法的,以及当碰到这个指令时,该执行的函数。一个load-balancer的指令应该设置了NGX_HTTP_UPS_CONF这个标志位,所以nginx知道这个指令只在upstream块中生效。它应该会提供一个到注册函数的指针。这里是upstream_hash模块中的指令定义:
&&&&{&ngx_string("hash"),&
&&&&&&NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,&
&&&&&&ngx_http_upstream_hash,&
&&&&&&NULL&},&
5.2&注册函数(registration&function)
上面的回调函数ngx_http_upstream_hash就是注册函数。之所以如此命名,是因为它在周围的upstream配置上注册了一个upstream&initialization函数。更多的,这个注册函数定义了:在特定的upstream块中,哪些到server指令的选项是合法的。(比如,weight=,fail_timeout=)。这是upstream_hash模块的注册函数:
ngx_http_upstream_hash(ngx_conf_t&*cf,&ngx_command_t&*cmd,&void&*conf)&
&&&&ngx_http_upstream_srv_conf_t&&*&
&&&&ngx_http_script_compile_t&&&&&&&
&&&&ngx_str_t&&&&&&&&&&&&&&&&&&&&&*&
&&&&ngx_array_t&&&&&&&&&&&&&&&&&&&*vars_lengths,&*vars_&
&&&&value&=&cf-&args-&&
&&&&/*&the&following&is&necessary&to&evaluate&the&argument&to&"hash"&as&a&$variable&*/&
&&&&ngx_memzero(&sc,&sizeof(ngx_http_script_compile_t));&
&&&&vars_lengths&=&NULL;&
&&&&vars_values&=&NULL;&
&&&&sc.cf&=&&
&&&&sc.source&=&&value[1];&
&&&&sc.lengths&=&&vars_&
&&&&sc.values&=&&vars_&
&&&&plete_lengths&=&1;&
&&&&plete_values&=&1;&
&&&&if&(ngx_http_script_compile(&sc)&!=&NGX_OK)&{&
&&&&&&&&return&NGX_CONF_ERROR;&
&&&&/*&end&of&$variable&stuff&*/&
&&&&uscf&=&ngx_http_conf_get_module_srv_conf(cf,&ngx_http_upstream_module);&
&&&&/*&the&upstream&initialization&function&*/&
&&&&uscf-&peer.init_upstream&=&ngx_http_upstream_init_&
&&&&uscf-&flags&=&NGX_HTTP_UPSTREAM_CREATE;&
&&&&/*&OK,&more&$variable&stuff&*/&
&&&&uscf-&values&=&vars_values-&&
&&&&uscf-&lengths&=&vars_lengths-&&
&&&&/*&set&a&default&value&for&"hash_method"&*/&
&&&&if&(uscf-&hash_function&==&NULL)&{&
&&&&&&&&uscf-&hash_function&=&ngx_hash_&
&&&&return&NGX_CONF_OK;&
为了避免在不同的主题之间跳跃,我们稍后解释$varialbe,它是非常简单的。设置一个回调函数,设置一些标志位。有哪些标志位呢?
NGX_HTTP_UPSTREAM_CREATE:让server指令可以出现在upstream块中。我几乎不能想像什么情况下你可以不用这个。
NGX_HTTP_UPSTREAM_WEIGHT:让server指令有一个weight=选项。
NGX_HTTP_UPSTREAM_MAX_FAILS:允许max_fails=选项。
NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:允许fail_timeout=选项。
NGX_HTTP_UPSTREAM_DOWN:允许down选项。
NGX_HTTP_UPSTREAM_BACKUP:允许backup选项。
每个模块都可以访问这些配置值,由模块来决定将用它们来做什么。那就是说,max_fails不会自动地限制(连接后端server的次数),所有关于失败处理的逻辑完全又模块作者所决定。后面将详细解释这些。现在,我们仍未完成对回调函数的跟踪。下一步,我们来看upstream&initialization函数(即上一个函数中init_upstream函数)。
5.3&upstream&initialization函数
Upstream&initializtion函数的目的是:解析域名;为socket分配空间;设置另一个回调函数。下面是upstream_hash函数做了些什么:
ngx_int_t&
ngx_http_upstream_init_hash(ngx_conf_t&*cf,&ngx_http_upstream_srv_conf_t&*us)&
&&&&ngx_uint_t&&&&&&&&&&&&&&&&&&&&&&&i,&j,&n;&
&&&&ngx_http_upstream_server_t&&&&&&*&
&&&&ngx_http_upstream_hash_peers_t&&*&
&&&&/*&set&the&callback&*/&
&&&&us-&peer.init&=&ngx_http_upstream_init_upstream_hash_&
&&&&if&(!us-&servers)&{&
&&&&&&&&return&NGX_ERROR;&
&&&&server&=&us-&servers-&&
&&&&/*&figure&out&how&many&IP&addresses&are&in&this&upstream&block.&*/&
&&&&/*&remember&a&domain&name&can&resolve&to&multiple&IP&addresses.&*/&
&&&&for&(n&=&0,&i&=&0;&i&&&us-&servers-&&i++)&{&
&&&&&&&&n&+=&server[i].&
&&&&/*&allocate&space&for&sockets,&etc&*/&
&&&&peers&=&ngx_pcalloc(cf-&pool,&sizeof(ngx_http_upstream_hash_peers_t)&
&&&&&&&&&&&&+&sizeof(ngx_peer_addr_t)&*&(n&-&1));&
&&&&if&(peers&==&NULL)&{&
&&&&&&&&return&NGX_ERROR;&
&&&&peers-&number&=&n;&
&&&&/*&one&port/IP&address&per&peer&*/&
&&&&for&(n&=&0,&i&=&0;&i&&&us-&servers-&&i++)&{&
&&&&&&&&for&(j&=&0;&j&&&server[i].&j++,&n++)&{&
&&&&&&&&&&&&peers-&peer[n].sockaddr&=&server[i].addrs[j].&
&&&&&&&&&&&&peers-&peer[n].socklen&=&server[i].addrs[j].&
&&&&&&&&&&&&peers-&peer[n].name&=&server[i].addrs[j].&
&&&&&&&&}&
&&&&/*&save&a&pointer&to&our&peers&for&later&*/&
&&&&us-&peer.data&=&&
&&&&return&NGX_OK;&
这个函数比我们想像的更棘手。大部分工作看起来应该是抽象的,但是它不是,这就是我们生活的世界。一个简化事情的策略是:调用另外一个模块的upstream&initialization函数,让它去做所有繁琐的工作(为连接分配额内存,等),而重写u-&peer.init回调函数。举个例子,参看http/modules/ngx_http_upstream_ip_hash_module.c
从我们的视角来看,重要的一点是,设置一个到peer&initialization函数的指针。在这个例子中,它是ngx_http_upstream_init_upstream_hash_peer。
5.4&peer&initialization函数
Peer&initialization函数在每个请求会被调用一次。它为模块设置一个数据结构,模块将会用到这个结构来选择一个合适的后端server来处理请求。这个结构在重试后端server的时候,是持久保持的。所以,它可以非常方便的用来保存失败的连接数,或者一个计算好的hash值。按约定,这个结构通常被叫做ngx_http_upstream_&module&name&_peer_data_t。
还有,peer&initialization函数设置2个回调函数:
get:load-balance函数
free:peer&release函数(一般用来更新统计信息)
好像这还不够,它还会初始化一个变量,叫tries。如果tries一直是正值,(如果后端失败,)nginx就会保持重试。当tries为0时,nginx放弃重试。Get和free函数需要正确的设置tries变量。
这里有从upstream_hash模块中拿出来的peer&initialization函数:
static&ngx_int_t&
ngx_http_upstream_init_hash_peer(ngx_http_request_t&*r,&
&&&&ngx_http_upstream_srv_conf_t&*us)&
&&&&ngx_http_upstream_hash_peer_data_t&&&&&*&
&&&&ngx_str_t&&
&&&&/*&evaluate&the&argument&to&"hash"&*/&
&&&&if&(ngx_http_script_run(r,&&val,&us-&lengths,&0,&us-&values)&==&NULL)&{&
&&&&&&&&return&NGX_ERROR;&
&&&&/*&data&persistent&through&the&request&*/&
&&&&uhpd&=&ngx_pcalloc(r-&pool,&sizeof(ngx_http_upstream_hash_peer_data_t)&
&&&&&+&sizeof(uintptr_t)&&
&&&&&&&*&((ngx_http_upstream_hash_peers_t&*)us-&peer.data)-&number&&
&&&&&&&&&&&&&&&&&&/&(8&*&sizeof(uintptr_t)));&
&&&&if&(uhpd&==&NULL)&{&
&&&&&&&&return&NGX_ERROR;&
&&&&/*&save&our&struct&for&later&*/&
&&&&r-&upstream-&peer.data&=&&
&&&&uhpd-&peers&=&us-&peer.&
&&&&/*&set&the&callbacks&and&initialize&"tries"&to&"hash_again"&+&1*/&
&&&&r-&upstream-&peer.free&=&ngx_http_upstream_free_hash_&
&&&&r-&upstream-&peer.get&=&ngx_http_upstream_get_hash_&
&&&&r-&upstream-&peer.tries&=&us-&retries&+&1;&
&&&&/*&do&the&hash&and&save&the&result&*/&
&&&&uhpd-&hash&=&us-&hash_function(val.data,&val.len);&
&&&&return&NGX_OK;&
&&&&好像还不错,现在,我们已经准备好了来选择一个后端server。
5.5&load-balance&函数
现在,该讲到主要的内容了。模块在这里选择一个后端server。Load-balance函数的原型将像这样:
static&ngx_int_t&&
ngx_http_upstream_get_&module_name&_peer(ngx_peer_connection_t&*pc,&void&*data);&
Data就是一个关于客户端连接信息的结构体。Pc将包含我们将要连接的server的信息。Load-balance函数的工作就是填写pc-&sockaddr,pc-&socklen和pc-&name的值。如果你知道一些网络编程的知识,这些变量你应该很熟悉。但是实际上他们对于当前的工作并不重要,我们并不关心它们代表什么,我们仅仅想知道如何找合适的值去填写它们。
这个函数必须找到一个存在的server的列表,从中选择一个,把它的值赋给pc。让我们看一下upstream_hash做了什么。
Upstream_hash模块在这之前,在ngx_http_upstream_init_hash函数中,会将server列表放到ngx_http_upstream_peer_data_t这个结构中。现在,这个结构以data变量存在:
&&&&ngx_http_upstream_hash_peer_data_t&*uhpd&=&&
&&&&peer列表现在保存在uhpd-&peers-&peer中。现在,让我们用计算好的hash值,除以数组的大小,来从这个数组中选择一个peer。
&&&&ngx_peer_addr_t&*peer&=&&uhpd-&peers-&peer[uhpd-&hash&%&uhpd-&peers-&number];&
现在,到了很重要的最后一步:
&&&&pc-&sockaddr&=&peers-&&
&&&&pc-&socklen&&=&peers-&&
&&&&pc-&name&&&&&=&&peers-&&
&&&&return&NGX_OK;&
就这些了。如果load-balancer返回NGX_OK,它的意思是“继续下去,并且尝试这个server”。如果返回NGX_BUSY,它表示所有所有的后端server都暂时不可用,nginx应该再次尝试。
但是,我们如何来保持跟踪哪些是不可用的?如果我们不想让它重试,由该如何?
5.6&peer&release函数
在每发生一次与后端链接之后,peer&release函数会被调用。它的目的是用来跟踪错误。这里是函数的原型:
ngx_http_upstream_free_&module&name&_peer(ngx_peer_connection_t&*pc,&void&*data,&&ngx_uint_t&state);&
前2个参数和我们在load-balancer函数中看到的是一样的。第3个参数是一个state变量,它表示链接是否成功。它可能包含2个被按位或到一起的值:NGX_PEER_FAILED(链接失败)和NGX_NEXT_PEER(链接失败,或者链接成功到是程序返回失败)。0表示链接成功。
对这些失败事件如何处理完全取决于模块作者。如果这些事件需要要被处理,结果应该保存在data,一个对每个请求的定制结构体。
Peer&release函数的一个至关重要的目的是:如果你不想让nginx一直保持重试调用load-balance,需要设置pc-&tries为0。最简单的peer&release函数看起来会像这样:
&&&pc-&tries&=&0;&
这将保证如果后端server有错误产生,则会返回客户端一个502&Bad&proxy错误。
这里有一个更复杂一点的例子,来自upstream_hash模块。如果一个后端链接失败了,它将在bit数组(叫tried,一个uintptr_t型的数组)中把它标志为失败,然后继续选择一个新的后端server,直到它找到一个没有失败的。
#define&ngx_bitvector_index(index)&index&/&(8&*&sizeof(uintptr_t))&
#define&ngx_bitvector_bit(index)&(uintptr_t)&1&&&&index&%&(8&*&sizeof(uintptr_t))&
static&void&
ngx_http_upstream_free_hash_peer(ngx_peer_connection_t&*pc,&void&*data,&
&&&&ngx_uint_t&state)&
&&&&ngx_http_upstream_hash_peer_data_t&&*uhpd&=&&
&&&&ngx_uint_t&&&&&&&&&&&&&&&&&&&&&&&&&&&&
&&&&if&(state&&&NGX_PEER_FAILED&
&&&&&&&&&&&&&&&--pc-&tries)&
&&&&&&&&/*&the&backend&that&failed&*/&
&&&&&&&&current&=&uhpd-&hash&%&uhpd-&peers-&&
&&&&&&&/*&mark&it&in&the&bit-vector&*/&
&&&&&&&&uhpd-&tried[ngx_bitvector_index(current)]&|=&ngx_bitvector_bit(current);&
&&&&&&&&do&{&/*&rehash&until&we're&out&of&retries&or&we&find&one&that&hasn't&been&tried&*/&
&&&&&&&&&&&&uhpd-&hash&=&ngx_hash_key((u_char&*)&uhpd-&hash,&sizeof(ngx_uint_t));&
&&&&&&&&&&&&current&=&uhpd-&hash&%&uhpd-&peers-&&
&&&&&&&&}&while&((uhpd-&tried[ngx_bitvector_index(current)]&&&ngx_bitvector_bit(current))&&&&--pc-&tries);&
由于load-balance函数仅看uhpd-&hash的最新值,所以这个函数可以达到预期效果。
很多应用并不需要重试或其它高可靠性的逻辑,但是,从这里你能看到,仅仅使用几行代码,我们就能提供这样一个功能。
6&开发和编译一个新的nginx模块
现在,你应该准备好了来看一个nginx模块,并且尝试理解它是如何工作的。在/src/http/moudles中,你能看到已有的模块。找一个和你将要做的相似的模块来阅读。看起来都很熟悉吧?应该是这样。通过这篇指南和模块源代码去理解它在做什么。
但是emiller没有写一个如何来读nginx模块的指南。我们不是在读模块,我们在写,在创作,与世界分享。
首先,你需要一个地方来创作你的模块。为你的模块新建一个目录,它可以放在硬盘上的任何地方,但是和nginx的源代码分离。你的新模块在开始时应该包含2个文件:
"ngx_http_&your&module&_module.c"
这个"config"文件将被./configure包含,它的内容由模块的类型决定。
Filter模块的"config"文件:
ngx_addon_name=ngx_http_&your&module&_module&
HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES&ngx_http_&your&module&_module"&
NGX_ADDON_SRCS="$NGX_ADDON_SRCS&$ngx_addon_dir/ngx_http_&your&module&_module.c"&
其它模块的"config"文件:
ngx_addon_name=ngx_http_&your&module&_module&
HTTP_MODULES="$HTTP_MODULES&ngx_http_&your&module&_module"&
NGX_ADDON_SRCS="$NGX_ADDON_SRCS&$ngx_addon_dir/ngx_http_&your&module&_module.c"&
现在,你可以写你的C文件了。我建议copy一个已经存在的、和你的需要相似的模块,但是,把它重命名为"ngx_http_&your&module&_module.c"。让这个成为你的模型,你修改它的一些行为来适合你的需求。当你理解和重写不同部分时,经常查看这篇指南。
如果你准备好了来编译模块,到nginx的目录下,输入:
./configure&--add-module=path/to/your/new/module/directory&
然后,像往常一样,make,make&install。如果所有的步骤都没有出问题,你的模块就会被正确的编译进来。非常棒,是吧?不需要动nginx的代码,添加一个模块到nginx的新版本非常快,仅仅需要使用这个./configure命令就可以了。顺便说一下,如果你的模块需要动态链接库,你可以将这个添加到你的"config"文件:
CORE_LIBS="$CORE_LIBS&-lfoo"&
这里,foo是你所需要的库。
如果你做了一个非常酷,非常有用的模块,请一定将这个消息发给nginx邮件列表,和大家一起分享。
阅读(...) 评论()}

我要回帖

更多关于 nginx log response 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信