Nginx的模块开发指南

Posted tab_tab_tab

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx的模块开发指南相关的知识,希望对你有一定的参考价值。

原文: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的模块有三个角色,我们会逐一介绍它们。

  1. 处理程序处理请求并产生输出
  2. 用一个handler对输出进行过滤
  3. 当超过一个合适的后端服务器存在时候,负载均衡器选择后端服务器发送一个请求。

这些模块做的真正工作,也许你会想与web服务器联系起来。当Nginx需要提供文件或需要代理到另一台服务器的请求,这时候就需要一个处理程序模块; 当Ngnix需要输出一个压缩文件或者是执行一个远程的服务器程序,这时候就需要一个过滤器模块;

Nginx的核心只是照顾所有的网络和应用协议和设置模块,可处理一个请求的序列。在集中式体系结构中,它对于你来说就像是一个很好的独立单元,去完成着工作。呃,简单两个图说明什么是集中式体系结构,或者说集中式系统对应的是分布式系统。

集中式:

分布式:
注:不像Apache模块,Nginx的模块没有动态链接。 (换句话说,他们都是静态编译的,都在Nginx的二进制文件中。)(译者注:至于为什么,这个很容易能想得来,一来不存在多个Ngnix程序大量运行,二来是为了效率,静态编译的程序一般效率比动态链接程序要高。因为后者运行时存在一个模块装入问题)

怎样一个模块才会被调用? 通常情况下,在服务器启动时,每个处理程序会获得一个机会根据配置文件的定义,附加到特定的位置。如果有一个以上的处理程序连接到特定的位置,只有一个会“赢”(但一个好的配置文件不会让冲突发生)。 处理程序可以通过三种方式返回:一切都很好,有错误,也可以​​拒绝处理请求,并推迟到默认的处理程序(通常是一些静态文件)。(译者注:可能是讲这个意思,在服务器跑起来时候,每个处理程序会链接一个指定的模块,然后如果有相同的处理程序链接同一个模块则只有一个成功。大概是这个意思吧?)

如果处理器是一个反向代理的一些后端服务器,还有另一种类型的模块间的负载平衡。 负载平衡器需要一个请求和一组后端服务器,并决定哪个服务器将获得的请求。 Nginx的附带两个负载均衡模块

  1. 轮询,像打扑克那样轮流着抽牌那样,轮着来。
  2. 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)适当的值。 这种设置功能将需要三个参数:

  1. 一个指向ngx_conf_t结构的指针,注:ngx_conf_t包含传给该指令的参数
  2. 一个指针指向当前ngx_command_t结构
  3. 一个指针指向模块的定制配置结构(configuration struct)

当遇到指令时,该设置函数将被调用。 nginx的提供了许多功能,在自定义配置结构设置特定类型的值。 这些功能包括:

  1. ngx_conf_set_flag_slot :转换“开”或“关”,1或0
  2. ngx_conf_set_str_slot :保存字符串作为ngx_str_t
  3. ngx_conf_set_num_slot :解析一个数字,并将其保存到一个int
  4. 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 ); 参数是:

  1. 这个位置的值 this location’s value
  2. 值继承,如果#1没有设置 the value to inherit if #1 is not set
  3. 默认如果没有#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.标题筛选器的解剖

标题筛选器包括三个基本步骤 ︰

  1. 决定是否要动手术,这种反应
  2. 操作的相应
  3. 操作的下一个过滤器

举一个例子,这里的一个简化的版的”不修改”标题过滤器,哪集到 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_

以上是关于Nginx的模块开发指南的主要内容,如果未能解决你的问题,请参考以下文章

Nginx的模块开发指南

「题解」蝙蝠侠的麻烦

MLX90640 红外热成像仪传感器模块开发笔记

Matlab实现蝙蝠优化算法

Matlab实现蝙蝠优化算法

点击 更改时间。蝙蝠在窗户