原文:http://www.evanmiller.org/nginx-modules-guide.html
译文:http://blog.csdn.net/tab_tab_tab/article/details/51407418
解蝙蝠侠的漫画人物有助于充分认识Nginx、Web服务器。
首先,蝙蝠侠快。 Nginx也快。 然后,蝙蝠侠同犯罪做斗争,Nginx和浪费CPU周期和内存泄漏做斗争。 最后,蝙蝠侠在压力下进行 工作。Nginx就其本身而言,擅长重的服务器负载下运行。
但是,蝙蝠侠将在没有多功能腰带下什么用都没有。
上图就是多功能腰带同学。
当蝙蝠侠需要冷静下来,准备给敌人发短信即将展开一次皇城PK时候,你最好相信他会检查一下他的多功能腰带。 多功能腰带是如此重要,对于蝙蝠侠的行动来说。如果让蝙蝠侠在穿着开裆裤,和戴着多功能腰带之间做出选择,他肯定会选择多功能腰带。 事实上,他根本* *选择工具带,这就是为什么蝙蝠侠穿紧身衣橡胶,而不是裤子。事实上,蝙蝠侠也就是这样选择的,所以蝙蝠侠为了穿裤不开裆,选择穿紧身裤。(老外的冷笑话…作为中国人有点不明觉厉,你们能看懂这个冷笑话么)
Nginx取而代之多功能腰带的一个模块链(module chain)。当nginx有需要gzip或分块编码的响应这类需求时候,它拿出一个模块来做这项工作;当nginx模块有需要对基于网络上某一个IP地址的资源访问或者是HTTP认证凭证时候,它拿出一个模块来做转移(deflecting 不知道翻译成什么好)这项工作;当Nginx与memcache或者FastCGI服务器进行通信,一个模板就是对话机。Nginx的多功能腰带就是模板链,当需要什么功能时候,模板链就给出这个模板来提供这个功能。
而蝙蝠侠的多功能腰带也不是万能的,比如可能出现高能敌人,这时候蝙蝠侠就需要特殊的武器去对付敌人。这时候问题是这个特殊的武器可能在多功能腰带中之前不曾存在的,这时候就需要一个工程师卢修斯·福克斯(Lucius Fox),一个略疯狂的科学家,去帮助蝙蝠侠研究各种小工具。下图是蝙蝠侠和他的工程师…
本指南的目的是要你教的Nginx的模块链的细节,这样你就可以像蝙蝠侠的工程师卢修斯·福克斯(Lucius Fox)。 你在本文指南下,你可以设计和生产出高质量的模块,使Nginx做以前不能做的事了。 nginx模块的系统有很多细微差别和细节,所以你可能会想翻这个文档。 我试图尽可能说明确概念,但我会直截了当,努力写好nginx模块。
0.先决条件
你应该熟悉C,呃,当然不是语法层次上的熟悉; 你应该知道结构体,不要被什么指针和函数引用吓跑,并认识到预处理器的。 如果你需要提升这些的话,没有什么比这本书好了 。《The C Programming Language》
HTTP有基本的了解是有益的。 你会工作在Web服务器上,毕竟。
您还应该熟悉Nginx的配置文件。 如果你熟悉,这里是它的要点:有四个上下文contexts (称为main, server, upstream, 和 location),它可以包含一个或多个参数的指令。 在main上下文指令适用于一切; 在server上下文指令适用于一个特定的主机、端口; 在upstream上下文指令适用于一组后端服务器; location上下文指令只适用于匹配网络位置(如”/”, “/images” 等)。
location上下文从server上下文继承,server上下文从main上下文继承。 upstream既不继承,也没有赋予它的属性; 它有没有真正在其他地方适用其自己的特殊指令。 我将把上述四个环境相当多,所以…不要忘记他们。
(或者可以看看这个《(总结)Nginx配置文件nginx.conf中文详解》)
让我们开始吧。。。。
1.Ngnix高级概述之模块授权( Module Delegation)
Nginx的模块有三个角色,我们会逐一介绍它们。
- 处理程序处理请求并产生输出
- 用一个handler对输出进行过滤
- 当超过一个合适的后端服务器存在时候,负载均衡器选择后端服务器发送一个请求。
这些模块做的真正工作,也许你会想与web服务器联系起来。当Nginx需要提供文件或需要代理到另一台服务器的请求,这时候就需要一个处理程序模块; 当Ngnix需要输出一个压缩文件或者是执行一个远程的服务器程序,这时候就需要一个过滤器模块;
Nginx的核心只是照顾所有的网络和应用协议和设置模块,可处理一个请求的序列。在集中式体系结构中,它对于你来说就像是一个很好的独立单元,去完成着工作。呃,简单两个图说明什么是集中式体系结构,或者说集中式系统对应的是分布式系统。
集中式:
分布式:
注:不像Apache模块,Nginx的模块没有动态链接。 (换句话说,他们都是静态编译的,都在Nginx的二进制文件中。)(译者注:至于为什么,这个很容易能想得来,一来不存在多个Ngnix程序大量运行,二来是为了效率,静态编译的程序一般效率比动态链接程序要高。因为后者运行时存在一个模块装入问题)
怎样一个模块才会被调用? 通常情况下,在服务器启动时,每个处理程序会获得一个机会根据配置文件的定义,附加到特定的位置。如果有一个以上的处理程序连接到特定的位置,只有一个会“赢”(但一个好的配置文件不会让冲突发生)。 处理程序可以通过三种方式返回:一切都很好,有错误,也可以??拒绝处理请求,并推迟到默认的处理程序(通常是一些静态文件)。(译者注:可能是讲这个意思,在服务器跑起来时候,每个处理程序会链接一个指定的模块,然后如果有相同的处理程序链接同一个模块则只有一个成功。大概是这个意思吧?)
如果处理器是一个反向代理的一些后端服务器,还有另一种类型的模块间的负载平衡。 负载平衡器需要一个请求和一组后端服务器,并决定哪个服务器将获得的请求。 Nginx的附带两个负载均衡模块:
- 轮询,像打扑克那样轮流着抽牌那样,轮着来。
- IP散列,已用来确保一些特殊的请求会每次达到相同的后端服务器上。
如果处理程序不产生错误,则该过滤器被调用。 多个过滤器可以连接到每个位置,以便(举例子)响应可以被压缩,然后分块。 他们执行的顺序是在编译时确定的。 过滤器有设计模式经典的“责任链”:一个过滤器被调用时,做其工作,然后调用下一个过滤器,直到最后的过滤器被调用,Nginx的完成了响应。
有关过滤器链的很酷的是,每一个过滤器不等待前面的过滤器来完成;因为它可以处理以前过滤器的输出,然后处理完作为自己的输出,有点像linux系统的管道一样,一个是生产者,一个是消费者。只是有点酷炫的是,就像食物链,一样,每个消费者可能也为下一级的消费者提供能量一样。。。而过滤器上的缓冲区 ,这通常是一个页面(4K)的规模经营,虽然你可以在你的nginx.conf改变这种情况。举例子吧,这就意味着一个模块可以在开始压缩后,就去传输客服端,即使还没有得到整个压缩后的结果。(一边压缩一边上存,最通俗的理解,呃,个人认为是这个意思的) 太好了!
因此,总结的概念概述,典型的处理周期如下:
客户端发送HTTP请求→Nginx的根据本地配置文件选择合适的handler→(前提是后端服务器要合适)负载平衡器挑选一个后端服务器→处理程序做的事情,并将每个输出缓冲区的第一个过滤器→第一过滤的相应处理程序将输出到第二过滤器→第二至第三→第三到第四→等→最终响应发送到客户端
我说“通常”,是因为Nginx的的模块调用是特别定制的。它把一个很大的负担放在写模块配置的那个家伙身上(也就是程序员同学…),来定义如何以及何时该模块应该运行(我觉得太大的负担)。调用实际上是通过一系列数目不少的回调,也就是说,你可以提供一个函数来执行:
- 就在服务器读取配置文件之前
- 对于所显示的位置和服务器的每一个配置指令
- 当Nginx的初始化main 配置
- 当Nginx初始化server (即主机/端口)配置
- 当Nginx混合server配置与main配置
- 当Nginx配置初始化location配置
- 当Nginx合并location配置与其parent server配置
- 当nginx的主进程启动
- 当一个工作进程退出
- 当master进程退出
- 处理一个请求
- 过滤响应头
- 过滤相应体
- 选择后端服务器
- 向后端服务器发起请求
- 重新启动对后端服务器的请求
- 处理来自后端服务器的响应
- 完成与后端服务器的交互
好家伙! 这是一个有点势不可挡。你可以利用这些,比如说拦截一些特定的消息(计算机英语中总出现的”hooks” 是拦截特定消息的意思,不是钩子的意思,,,,老外的世界观究竟是怎么样的,怎么我感觉他们说话逻辑好像和我们是完全不一样的。。),再比如说利用上面提到的一对函数,只要你利用这些你手头上的资源,你可以做到好多有用的事情,因为你的权力太大了…好吧,是时候深入的了解某一些模块了!!!!!!!!
2. Nginx的模块组件
正如我所说的,你有很大的灵活性,当谈到作出Nginx的模块。 本节将描述几乎总是存在的部分。 它的目的是为理解一个模块的指南,以及当你觉得你已经准备好开始编写一个模块的引用。
2.1 模块配置结构体
模块最多可以定义三个配置结构,main,server,location。大多数模块只需要一个位置配置。 这些命名约定是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 ); 这些都是你知道的基本数据类型的别名,如果你感兴趣的话。可以看核心/ ngx_config.h这个可以满足你的好奇心。
2.2 模块指令
一个模块的指令出现在一个ngx_command_ts静态数组里面。这里有一个官网公布的例子,一个我(原文作者)写的小小的模块。
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_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数组里面(类似4在int数组中这个意思),这里有一个官网公布的例子,一个我(原文作者)写的小小的模块。
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_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 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(“proxy_pass”);
直接上定义,实际有点像c++的string一样,用C语言模拟的一个string类,这样能理解了吧。
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
type是一组标志,使用位操作按位与之类的,主要有:
- NGX_HTTP_MAIN_CONF :指令是在main配置有效
- NGX_HTTP_SRV_CONF :指令是在server配置有效
- NGX_HTTP_LOC_CONF :指令在location配置有效
- NGX_HTTP_UPS_CONF :指令是upstream配置有效
- NGX_CONF_NOARGS :指令可以接受0参数
- NGX_CONF_TAKE1 :指令可以接受1参数
- NGX_CONF_TAKE2 :指令可以接受2参数
- …
- NGX_CONF_TAKE7 :指令可以接受7参数
- NGX_CONF_FLAG :指令取一个boolean(“开”或“关”)
- NGX_CONF_1MORE :指令可以接受大于等于1个参数
- NGX_CONF_2MORE :指令可以接受大于等于2个参数
还有超级多其他的选项,具体的参考core/ngx_conf_file.h.
set是一个函数指针,指向一个函数,该函数功能是设置模块配置。通常此功能将转化传递给这个指令的参数,并保存在它的配置结构(configuration struct)适当的值。 这种设置功能将需要三个参数:
- 一个指向ngx_conf_t结构的指针,注:ngx_conf_t包含传给该指令的参数
- 一个指针指向当前ngx_command_t结构
- 一个指针指向模块的定制配置结构(configuration struct)
当遇到指令时,该设置函数将被调用。 nginx的提供了许多功能,在自定义配置结构设置特定类型的值。 这些功能包括:
- ngx_conf_set_flag_slot :转换“开”或“关”,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指定写入配置结构(configuration struct)的哪些部分
最后,post只是一个指针,当正在读配置文件时候,它可能需要指向其他休息的模块。它通常为NULL。(原文:Finally, post is just a pointer to other crap the module might need while it’s reading the configuration. It’s often NULL.)
ngx_null_command作为命令序列的终止,放在最后一位。(译者注,就是有点类似C语言的字符串结尾’\0’一样,起到标识结尾的作用,不懂可以ctrl+f定位一下ngx_null_command,就能发现static ngx_command_t ngx_http_circle_gif_commands[]中最后一个就是ngx_null_command)
2.3 模块上下文
这里有一个静态的ngx_http_module_t结构,这玩意儿只是大量的函数引用,这些函数作用都是用于创建三个配置并将它们合并在一起之类的。
因此,这些函数引用分别由:
- 预配置 preconfiguration
- 配置后 postconfiguration
- 创建main配置(即做一个malloc和设置默认值)
- 初始化主要配置(默认设置nginx.conf里的内容)
- 创建server 的配置
- 与server 的配置的配置合并
这些不同的参数取决于他们在做什么。 这里的结构定义,取自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,Ngnix会搞定它的~~
大多数的handlers 只是使用最后的两个,一个功能用于给特定位置configuration分配内存(ngx_http_create_loc_conf ),另外一个功能用于设置默认值,并将此配置合并到任何继承的配置中(ngx_http_merge_loc_conf)。如果配置无效,则合并函数也负责产生错误;这些错误停止服务器启动。
这里有一个例子模块上下文结构( module context struct):
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 */
};
是时候更深入了解他们了。所有的配置看起来都像是调用相同的Ngnix 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;
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的内存分配;它考虑到正在free时候,如果模板正在使用ngx_palloc (一个malloc 的封装函数)或者ngx_pcalloc (一个cmalloc 的封装函数).
可能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_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_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为不同的数据类型提供了很好的融合功能( ngx_conf_merge__value ); 参数是:
- 这个位置的值 this location’s value
- 值继承,如果#1没有设置 the value to inherit if #1 is not set
- 默认如果没有#1#也2被设置 the default if neither #1 nor #2 is set
然后将结果存储在第一个参数。 可用的合并功能包括ngx_conf_merge_size_value , ngx_conf_merge_msec_value和其他。 见core/ ngx_conf_file.h的完整列表。
小问题:如何将这些功能写的第一个参数,因为第一个参数是按值传递吗?
答:这些功能是由预处理器定义,然后你们懂了....
注意这里还是会产生错误:函数写的东西到日志文件,并返回NGX_CONF_ERROR 。返回代码暂停服务器启动。(由于消息是NGX_LOG_EMERG级别记录,该消息将标准输出,仅供参考core/ ngx_log.h, 这里有日志级别的列表。)
2.4 模块定义
计算机世界里面有一句名言,计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。接下来我们添加一个间接更多的层, ngx_module_t结构。 该变量称为ngx_http__module 。这是对上下文和指令的引用,以及余下的回调函数。模块定义有时被用作key来查找与特定模块相关的数据。 该模块的定义通常是这样的:
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
};
替换恰如其分。模块可以添加的回调,进程/线程创建和死亡,但大部分模块让事情变得简单。 (传递给每一个回调的参数,请参阅 core/ ngx_conf_file.h)。
2.5 模块安装
安装模块是否合适,这取决于模块是处理,过滤,或负载平衡器
The proper way to install a module depends on whether the module is a handler, filter, or load-balancer
3.处理程序
现在我们会把一些小的模块放在显微镜下观察它们是如何工作的。
3.1 Handler 剖析(非代理)
处理程序通常做四件事: 获得 位置配置(location configuration), 生成相应的响应, 发送头部, 和发送主体.。处理程序有一个参数就是和请求结构。 请求结构有很多关于客户端的请求,如请求方法,URI,和头有关的信息。我们将会逐一分析他们。
3.1.1 获得 位置配置(location configuration)
这部分容易。 所有你需要做的就是调用ngx_http_get_module_loc_conf并传入当前请求结构和模块定义。比如我写的ngx_http_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_config;
circle_gif_config = ngx_http_get_module_loc_conf(r, ngx_http_circle_gif_module);
。。。
现在我有了我在合并函数中设置的所有变量
3.1.2 生成相应的响应
这是模块实际上做的工作中最有趣的一部分。
这时候你需要知道这个有用的请求结构
typedef struct {
...
/* the memory pool, used 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有很多有用的东西,如cookies和浏览器的信息,但许多模块不需要这玩意儿。呃,如果你有兴趣。请看这里,HTTP / ngx_http_request.h
这应该是足够的信息,以产生一些有用的输出。完整的ngx_http_request_t结构中可以找到http/ngx_http_request.h
3.1.3 发送头部
响应头存活在一个叫headers_out的结构中,这个结构是对的请求结构引用。handler 设置它想要的那些,然后调用ngx_http_send_header(r) 一些有用的部分headers_out包括:
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 last_modified_time;
..
} ngx_http_headers_out_t;
(其余可以在这里找到:http/ngx_http_request.h)。
举例来说,如果一个模块设置Content-Type为“image / GIF”,内容长度设置为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.content_type.data = (u_char *) "image/gif";
ngx_http_send_header(r);
大多数合法的HTTP头是可用的,这种情况下你一般会感觉到很开心,然而,一些头设置有一点麻烦,相比你在上面看到的那些;例如,content_encoding类型(ngx_table_elt_t *),模块必须为它分配内存。这是用一个被调用的函数完成ngx_list_push ,这需要在一个ngx_list_t (类似于一个数组),并返回一个引用,这个引用指向list 刚刚创建出来的成员(类型ngx_table_elt_t )。 下面的代码编码设置为”deflate” ,然后发送头部。
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);
这种机制通常被用来当一个头可以同时在多个值;它(理论上)更便于过滤器模块来增加和同时保留其他删除某一些值,因为他们不必诉诸于字符串操作。
3.1.4 发送主体
现在,模块已经产生一个响应,并把它在内存中,它需要分配给一个特殊的缓冲区的反应,然后分配缓冲区链链接 , 然后调用链条上的“发送体”的功能。
什么是链节? nginx的允许处理模块生成(和过滤器模块处理)响应一次一个缓冲器; 每个链节保持的指针链中的下一个链接,或者NULL ,如果它是最后一个。 我们将保持简单,假设只有一个缓冲区。
首先,一个模块将宣布缓冲器和链条:
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 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 */
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 */
现在,模块将它连接到链上。
out.buf = B;
out.next = NULL;
最后,我们返回主体(boby),并返回输出过滤器链的状态代码
return ngx_http_output_filter(r, &out);
缓冲区链的Nginx的IO模型的重要组成部分,所以你必须熟悉它们的工作原理。
小问题:为什么缓冲区有last_buf变量,我们可以告诉我们正处在一个链的末端通过检查“下一个” NULL ?
回答:A链可能是不完整的,也就是说,有多个缓冲区,而不是在此请求或响应所有缓冲器。 因此,一些缓冲区在链的末端,但不是请求的结束。 这给我们带来...
明天再更吧。。。。。。
=======================================================
好吧,我们接着往下写
3.2。 upstream(即代理服务器)处理器的剖析
我挥动双手,让你的处理程序生成一个响应。有时你会能够得到这种应对只是一段 C 代码,但往往你会想要谈到另一台服务器 (例如,如果您正在编写一个模块来执行另一个网络协议)。你可以亲自做所有的网络编程,但是如果你接收到部分响应会发生什么?你不想要阻止与您自己的事件循环的主事件循环,而你在等待响应的休息。你会杀了 Nginx 的性能。幸运的是,Nginx 可以挂接到其自身机制的权利处理后端服务器 (称为”上游”),这样您的模块可以谈到另一台服务器不妨碍其他请求。本节描述如何模块会谈到上游,例如 Memcached、 FastCGI 或另一个 HTTP 服务器。
3.2.1.摘要上游回调
与其他模块的处理程序函数,不同的是上游的模块的处理程序函数做小小的”真正的工作”。它并不调用ngx_http_output_filter。它只是设置准备写入和读取从上游服务器时将调用的回调。有实际上 6 可用挂钩(挂钩意思:拦截指定的消息,并用自己的方式处理)
create_request:精心制作一个请求缓冲区 (或链着他们) 发送到上游
reinit_request被称为如果到后端的连接被重置 (只是之前create_request被称为第二次)
process_header处理的第一位的上游的反应,并通常保存一个指针,指向上游的”负载”
如果客户端中止请求,则调用abort_request
Nginx 完成时调用finalize_request从上游读取消息
input_filter是一个正文过滤器,可以在响应正文调用 (例如,要删除一个预告片)
这些该如何连接?这里是一个简化的版的代理模块处理程序 ︰
static ngx_int_t
ngx_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_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->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 = ngx_http_read_client_request_body(r, ngx_http_upstream_init);
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}
它做一点内部事务,但重要的部件是回调。此外注意到关于ngx_http_read_client_request_body位。这位被设置时候, Nginx 已经完成从客户端读取另一个回调。
每个这些回调将做什么?通常, reinit_request, abort_request和finalize_request将设置或重置一些排序的内部状态,只有几行字。真正的主力是create_request和process_header.
3.2.2.create_request 回调
为了简单起见,假设我有上游服务器,读取一个字符,并打印出两个字符。我的函数会是什么样子?
create_request需要为单字符请求分配一个缓冲区,为该缓冲区分配链链接,然后指向那链条上游的结构。看起来会像这样 ︰
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 *cl;
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 = cl;
/* 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,别忘了你也享有请求标头和cookies。
3.2.3 process_header 回调
现在是时候为process_header。正如create_request添加到请求正文, process_header 转移指向的部分,则客户将接收的响应的指针。它还从上游头中读取,并相应地设置客户端响应标头。
这里是一个最低的例子,阅读在这两个字符响应。让我们假设的第一个字符是”地位”字符。如果它是一个问号,我们想要返回到客户端找不到 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->upstream;
/* read the first character */
switch(u->buffer.pos[0]) {
case ‘?‘:
r->header_only; /* suppress this buffer from the client */
u->headers_in.status_n = 404;
break;
case ‘ ‘:
u->buffer.pos++; /* move the buffer to point to the next character */
u->headers_in.status_n = 200;
break;
}
return NGX_OK;
}
就是这样,他做了操作hander、 更改pointer.请注意, headers_in实际上响应标头结构像我们以前见过 (参见)http/ngx_http_request.h,但它可以填入从上游的标头。一个真正的代理模块将做更多头处理,更何况错误处理,但已经得到这种的处理思想了。
但是……如果我们没有从来自上游的一个缓冲区的整个头部?
3.2.4 保持状态
好吧,记得我怎么说abort_request,reinit_request和finalize_request可用于复位内部状态?这是因为许多上游模块有内部状态。该模块需要定义一个自定义上下文结构来跟踪它已经从上游迄今读取。这是不一样的“模块上下文”上文提到的。这是一个预先定义的类型,而自定义背景下能有什么元素,你需要的数据(这是你的结构)。这个上下文结构应该在内部被实例化的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);
最后一行基本上注册自定义上下文结构与特定的请求和模块名称,以便于以后检索。每当你需要此上下文结构 (可能在回调),只是做 ︰
ngx_http_proxy_ctx_t *p;
p = ngx_http_get_module_ctx(r, ngx_http_proxy_module);
p会有的当前状态。设置它,将其重置,递增、 递减,推任意数据在那里,任何你想要的东西。这是伟大的方式使用持久性状态机从上游读取时返回数据中的块,再无阻塞主事件循环。太好了 !
3.3.处理程序安装
通过将代码添加到回调的指令,使该模块可以安装处理程序。例如,这个有一个例子ngx_command_t 这样:
{ ngx_string("circle_gif"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_circle_gif,
0,
0,
NULL }
回调是第三个元素,在此案例ngx_http_circle_gif。还记得对此回调函数的参数是指令结构 (ngx_conf_t,其中包含用户的参数)、 有关ngx_command_t结构和模块的自定义配置结构体的指针。在本例子中,功能看起来像 ︰
static char *
ngx_http_circle_gif(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_circle_gif_handler;
return NGX_CONF_OK;
}
有下面两个步骤 ︰ 第一,”核心”结构获得这个位置,然后将处理程序分配给它。很简单,是吗?
我说过我只知道处理模块。它是时间来移动到过滤器模块,输出过滤器链中的组件上。
4.过滤器
过滤器 操作生成的处理程序响应。过滤器操作的 HTTP 标头和正文的过滤操作的响应内容。
4.1.标题筛选器的解剖
标题筛选器包括三个基本步骤 ︰
- 决定是否要动手术,这种反应
- 操作的相应
- 操作的下一个过滤器
举一个例子,这里的一个简化的版的”不修改”标题过滤器,哪集到 304 状态不修饰的如果客户端的如果修改时间以来头匹配响应的最后修改时间标头。注意标题过滤器都需要在ngx_http_request_t结构中,作为唯一的参数,这使我们能够访问客户端头和很快发送响应标头。
static
ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r)
{
time_t if_modified_since;
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结构是一样因为我们看见节中关于处理程序 (参见http/ngx_http_request.h),并可以操纵,没有止境。
4.2 正文筛选器的解剖
缓冲区链使它有点棘手写体筛选器中,因为正文过滤器可以一次只操作一个缓冲区 (链条)。模块必须决定是否向覆盖输入缓冲区,替换缓冲区与新分配的缓冲区,或插入一个新的缓冲区之前或之后的缓冲区问题。把事情复杂化,有时模块将接收几个缓冲区,它已不完全缓冲链,它必须动手术。不幸的是,Nginx 不操纵缓冲区链,所以正文过滤器可以很难理解 (和写) 提供一个高级别的 API。但是,这里有一些你可能会看到在行动中的操作。
正文过滤器原型可能看起来像 (本例中取自 Nginx 源中的”分块”筛选器) ︰
static ngx_int_t ngx_http_chunked_body_filter(ngx_http_request_t *r, ngx_chain_t *in);
第一个参数是我们的老朋友请求结构。第二个参数是一个指针,指向当前部分链 (它可以包含 0、 1 或更多的缓冲区) 的头。
让我们看一个简单的例子。假设我们想要插入文本”
ngx_chain_t *chain_link;
int chain_contains_last_buffer = 0;
chain_link = in;
for ( ; ; ) {
if (chain_link->buf->last_buf)
chain_contains_last_buffer = 1;
if (chain_link->next == NULL)
break;
chain_link = chain_link->next;
}
现在让我们来保释出来,如果我们没有那最后的缓冲区 ︰
if (!chain_contains_last_buffer)
return ngx_http_next_body_filter(r, in);
现在最后一个缓冲区存储在 chain_link 中。现在我们分配一个新的缓冲区 ︰
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;
并将其挂钩缓冲区进入一个新的链链接 ︰
ngx_chain_t *added_link;
added_link = ngx_alloc_chain_link(r->pool);
if (added_link == NULL)
return NGX_ERROR;
added_link->buf = b;
added_link->next = NULL;
最后,钩到最后的链条,我们发现之前的新链链接 ︰chain_link->next = added_link;
和重置要反映现实的”last_buf”变量 ︰
chain_link->buf->last_buf = 0;
added_link->buf->last_buf = 1;
和沿着改性链传递到下一个输出筛选器 ︰
return ngx_http_next_body_filter(r, in);
得到的函数需要更多的努力,比你会做什么用,比如说 mod_perl ($response->body =~ s/$/<!-- Served by mod_perl -->/)
,但缓冲区链是一个非常强大的构造,使程序员能够以增量方式处理数据,以便客户端获取的东西,尽快。然而,在我看来,缓冲区链迫切需要一个更清洁的接口,程序员不能离开链处于不一致状态。现在,操作风险由您自己承担。
4.3.过滤器安装
筛选器被安装后配置步骤中。我们在同一个地方安装头过滤器和正文过滤器。
让我们看看一个简单的例子的分块的过滤器模块。其模块上下文看起来像这样 ︰
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_filter;
ngx_http_top_header_filter = ngx_http_chunked_header_filter;
ngx_http_next_body_filter = ngx_http_top_body_filter;
ngx_http_top_body_filter = ngx_http_chunked_body_filter;
return NGX_OK;
}
这里怎么一回事?好吧,如果你还记得,过滤器设置了责任链。当处理程序生成的响应时,它会调用两个函数 ︰ 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是各自的”首长”的正文和头的筛选器链。每个链上的”链接”保持到下一个链接的函数引用链 (这些引用被称为ngx_http_next_body_filter和ngx_http_next_header_filter) 中。当筛选完成后执行的它只是调用下一个过滤器,,直到特别定义的”写”筛选器被调用,其中包扎的 HTTP 响应。在此 filter_init 函数你看到什么是模块将自身添加到筛选器链;它在”下一步”的变量中保持对旧的”顶”筛选器的引用,并声明的功能,成为新的”顶”过滤器。(因此,最后一个筛选器来安装是首先被处决)
边注 ︰ 到底是如何工作?
每个筛选器返回一个错误代码,或者使用此参数作为返回语句 ︰
return ngx_http_next_body_filter();
因此,如果筛选器链的上游 (特别规定) 链末端的"OK"响应是返回,但如果是一路走来,错误链被切断短 Nginx 服务了相应的错误消息。它是一个单向链接与快速故障实施仅与函数引用列表。
5.负载平衡器
负载平衡器只是方式来决定哪个后端服务器将接受一个特定的请求;实现存在分发请求在轮循机制方式或哈希处理一些有关请求的信息。这一节将描述负载平衡器的安装和它的调用,使用 upstream_hash 模块 (完整源代码) 作为一个例子。upstream_hash 选择一个后端通过散列变量中指定的 nginx.conf。
负载平衡的模块有六件 ︰
- 有利的配置指令 (例如,hash;) 将调用注册函数
- 注册功能将定义规定server选项 (例如,weight=) 和注册上游初始化函数
- 上游的初始化函数被称为刚配置得到验证,和它 ︰
3.1.将server名称解析为特定的 IP 地址
3.2.对于套接字分配空间
3.3.设置回调到同行的初始化函数
- 同行的初始化函数,调用一次每个请求,设置数据结构的负载平衡功能将访问和操作;
- 负载平衡功能决定在哪路由请求;每个客户端请求 (更多,如果后端请求失败),它被称为至少一次。这是有趣的事情发生的地方。
- 并且最后,同行释放函数可以更新统计后与特定的后端服务器的通信已完成 (无论成功或不成功)
它是很多,但我会把它分解成碎片。
5.1.有利的指令
指令声明,召回,指定它们是有效和函数调用时遇上了你。一个指令,一个负载平衡器应该有的NGX_HTTP_UPS_CONF标记设置,以便 Nginx 知道此指令只是在upstream的块内有效。它应该提供一个注册函数指针。这里是从 upstream_hash 模块的指令声明 ︰
{ ngx_string("hash"),
NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
ngx_http_upstream_hash,
0,
0,
NULL },
5.2.注册功能
回调ngx_http_upstream_hash以上是注册功能,因此被命名 (我) 因为它与周围的upstream配置注册上游初始化函数。此外,注册函数定义了哪些选项的server指令是此特定upstream块内部法律 (例如,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 *uscf;
ngx_http_script_compile_t sc;
ngx_str_t *value;
ngx_array_t *vars_lengths, *vars_values;
value = cf->args->elts;
/* 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 = cf;
sc.source = &value[1];
sc.lengths = &vars_lengths;
sc.values = &vars_values;
sc.complete_lengths = 1;
sc.complete_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_hash;
uscf->flags = NGX_HTTP_UPSTREAM_CREATE;
/* OK, more $variable stuff */
uscf->values = vars_values->elts;
uscf->lengths = vars_lengths->elts;
/* set a default value for "hash_method" */
if (uscf->hash_function == NULL) {
uscf->hash_function = ngx_hash_key;
}
return NGX_CONF_OK;
}
我们以后可以评价$variable ,它是相当简单;指定一个回调,设置一些标志。提供了什么标志?
- NGX_HTTP_UPSTREAM_CREATE︰ 让有server指示此上游的块中。我想不到的地方你不会使用这种情况。
- 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将不会自动执行;所有的故障逻辑是模块的作者。以后会更多。现在,我们还没有完成追踪回调。接下来,我们有上游初始化函数 (以前的目标函数中的init_upstream回调)。
5.3.上游初始化函数
上游的初始化函数的用途是解析主机名称,为套接字,分配空间并分配 (另一个) 回调。这里是 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 *server;
ngx_http_upstream_hash_peers_t *peers;
/* set the callback */
us->peer.init = ngx_http_upstream_init_upstream_hash_peer;
if (!us->servers) {
return NGX_ERROR;
}
server = us->servers->elts;
/* 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->nelts; i++) {
n += server[i].naddrs;
}
/* 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->nelts; i++) {
for (j = 0; j < server[i].naddrs; j++, n++) {
peers->peer[n].sockaddr = server[i].addrs[j].sockaddr;
peers->peer[n].socklen = server[i].addrs[j].socklen;
peers->peer[n].name = server[i].addrs[j].name;
}
}
/* save a pointer to our peers for later */
us->peer.data = peers;
return NGX_OK;
}
这个函数是不是一个人可能会希望更多地参与。大部分的工作看起来像它应该相当抽象,但它不是,所以这就是我们的相处。一种策略简化事情是调用另一个模块,上游的初始化函数有它做的脏读工作 (同行分配等),并随后重写us->peer.init回调之后。有关示例,请参见
http/modules/ngx_http_upstream_ip_hash_module.c.
5.4.同行初始化函数
同行的初始化函数的每个请求调用一次。它设置了一个数据结构,模块将使用正试图找到一个适当的后端服务器来服务这项要求;这种结构在后端重新尝试,是持续的因此它是一个方便的地方,来跟踪连接失败或计算出来的散列值的数目。按照惯例,这个结构被称为ngx_http_upstream__peer_data_t.
此外,同行初始化函数设置两个回调 ︰
- get︰ 负载平衡功能
- free︰ 同行释放功能 (当连接完成后,通常只是更新一些统计数据)
仿佛这还不够,它还 initalizes 变量称为tries。只要tries是积极的 nginx 将不断重试此负载平衡器。当tries为零时,nginx 会放弃。它是get和free的功能,适当设置tries。
这里是 upstream_hash 模块中的对等方初始化函数 ︰
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 *uhpd;
ngx_str_t val;
/* 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;
uhpd->peers = us->peer.data;
/* set the callbacks and initialize "tries" to "hash_again" + 1*/
r->upstream->peer.free = ngx_http_upstream_free_hash_peer;
r->upstream->peer.get = ngx_http_upstream_get_hash_peer;
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;
}
没那么糟糕。现在,我们已准备好要选取上游服务器的功能。
5.5.负载平衡功能
负载平衡功能的原型看起来像 ︰
static ngx_int_t
ngx_http_upstream_get_<module_name>_peer(ngx_peer_connection_t *pc, void *data);
data是我们有用的信息,关于此客户端连接的结构。pc将有我们要连接到的服务器的信息。负载平衡功能的工作是填写pc->sockaddr、 pc->socklen,和pc->name值。如果你知道一些网络编程,那么这些变量的名称可能是熟悉;但他们实际上不到手头的任务非常重要。我们不介意他们的代表;我们只是想要知道在哪里可以找到适当的值来填充。
此函数必须找到可用服务器的列表,选择一个行业,并将它的值分配给pc。让我们看看 upstream_hash 如何它。
upstream_hash 以前藏服务器列表到回中调用ngx_http_upstream_init_hash (以上) 的ngx_http_upstream_hash_peer_data_t结构。此结构现已作为data:
ngx_http_upstream_hash_peer_data_t *uhpd = data;
同行的列表现在存储在uhpd->peers->peer。让我们计算出来的散列值除以服务器数目选取同行从这个数组 ︰
ngx_peer_addr_t *peer = &uhpd->peers->peer[uhpd->hash % uhpd->peers->number];
结尾:
pc->sockaddr = peer->sockaddr;
pc->socklen = peer->socklen;
pc->name = &peer->name;
return NGX_OK;
这就完了,如果负载平衡器返回NGX_OK,这意味着,”下去,试此服务器”。如果它返回NGX_BUSY,它意味着所有后端主机不可用,和 Nginx 应该再试一次。
……,但怎么做我们跟踪的不可用的是什么?我们不想再试一次吗?
5.6.同行释放函数
同行释放函数操作后上游连接发生;其目的是跟踪失败。这里是它的函数原型 ︰
void
ngx_http_upstream_free_<module name>_peer(ngx_peer_connection_t *pc, void *data,
ngx_uint_t state);
前两个参数都一样正如我们看到在负载平衡器的作用。第三个参数是一个state变量,指示连接是否成功。它可能包含按位或会在一起的两个值 ︰ NGX_PEER_FAILED (连接失败) 和NGX_PEER_NEXT (无论是连接失败,或它成功,但应用程序返回了一个错误)。零表示连接成功了。
它是模块的作者来决定如何处理这些失败事件。如果他们在所有使用,结果应该存储在data,自定义的每个请求的数据结构的指针。
但同行释放功能的关键目的是设置pc->tries为零如果你不想 Nginx 在此请求过程中不断尝试此负载平衡器。最简单的同行释放函数将如下所示 ︰
pc->tries = 0;
这将确保,如果有任何错误到达后端服务器,502 不良代理错误将返回给客户端。
这里是一个更复杂的例子,取自 upstream_hash 模块。如果后端连接失败时,它标记为失败的位向量 (称为tried,数组类型uintptr_t),然后选择一个新的后端,直到它找到一个没有保持。
#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 = data;
ngx_uint_t current;
if (state & NGX_PEER_FAILED
&& --pc->tries)
{
/* the backend that failed */
current = uhpd->hash % uhpd->peers->number;
/* 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->number;
} while ((uhpd->tried[ngx_bitvector_index(current)] & ngx_bitvector_bit(current)) && --pc->tries);
}
}
这样做是因为负载平衡功能将只是看看新的uhpd->hash值.
许多应用程序不需要重试或高可用性的逻辑,但它是可能提供的只是几行代码就像你在这里看到。
6.编写和编译一个新的 Nginx 模块
所以到现在为止,你应该准备看看 Nginx 模块,试着了解什么 (和你会知道去哪里寻求帮助)。看看在src/http/modules/来查看可用的模块。选择一个模块,类似于你来完成,并通过它看到底。东西看起来很熟悉吗?它应该。请参阅本指南与模块源要了解什么。
但 Emiller 没有写出的球在指南阅读 Nginx 模块。地狱没有。这是一个球出指南。我们没有在读。我们正在编写。创建。与世界分享。
第一件事,你需要一个地方工作对您的模块。让您在您的硬盘的任何位置的模块文件夹但分开 Nginx 源 (和确保您有最新的副本从nginx.net)新文件夹应该开始于包含两个文件 ︰
"config"
"ngx_http_<your module>_module.c"
“配置”文件将包括由./configure,和它的内容将取决于类型的模块。
“配置”为滤波器模块 ︰
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"
“配置”为其他模块 ︰
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 文件。我推荐复制一个现有的模块,做着类似的对你的希望,但重命名该”ngx_http__module.c”让这成为你的模型,当你改变行为,以满足您的需求,并参考本指南您理解并重塑的不同部分。
当你准备编译时,只是进入的 Nginx 目录
./configure --add-module=path/to/your/new/module/directory
然后make,make install像你通常会。如果一切顺利,将正确编译您的模块中。不错,是吧?不需要要与 Nginx 粪肥源,并将添加到新版本的 Nginx 模块一个单元,只需使用同一./configure命令。顺便说一句,如果您的模块需要的任何动态链接的库,您可以添加这到你的”配置”文件 ︰
CORE_LIBS="$CORE_LIBS -lfoo"
## 7.高级主题 ##
本指南涵盖 Nginx 模块开发的基本知识。在编写更复杂的模块上的提示,请务必查阅.Nginx 模块开发 Emiller 的高级主题
附录 a ︰ 代码引用
示例插件 ︰ circle_gif
示例插件 ︰ upstream_hash
示例插件 ︰ upstream_fair