nginx Handler模块开发过程整理

news/2024/7/7 5:35:16 标签: nginx, handler


一、综述

一般来说,在nginx中作为第三方开发者最可能开发的类型模块包括:handler,filter和load-balancer。

handler模块主要是用来接受来自客户端的请求并产生输出的模块。
也有说upstream模块实际上也是一种handler模块,只不过它产生的内容来自于从后端服务器获取的,而非本机产生的。

在配置文件中,使用location指令可以配置content handler模块,当nginx系统系统的时候,每个handler模块都关联了同一个location,而实际上只有一个handler模块真正会起作用。当然,大多数情况下模块开发人员都会避免这种情况出现。


handler 模块处理的结果通常有三种情况: 处理成功,处理失败(处理的时候发生了错误)或者是拒绝去处理。
在拒绝处理的情况下,这个 location 的处理就会由默认的 handler 模块来进行处理。

例如,当请求一个静态文件的时候,如果关联到这个 location 上的一个 handler模块拒绝处理,就会由默认的 ngx_http_static_module 模块进行处理,该模块是一个典型的handler 模块。



二、模块的基本结构

2.1 模块配置结构

基本上每个模块都会提供一些配置指令,以便于用户可以通过配置来控制该模块的行为。
那么这些配置信息怎么存储呢?那就需要定义该模块的配置结构来进行存储。

Nginx 的配置信息分成了几个作用域(scope,有时也称作上下文),这就是 main, server, 以及location。同样的每个模块提供的配置指令也可以出现在这几个作用域里。那对于这三个作用域的配置信息,每个模块就需要定义三个不同的数据结构去进行存储。当然,不是每个模块都会在这三个作用域都提供配置指令的,需要几个就定义几个。

需要注意的是:
在模块的开发过程中,我们最好使用 nginx 原有的命名习惯。保持编码风格一致。格式:ngx_http__(main|srv|loc)_conf_t


2.2 模块配置指令

模块的配置指令定义在一个静态的数组中。

ngx_command_t 结构体的定义位于 src/core/ngx_conf_file.h 中

struct ngx_command_s {
	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:配置指令的名称
  • type:该配置的类型(也称为该配置指令属性的集合)。nginx提供了很多预定义的属性值,通过逻辑或运算符组合在一起,形成对这个配置指令的详细说明。取值如下图:
  • set:函数指针,当nginx在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理。函数指针原型:char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);处理成功时,返回 NGX_OK,否则返回NGX_CONF_ERROR 或者是一个自定义的错误信息的字符串。
  • conf:该字段被NGX_HTTP_MODULE类型模块所用,指定当前配置项存储的内存位置。实际上是使用哪个内存池。http模块对所有 http 模块所要保存的配置信息,划分了 main, server 和 location 三个地方进行存储,每个地方都有一个内存池用来分配存储这些信息的内存。这里可能 的 值 为 NGX_HTTP_MAIN_CONF_OFFSET 、 NGX_HTTP_SRV_CONF_OFFSET 或NGX_HTTP_LOC_CONF_OFFSET。当然也可以直接置为0 , 就是NGX_HTTP_MAIN_CONF_OFFSET。
  • offset:指定该配置项值的精确存放位置,一般指定为某一个结构体变量的字段偏移。因为对于配置信息的存储,一般我们都是定义个结构体来存储的。那么比如我们定义了一个结构体 A,该项配置的值需要存储到该结构体的 b 字段。那么在这里就可以填写为 offsetof(A, b)。对于有些配置项,它的值不需要保存或者是需要保存到更为复杂的结构中时,这里可以设置为 0。
  • post:该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理。大多数时候,都不需要,所以简单地设为 0 即可。

示例:

static ngx_command_t ngx_http_hello_commands[] = {
	{
		ngx_string("hello_string"),
		NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
		ngx_http_hello_string,
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_hello_loc_conf_t, hello_string),
		NULL 
	},
	{
		ngx_string("hello_counter"),
		NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
		ngx_http_hello_counter,
		NGX_HTTP_LOC_CONF_OFFSET,
		offsetof(ngx_http_hello_loc_conf_t, hello_counter),
		NULL 
	},
	ngx_null_command
};

示例定义了两个配置指令,hello_string(可以接受一个参数,或者没有参数)、hello_counter(接受一个NGX_CONF_FLAG类型的参数)。

需要注意的是,在 ngx_http_hello_commands 这个数组定义的最后,都要加一个ngx_null_command 作为结尾。


2.3 模块上下文结构

模块上下文结构是一个 ngx_http_module_t 类型的静态变量。这个变量实际上是提供一组回调函数指针,这些函数有在创建存储配置信息的对象函数,也有在创建前和创建后会调用的函数,这些函数都将被nginx在合适的时间进行调用。

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;

参数说明:

  • preconfiguration:在创建和读取该模块的配置信息之前被调用。
  • postconfiguration:在创建和读取该模块的配置信息之后被调用。
  • create_main_conf:调用该函数创建本模块位于 http block 的配置信息存储结构。该函数成功的时候,返回创建的配置对象。失败的话,返回 NULL。
  • init_main_conf:调用该函数初始化本模块位于 http block 的配置信息存储结构。该函数成功的时候,返回 NGX_CONF_OK。失败的话,返回 NGX_CONF_ERROR 或错误字符串。
  • create_srv_conf:调用该函数创建本模块位于 http server block 的配置信息存储结构,每个 server block 会创建一个。该函数成功的时候,返回创建的配置对象。失败的话,返回 NULL。
  • merge_srv_conf:因为有些配置指令既可以出现在 http block,也可以出现在 http server block 中。那么遇到这种情况,每个 server都会有自己存储结构来存储该 server 的配置,但是在这种情况下 http block 中的配置与 server block 中的配置信息发生冲突的时候,就需要调用此函数进行合并,该函数并非必须提供,当预计到绝对不会发生需要合并的情况的时候,就无需提供。当然为了安全起见还是建议提供。该函数执行成功的时候,返回 NGX_CONF_OK。失败的话,返回 NGX_CONF_ERROR 或错误字符串。
  • create_loc_conf:调用该函数创建本模块位于 location block 的配置信息存储结构。每个在配置中指明的 location 创建一个。该函数执行成功,返回创建的配置对象。失败的话,返回 NULL。
  • merge_loc_conf:与 merge_srv_conf 类似,这个也是进行配置值合并的地方。该函数成功的时候,返回 NGX_CONF_OK。失败的话,返回 NGX_CONF_ERROR 或错误字符串。


Nginx 里面的配置信息都是上下一层层的嵌套的,对于具体某个 location 的话,对于同一个配置,如果当前层次没有定义,那么就使用上层的配置,否则使用当前层次的配置。

配置信息一般默认都应该设为一个未初始化的值,针对这个需求, Nginx 定义了一系列的宏定义来代表各种配置所对应数据类型的未初始化值,如下:

#define NGX_CONF_UNSET -1
#define NGX_CONF_UNSET_UINT (ngx_uint_t) -1
#define NGX_CONF_UNSET_PTR (void *) -1
#define NGX_CONF_UNSET_SIZE (size_t) -1
#define NGX_CONF_UNSET_MSEC (ngx_msec_t) -1


因为对于配置项的合并,逻辑都类似,也就是前面已经说过的,如果在本层次已经配置了,也就是配置项的值已经被读取进来了(那么这些配置项的值就不会等于上面已经定义的那些UNSET 的值),就使用本层次的值作为定义合并的结果,否则,使用上层的值,如果上层的值也是这些 UNSET 类的值,那就赋值为默认值,否则就使用上层的值作为合并的结果。对于这样类似的操作, Nginx 定义了一些宏操作来做这些事情,我们来看其中一个的定义。

#define ngx_conf_merge_uint_value(conf, prev, default)
\
if (conf == NGX_CONF_UNSET_UINT)
{ \
	conf = (prev == NGX_CONF_UNSET_UINT) ? default : prev;
	\
}

显而易见,这个逻辑确实比较简单,所以其它的宏定义也类似,其中的一部分如下:

ngx_conf_merge_value
ngx_conf_merge_ptr_value
ngx_conf_merge_uint_value
ngx_conf_merge_msec_value
ngx_conf_merge_sec_value

示例:

static ngx_http_module_t ngx_http_hello_module_ctx = {
	NULL, /* preconfiguration */
	ngx_http_hello_init, /* postconfiguration */
	NULL, /* create main configuration*/
	NULL, /* init main configuration */
	NULL, /* create server configuration*/
	NULL, /* merge server configuration*/
	ngx_http_hello_create_loc_conf, /* create locationconfiguration */
	NULL /* merge location configuration*/
};

需要注意的是:这里并没有提供 merge_loc_conf 函数,因为我们这个模块的配置指令已经确定只出现在 NGX_HTTP_LOC_CONF 中这一个层次上,不会发生需要合并的情况。


2.4 模块的定义

对于开发一个模块来说,都需要定义一个 ngx_module_t 类型的变量来说明这个模块本身的信息,从某种意义上来说,这是模块最重要的一个信息,它告诉了nginx这个模块的一些信息。上面定义的配置信息,还有模块上下文信息,都是通过这个结构来告诉nginx系统的,也就是加载模块的上层代码,都需要通过定义这个结构,来获取信息。

ngx_module_t 定义

typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s {
	ngx_uint_t ctx_index;
	ngx_uint_t index;
	ngx_uint_t spare0;
	ngx_uint_t spare1;
	ngx_uint_t abi_compatibility;
	ngx_uint_t major_version;
	ngx_uint_t minor_version;
	void *ctx;
	ngx_command_t *commands;
	ngx_uint_t type;
	ngx_int_t (*init_master)(ngx_log_t *log);
	ngx_int_t (*init_module)(ngx_cycle_t *cycle);
	ngx_int_t (*init_process)(ngx_cycle_t *cycle);
	ngx_int_t (*init_thread)(ngx_cycle_t *cycle);
	void (*exit_thread)(ngx_cycle_t *cycle);
	void (*exit_process)(ngx_cycle_t *cycle);
	void (*exit_master)(ngx_cycle_t *cycle);
	uintptr_t spare_hook0;
	uintptr_t spare_hook1;
	uintptr_t spare_hook2;
	uintptr_t spare_hook3;
	uintptr_t spare_hook4;
	uintptr_t spare_hook5;
	uintptr_t spare_hook6;
	uintptr_t spare_hook7;
};
#define NGX_NUMBER_MAJOR 3
#define NGX_NUMBER_MINOR 1
#define NGX_MODULE_V1 0, 0, 0, 0, 
\
NGX_DSO_ABI_COMPATIBILITY, NGX_NUMBER_MAJOR,
NGX_NUMBER_MINOR
#define NGX_MODULE_V1_PADDING 0, 0, 0, 0, 0, 0, 0, 0

示例:

ngx_module_t ngx_http_hello_module = {
	NGX_MODULE_V1,
	&ngx_http_hello_module_ctx, /* module context */
	ngx_http_hello_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
};

模块可以提供一些回调函数给nginx,当nginx在创建进程线程或结束进程线程时进行调用。但大多数模块在这些时刻并不需要做什么,因此都简单赋值为NULL。



handlerfont_242">三、handler模块

handler_244">3.1 handler模块的基本结构

除了第二节中提供的基础结构外,handler模块必须提供一个真正的处理函数,这个函数负责对来自客户端请求的真正处理。这个函数的处理,既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的handler进行处理,或是选择丢个后续的filter处理。

函数原型声明:

typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);

r 是 http 请求。里面包含请求所有的信息。函数处理成功返回 NGX_OK,处理发生错误返回 NGX_ERROR,拒绝处理(留给后续的 handler进行处理)返回 NGX_DECLINE。 返回 NGX_OK 也就代表给客户端的响应已经生成好了,否则返回 NGX_ERROR 就发生错误了。


handler_258">3.2 handler模块的挂载

handler模块真正处理函数通过两种方式挂载到处理过程中:按处理阶段挂载和按需挂载


1、按处理阶段挂载

使用这种方式挂载的 handler 也被称为 content phase handlers。
为了更加精细的控制对于客户端的请求处理过程,nginx把这个处理过程划分成了11个阶段。阶段列举如下:
在这里插入图片描述
一般情况下,我们自己定义的模块,大多数是挂载在 NGX_HTTP_CONTENT_PHASE阶段。
挂载动作一般在模块上下文调用的postconfiguration函数中实现。

有几个阶段是特例,它不调用挂载的任何地方的handler,也就是你就不用挂载到这几个阶段,这几个阶段是:NGX_HTTP_FIND_CONFIG_PHASE、NGX_HTTP_POST_ACCESS_PHASE、NGX_HTTP_POST_REWRITE_PHASE、NGX_HTTP_TRY_FILES_PHASE


挂载代码:

static ngx_int_t
ngx_http_hello_init(ngx_conf_t *cf)
{
	ngx_http_handler_pt *h;
	ngx_http_core_main_conf_t *cmcf;
	cmcf = ngx_http_conf_get_module_main_conf(cf,
	ngx_http_core_module);
	h =
	ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
	if (h == NULL) {
		return NGX_ERROR;
	}
	*h = ngx_http_hello_handler;
	return NGX_OK;
}



2、按需挂载

这种方式挂载的handler也被称为content handler

当一个请求进来以后, nginx 从 NGX_HTTP_POST_READ_PHASE 阶段开始依次执行每个阶段中所有 handler。执行到 NGX_HTTP_CONTENT_PHASE 阶段的时候,如果这个 location 有一个对应的 content handler 模块,那么就去执行这个 content handler 模块真正的处理函数。否则继续依次执行 NGX_HTTP_CONTENT_PHASE 阶段中所有 content phase handlers,直到某个函数处理返回 NGX_OK 或者 NGX_ERROR。

换句话说,当某个 location 处理到 NGX_HTTP_CONTENT_PHASE 阶段时,如果有 content handler模块,那么 NGX_HTTP_CONTENT_PHASE挂载的所有 content phase handlers都不会被执行了。但是使用这个方法挂载上去的 handler 有一个特点是必须在 NGX_HTTP_CONTENT_PHASE 阶段才能执行到。如果你想自己的 handler 在更早的阶段执行,那就不要使用这种挂载方式。


那什么情况下使用按需挂载呢?
某个模块对某个 location 进行了处理以后,发现符合自己处理的逻辑,而且也没有必要再调用NGX_HTTP_CONTENT_PHASE 阶段的其它 handler 进行处理的时候,就动态挂载上这个 handler


挂载源码:

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;
}


handler_322">3.3 handler的编写步骤


handler实现步骤总结:

  1. 编写模块基本结构,包括模块的定义、模块上下文结构、模块的配置结构等。
  2. 实现handler的挂载函数,根据模块的需求选择正确的挂载方式。
  3. 编写handler处理函数,模块的功能主要通过这个函数完成。


handler__333">3.4 handler 模块的编译和使用


1、 编写config文件

对于开发一个模块,需要把这个模块的 C 代码组织到一个目录里,同时需要编写一个config 文件。

这个 config 文件的内容就是告诉 nginx 的编译脚本,该如何进行编译。比如 hello handler module 的 config 文件的内容:

ngx_addon_name=ngx_http_hello_module
HTTP_MODULES="$HTTP_MODULES ngx_http_hello_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS
$ngx_addon_dir/ngx_http_hello_module.c"

需要说明的是,如果这个模块的实现有多个源文件,那么都在 NGX_ADDON_SRCS 这个变量里,依次写进去就可以。


2、编译

不同于apache源码,提供了单独的编译工具,可以在没有apache源码的情况下单独编译一个模块的代码。
nginx模块的编译,需要到nginx的源码目录里,通过configure指令的参数进行编译。

以hello module为例,编译指令:

$ ./configure –prefix=/usr/local/nginx-1.4.1 –add-module=/home/my/open_source/hello_module

注:示例模块的代码和 config 文件都放在/home/my/open_source/hello_module 这个目录下。


3、使用

使用一个模块需要根据这个模块定义的配置指令来做。比如我们这个简单的 hello handler module 的使用就很简单。在测试服务器的配置文件里,就是在 http 里面的默认的 server里面加入如下的配置:

location /test {
	hello_string world;
	hello_counter on;
}

当我们访问这个地址的时候, http://127.0.0.1/test 的时候,就可以看到返回的结果。


http://www.niftyadmin.cn/n/1531494.html

相关文章

ZeroMQ的编译安装和使用实例

一、编译安装 1.1 资料网址 ZeroMQ官方文档网址:http://zguide.zeromq.org/page:all 源码下载地址:http://download.zeromq.org/ 1.2 安装必须的库 $ sudo apt-get install libtool $ sudo apt-get install pkg-config $ sudo apt-get install build-es…

数据库文件的版本为706,当前服务器支持662版几更低版本。不支持降级路径。

706是SQL Server 2012 数据文件的内部版本号,而你正在运行的服务是2008(662及更低版本),SQL SERVER无法向上兼容,即无法将706降级到662。

ZeroMQ源码分析笔记之架构

一、概述 ZeroMQ是一种基于消息队列的多线程网络库,其对套接字类型、连接处理、帧、甚至路由的底层细节进行抽象。提供跨越多种传输协议的套接字。 引用云风的话来说:ZeroMQ并不是对一个socket的封装,不能用它去实现已有的网络协议。它不同于…

ZeroMQ源码分析笔记之线程间收发命令

一、概述 从ZeroMQ源码分析笔记之架构了解到,线程间通信包括两类: 1) 一类用于收发命令,告知对象调用什么方法做什么事情,命令结构由command_t 结构体确定。 2)socket_base_t 实例与session的消息通信&…

远程连接服务器显示:发生身份验证错误 要求的函数不受支持

问题不是出在服务器,而是出在远程桌面客户端,在微软打了补丁后,相当于限制了权限。修改要去登录服务器的那台客户端电脑就可以了。 客户端操作系统:win7专业版 双击“加密Oracle修正“ 然后重新打开远程登录客户端就可以登录了。…

Redis存储结构探究

一、概述 主流的key-value存储系统,都是在系统内部维护一个hash表,因为对hash表的操作时间复杂度为O(1)。如果数据增加以后,导致冲突严重,时间复杂度增加,则可以对hash表进行rehash,以来保证操作的常量时间…

VS2015报错C4996处理,error C4996: 'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_D

在vs2015中socket编程时,在编下如下代码时: [cpp] view plain copy sockaddr_in clientService; clientService.sin_family AF_INET; clientService.sin_addr.s_addr inet_addr("10.224.5.27"); clientService.sin_port htons(7008); …

阿里云服务器ping不通解决办法

解决办法: 1. 登录阿里云ECS控制台,单击【安全组】>【配置规则】。 2. 选择【公网入方向】>【快速创建规则】。 3. 把常用的几个勾上。 4. 注意入口出口都要设置。 设置完如下图: 这时候就可以访问了 如果想从自己电脑ping通服务…