nginx模块分为核心模块和功能性模块,功能性模块包括conf、event、http、mail、stream几大类.
除了conf类以外,其他几类功能性模块都包含众多的模块,并且每个类别中都有一个核心模块。
其作用是提供本类别功能通用的框架接口,逻辑处理等,并调用同类型的其他模块完成具体的功能.
例如NGX_HTTP_MODULE类别中的核心模块ngx_http_core_module,负责http块配置项中通用的配置解析、http请求处理的整体流程,并调用其他NGX_HTTP_MODULE模块完成具体的处理。
nginx的模块根据其功能基本上可以分为以下几种类型:
- event module:
负责请求连接的建立,分发等网络事件及定时器事件.
其中所有模块封装到ngx_events_module_t接口中供其他模块直接调用。 -
http/stream/mail模块
对应nginx用户功能的三个主体。
以Http模块为例,初始化,退出,对配置项的处理等工作也统一封装在ngx_http_module_t中
http请求的处理过程各模块可插拔,为固定的11个阶段,模块想介入,只需在ngx_http_module_t中定义回调函数,http协议内容多,对结果的处理也高度模块化,根据配置项将模块选择性插入到输出过滤链中。 - phase handler:
此类型的模块也被直接称为handler模块。主要负责处理客户端请求并产生待响应内容,比如ngx_http_static_module模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。Handler 模块(callback):接受来自客户端的请求并构建响应头和响应体。
- filters : 对handlers产生的响应内容做各种过滤处理(即增,删,改),比如 ngx_http_not_modify_filter_moudle,如果通过时间判断前后2次请求的响应内容没有发生任何改变,那么可以直接响应"304 Not Modified"状态标识,让客户端使用缓存即可,而原本发送的响应内容将被清除掉.
Filter 模块:过滤(filter)模块是过滤响应头和内容的模块,可以对回复的头和内容进行处理。它的处理时间在获取回复内容之后,向用户发送响应之前。 - upstream:
upstream模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。
upstream模块是一种特殊的handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。
使 Nginx 跨越单机的限制,完成网络数据的接收、处理和转发,纯异步的访问后端服务。
- load-balancer:
负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。
http,stream,mail依赖事件模块,事件模块依赖核心模块和配置模块.
上层模块依赖底层的系统调用
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
3. 模块的相关结构体- ngx_module_t接口
- 配置项
- 核心模块类型的上下文
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
nginx将各功能模块组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理。 每个模块实现特定的功能。例如,实现对请求解压缩的模块,实现SSI的模块,实现与上游服务器进行通讯的模块,实现与FastCGI服务进行通讯的模块。
Nginx模块被划分为几大类,比如核心模块NGX_CORE_MODULE,事件模块NGX_EVENT_MODULE,HTTP模块NGX_HTTP_MODULE。结构体ngx_module_s定义了Nginx模块,我们重点需要关注这几个(类)字段:
- 钩子函数,比如init_master/exit_master在master进程启动/退出时回调;init_process/exit_process在work进程启动/退出时回调;init_module在模块初始化时候调用;
- 指令数组commands,其定义了该模块可以解析哪些配置;这个很好理解,功能由各个模块实现,与功能对应的配置也应该由各个模块解析处理;
- 模块上下文ctx,查看源码的话你会发现其类型为void*,那是因为不同类型的模块ctx定义不一样。ctx结构通常都定义了配置创建以及初始化回调;另外,事件模块还会定义事件处理(添加事件,修改事件,删除事件,事件循环)回调;HTTP模块还定义了HTTP处理流程的回调,这些回调会在HTTP流程 11个执行阶段调用。
总结一句话,Nginx的框架已定义,主流程已知,各个模块只需要实现并注册流程中的回调即可,Nginx会在合适的时机执行这些回调。
最后再来一副脑图简单列一下重点知识,还需要读者去进一步研究探索:
Nginx取而代之多功能腰带的一个模块链(module chain)。当nginx有需要gzip或分块编码的响应这类需求时候,它拿出一个模块来做这项工作;当nginx模块有需要对基于网络上某一个IP地址的资源访问或者是HTTP认证凭证时候,它拿出一个模块来做转移(deflecting 不知道翻译成什么好)这项工作;当Nginx与memcache或者FastCGI服务器进行通信,一个模板就是对话机。Nginx的多功能腰带就是模板链,当需要什么功能时候,模板链就给出这个模板来提供这个功能。
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
1. 模块介绍高度模块化的设计是nginx的架构基础。在nginx中,除了少量的核心代码,其他一切皆为模块。
这种模块化设计同时具有以下几个特点:
- 高度抽象的模块接口
所有的模块都遵循着同样的 ngx_module_t 接口设计规范,这减少了整个系统中的变数。
- 配置模块的设计
配置模块的类型叫做NGX_CONF_MODULE,它仅有的模块叫做ngx_conf_module。
- 多层次、多类别的模块设计
所有的模块间是分层次的,分类别的。不同的模块虽然都具备相同的ngx_module_t接口,单在请求处理流程中的层次并不相同。nginx将各功能组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块进行处理。
模块的基本结构模块配置结构:
Nginx的配置信息分成了几个作用域(scope,有时也称作上下文),这就是main, server, 以及location-->每个模块提供的配置指令--->三个数据结构存储配置指令
基本上每个模块都会提供一些配置指令,以便于用户可以通过配置来控制该模块的行为。那么这些配置信息怎么存储呢?那就需要定义该模块的配置结构来进行存储。
模块配置指令一个模块的配置指令是定义在一个静态数组中的。同样地,我们来看一下从hello module中截取的模块配置指令的定义。
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
};
例如,我们是定义了两个配置指令
- name:配置指令名称,如“proxy_pass”;
- type:指令类型,可以将指令类型分为两类,
1)说明指令可以出现的位置,比如配置文件(只能在配置文件最外层,不能出现在任何指令块内部),http指令块,或者events指令块,或者server指令块,或者location指令块; - set:这是一个函数指针,当nginx在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理
cf: 该参数里面保存从配置文件读取到的原始字符串以及相关的一些信息。
cmd: 这个配置指令对应的ngx_command_t结构。
conf: 就是定义的存储这个配置值的结构体,比如在上面展示的那个ngx_http_hello_loc_conf_t。当解析这个hello_string变量的时候,传入的conf就指向一个ngx_http_hello_loc_conf_t类型的变量。用户在处理的时候可以使用类型转换,转换成自己知道的类型,再进行字段的赋值。
- conf和offset: 因为http模块对所有http模块所要保存的配置信息,划分了main, server和location三个地方进行存储,每个地方都有一个内存池用来分配存储这些信息的内存
- post 可以指向任何一个在读取配置过程中需要的数据,以便于进行配置读取的处理
这是一个ngx_http_module_t类型的静态变量。这个变量实际上是提供一组回调函数指针.
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;
这个变量提供8个回调函数指针,这个模块真实作用是处理配置文件nginx.conf.:
1 preconfiguration:2 postconfiguration:3 create_main_conf:4 init_main_conf:
5 create_srv_conf:6 merge_srv_conf:7 create_loc_conf:8 merge_loc_conf:
执行顺序是3 5 7 1 4 6 8 2;在读取配置的不同阶段中如果对指令有要做相应的操作 实现相应的回调函数添加逻辑即可。
在进程启动解析nginx.conf中的命令时候,遇到http 会进入到 3 5 7 1,遇到 server 时会进入到 5 7,遇到location 会进入到7,最后结尾大括号时会进行 4 6 8 2操作,前面执行了几次5 后面就会执行几次6, 7 和8 也是这种情况。
| 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或错误字符串。 |
开发一个模块需要定义一个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;
....
};
再看一下hello模块的模块定义。
ngx_module_t ngx_http_hello_module = {
NGX_MODULE_V1,
&ngx_http_hello_module_ctx,
ngx_http_hello_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
模块可以提供一些回调函数给nginx,当nginx在创建进程线程或者结束进程线程时进行调用。
但大多数模块在这些时刻并不需要做什么,所以都简单赋值为NULL。
基本上作为第三方开发者最可能开发的就是三种类型的模块,即handler,filter和load-balancer。Handler模块就是接收来自客户端的请求并产生输出的模块。
配置文件中使用location指令可以配置content handler模块,当Nginx系统启动的时候,每个handler模块都有一次机会把自己关联到对应的location上。
handler模块处理的结果通常有三种情况: 处理成功,处理失败(处理的时候发生了错误)或者是拒绝去处理。在拒绝处理的情况下,这个location的处理就会由默认的handler模块来进行处理。例如,当请求一个静态文件的时候,如果关联到这个location上的一个handler模块拒绝处理,就会由默认的ngx_http_static_module模块进行处理,该模块是一个典型的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模块真正的处理函数通过两种方式挂载到处理过程中,
一种方式就是按处理阶段挂载;另外一种挂载方式就是按需挂载。
为了更精细地控制对于客户端请求的处理过程,nginx把这个处理过程划分成了11个阶段。他们从前到后,依次列举如下:
| NGX_HTTP_POST_READ_PHASE:读取请求内容阶段 | |
|---|---|
| NGX_HTTP_SERVER_REWRITE_PHASE:Server请求地址重写阶段 | |
| NGX_HTTP_FIND_CONFIG_PHASE:配置查找阶段: | |
| NGX_HTTP_REWRITE_PHASE:Location请求地址重写阶段 | |
| NGX_HTTP_POST_REWRITE_PHASE:请求地址重写提交阶段 | |
| NGX_HTTP_PREACCESS_PHASE:访问权限检查准备阶段 | |
| NGX_HTTP_ACCESS_PHASE:访问权限检查阶段 | |
| NGX_HTTP_POST_ACCESS_PHASE:访问权限检查提交阶段 | |
| NGX_HTTP_TRY_FILES_PHASE:配置项try_files处理阶段 | |
| NGX_HTTP_CONTENT_PHASE:内容产生阶段 | |
| NGX_HTTP_LOG_PHASE:日志模块处理阶段 |
自定义的模块挂载在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
所以其实真正是有7个phase你可以去挂载handler。
挂载的代码如下(摘自hello module):
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);
*h = ngx_http_hello_handler;
return NGX_OK;
}
使用这种方式挂载的handler也被称为 content phase handlers。
按需挂载以这种方式挂载的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。
下面来看一下使用这种挂载方式的具体例子(摘自Emiller’s Guide To Nginx Module Development)。
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的编写步骤
回顾一下实现一个handler的步骤:
- 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等。
- 实现handler的挂载函数。根据模块的需求选择正确的挂载方式。
- 编写handler处理函数。模块的功能主要通过这个函数来完成。
看起来不是那么难,对吧?还是那句老话,世上无难事,只怕有心人! 现在我们来完整的分析前面提到的hello handler module示例的功能和代码。
handler模块实例参见: handler模块的编译和使用模块的功能开发完了之后,模块的使用还需要编译才能够执行,下面我们来看下模块的编译和使用
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这个变量里,依次写进去就可以。
更多handler模块示例分析 http access module该模块的代码位于src/http/modules/ngx_http_access_module.c中。该模块的作用是提供对于特定host的客户端的访问控制。可以限定特定host的客户端对于服务端全部,或者某个server,或者是某个location的访问。 该模块的实现非常简单,总共也就只有几个函数。
static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r); ....
对于与配置相关的几个函数都不需要做解释了,需要提一下的是函数ngx_http_access_init,该函数在实现上把本模块挂载到了NGX_HTTP_ACCESS_PHASE阶段的handler上,从而使自己的被调用时机发生在了NGX_HTTP_CONTENT_PHASE等阶段前。因为进行客户端地址的限制检查,根本不需要等到这么后面。
http static module本模块的作用就是读取磁盘上的静态文件,并把文件内容作为产生的输出。在Web技术发展的早期,只有静态页面,没有服务端脚本来动态生成HTML的时候。恐怕开发个Web服务器的时候,第一个要开发就是这样一个content handler。
我们首先来看一下该模块的模块上下文的定义。
ngx_http_module_t ngx_http_static_module_ctx = {
NULL,
ngx_http_static_init,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
是非常的简洁吧,连任何与配置相关的函数都没有。对了,因为该模块没有提供任何配置指令。
也确实没什么好配置的。唯一需要调用的函数是一个ngx_http_static_init函数。
static ngx_int_t
ngx_http_static_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);
*h = ngx_http_static_handler;
return NGX_OK;
}
仅仅是挂载这个handler到NGX_HTTP_CONTENT_PHASE处理阶段。
notes:调用时机
下面我们就看一下这个模块最核心的处理逻辑所在的ngx_http_static_handler函数。该函数大概占了这个模块代码量的百分之八九十。
static ngx_int_t
ngx_http_static_handler(ngx_http_request_t *r)
{
u_char *last, *location;
size_t root, len;
ngx_str_t path;
ngx_int_t rc;
ngx_uint_t level;
ngx_log_t *log;
ngx_buf_t *b;
ngx_chain_t out;
ngx_open_file_info_t of;
ngx_http_core_loc_conf_t *clcf;
// 仅支持 get/head/post 方法进行静态文件处理
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
return NGX_HTTP_NOT_ALLOWED;
}
//下一个阶段
if (r->uri.data[r->uri.len - 1] == '/') {
return NGX_DECLINED;
}
log = r->connection->log;
last = ngx_http_map_uri_to_path(r, &path, &root, 0);
path.len = last - path.data;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
"http filename: "%s"", path.data);
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_memzero(&of, sizeof(ngx_open_file_info_t));
of.read_ahead = clcf->read_ahead;
of.directio = clcf->directio;
of.valid = clcf->open_file_cache_valid;
of.min_uses = clcf->open_file_cache_min_uses;
of.errors = clcf->open_file_cache_errors;
of.events = clcf->open_file_cache_events;
if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
!= NGX_OK)
{
switch (of.err) {
case 0:
return NGX_HTTP_INTERNAL_SERVER_ERROR;
case NGX_ENOENT:
case NGX_ENOTDIR:
case NGX_ENAMETOOLONG:
level = NGX_LOG_ERR;
rc = NGX_HTTP_NOT_FOUND;
break;
case NGX_EACCES:
#if (NGX_HAVE_OPENAT)
case NGX_EMlink:
case NGX_ELOOP:
#endif
level = NGX_LOG_ERR;
rc = NGX_HTTP_FORBIDDEN;
break;
default:
level = NGX_LOG_CRIT;
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
break;
}
if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
ngx_log_error(level, log, of.err,
"%s "%s" failed", of.failed, path.data);
}
return rc;
}
r->root_tested = !r->error_page;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
if (of.is_dir) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
ngx_http_clear_location(r);
r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t));
len = r->uri.len + 1;
if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
location = path.data + clcf->root.len;
*last = '/';
} else {
if (r->args.len) {
len += r->args.len + 1;
}
location = ngx_pnalloc(r->pool, len);
last = ngx_copy(location, r->uri.data, r->uri.len);
*last = '/';
if (r->args.len) {
*++last = '?';
ngx_memcpy(++last, r->args.data, r->args.len);
}
}
r->headers_out.location->value.len = len;
r->headers_out.location->value.data = location;
return NGX_HTTP_MOVED_PERMANENTLY;
}
rc = ngx_http_discard_request_body(r);
log->action = "sending response to client";
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = of.size;
r->headers_out.last_modified_time = of.mtime;
if (r != r->main && of.size == 0) {
return ngx_http_send_header(r);
}
r->allow_ranges = 1;
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
rc = ngx_http_send_header(r);
b->file_pos = 0;
b->file_last = of.size;
b->in_file = b->file_last ? 1: 0;
b->last_buf = (r == r->main) ? 1: 0;
b->last_in_chain = 1;
b->file->fd = of.fd;
b->file->name = path;
b->file->log = log;
b->file->directio = of.is_directio;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
1是检查客户端的http请求类型(r->method),如果请求类型为NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST,则继续进行处理,否则一律返回NGX_HTTP_NOT_ALLOWED从而拒绝客户端的发起的请求。
2是检查请求的url的结尾字符是不是斜杠‘/’,如果是说明请求的不是一个文件,给后续的handler去处理,比如后续的ngx_http_autoindex_handler(如果是请求的是一个目录下面,可以列出这个目录的文件),或者是ngx_http_index_handler(如果请求的路径下面有个默认的index文件,直接返回index文件的内容)。
3 接下来调用了一个ngx_http_map_uri_to_path函数,该函数的作用是把请求的http协议的路径转化成一个文件系统的路径。
然后根据转化出来的具体路径,去打开文件,打开文件的时候做了2种检查,一种是,如果请求的文件是个symbol link,根据配置,是否允许符号链接,不允许返回错误。还有一个检查是,如果请求的是一个名称,是一个目录的名字,也返回错误。
如果都没有错误,就读取文件,返回内容。其实说返回内容可能不是特别准确,比较准确的说法是,把产生的内容传递给后续的filter去处理。
模块的功能开发完了之后,模块的使用还需要编译才能够执行,下面我们来看下模块的编译和使用
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这个变量里,依次写进去就可以。
更多handler模块示例分析 http access module该模块的代码位于src/http/modules/ngx_http_access_module.c中。该模块的作用是提供对于特定host的客户端的访问控制。可以限定特定host的客户端对于服务端全部,或者某个server,或者是某个location的访问。 该模块的实现非常简单,总共也就只有几个函数。
static ngx_int_t ngx_http_access_handler(ngx_http_request_t *r); ....
对于与配置相关的几个函数都不需要做解释了,需要提一下的是函数ngx_http_access_init,该函数在实现上把本模块挂载到了NGX_HTTP_ACCESS_PHASE阶段的handler上,从而使自己的被调用时机发生在了NGX_HTTP_CONTENT_PHASE等阶段前。因为进行客户端地址的限制检查,根本不需要等到这么后面。
http static module本模块的作用就是读取磁盘上的静态文件,并把文件内容作为产生的输出。在Web技术发展的早期,只有静态页面,没有服务端脚本来动态生成HTML的时候。恐怕开发个Web服务器的时候,第一个要开发就是这样一个content handler。
我们首先来看一下该模块的模块上下文的定义。
ngx_http_module_t ngx_http_static_module_ctx = {
NULL,
ngx_http_static_init,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
是非常的简洁吧,连任何与配置相关的函数都没有。对了,因为该模块没有提供任何配置指令。
也确实没什么好配置的。唯一需要调用的函数是一个ngx_http_static_init函数。
static ngx_int_t
ngx_http_static_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);
*h = ngx_http_static_handler;
return NGX_OK;
}
仅仅是挂载这个handler到NGX_HTTP_CONTENT_PHASE处理阶段。
notes:调用时机
下面我们就看一下这个模块最核心的处理逻辑所在的ngx_http_static_handler函数。该函数大概占了这个模块代码量的百分之八九十。
static ngx_int_t
ngx_http_static_handler(ngx_http_request_t *r)
{
u_char *last, *location;
size_t root, len;
ngx_str_t path;
ngx_int_t rc;
ngx_uint_t level;
ngx_log_t *log;
ngx_buf_t *b;
ngx_chain_t out;
ngx_open_file_info_t of;
ngx_http_core_loc_conf_t *clcf;
// 仅支持 get/head/post 方法进行静态文件处理
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
return NGX_HTTP_NOT_ALLOWED;
}
//下一个阶段
if (r->uri.data[r->uri.len - 1] == '/') {
return NGX_DECLINED;
}
log = r->connection->log;
last = ngx_http_map_uri_to_path(r, &path, &root, 0);
path.len = last - path.data;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
"http filename: "%s"", path.data);
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_memzero(&of, sizeof(ngx_open_file_info_t));
of.read_ahead = clcf->read_ahead;
of.directio = clcf->directio;
of.valid = clcf->open_file_cache_valid;
of.min_uses = clcf->open_file_cache_min_uses;
of.errors = clcf->open_file_cache_errors;
of.events = clcf->open_file_cache_events;
if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
!= NGX_OK)
{
switch (of.err) {
case 0:
return NGX_HTTP_INTERNAL_SERVER_ERROR;
case NGX_ENOENT:
case NGX_ENOTDIR:
case NGX_ENAMETOOLONG:
level = NGX_LOG_ERR;
rc = NGX_HTTP_NOT_FOUND;
break;
case NGX_EACCES:
#if (NGX_HAVE_OPENAT)
case NGX_EMlink:
case NGX_ELOOP:
#endif
level = NGX_LOG_ERR;
rc = NGX_HTTP_FORBIDDEN;
break;
default:
level = NGX_LOG_CRIT;
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
break;
}
if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
ngx_log_error(level, log, of.err,
"%s "%s" failed", of.failed, path.data);
}
return rc;
}
r->root_tested = !r->error_page;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);
if (of.is_dir) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");
ngx_http_clear_location(r);
r->headers_out.location = ngx_palloc(r->pool, sizeof(ngx_table_elt_t));
len = r->uri.len + 1;
if (!clcf->alias && clcf->root_lengths == NULL && r->args.len == 0) {
location = path.data + clcf->root.len;
*last = '/';
} else {
if (r->args.len) {
len += r->args.len + 1;
}
location = ngx_pnalloc(r->pool, len);
last = ngx_copy(location, r->uri.data, r->uri.len);
*last = '/';
if (r->args.len) {
*++last = '?';
ngx_memcpy(++last, r->args.data, r->args.len);
}
}
r->headers_out.location->value.len = len;
r->headers_out.location->value.data = location;
return NGX_HTTP_MOVED_PERMANENTLY;
}
rc = ngx_http_discard_request_body(r);
log->action = "sending response to client";
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = of.size;
r->headers_out.last_modified_time = of.mtime;
if (r != r->main && of.size == 0) {
return ngx_http_send_header(r);
}
r->allow_ranges = 1;
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
rc = ngx_http_send_header(r);
b->file_pos = 0;
b->file_last = of.size;
b->in_file = b->file_last ? 1: 0;
b->last_buf = (r == r->main) ? 1: 0;
b->last_in_chain = 1;
b->file->fd = of.fd;
b->file->name = path;
b->file->log = log;
b->file->directio = of.is_directio;
out.buf = b;
out.next = NULL;
return ngx_http_output_filter(r, &out);
}
1是检查客户端的http请求类型(r->method),如果请求类型为NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST,则继续进行处理,否则一律返回NGX_HTTP_NOT_ALLOWED从而拒绝客户端的发起的请求。
2是检查请求的url的结尾字符是不是斜杠‘/’,如果是说明请求的不是一个文件,给后续的handler去处理,比如后续的ngx_http_autoindex_handler(如果是请求的是一个目录下面,可以列出这个目录的文件),或者是ngx_http_index_handler(如果请求的路径下面有个默认的index文件,直接返回index文件的内容)。
3 接下来调用了一个ngx_http_map_uri_to_path函数,该函数的作用是把请求的http协议的路径转化成一个文件系统的路径。
然后根据转化出来的具体路径,去打开文件,打开文件的时候做了2种检查,一种是,如果请求的文件是个symbol link,根据配置,是否允许符号链接,不允许返回错误。还有一个检查是,如果请求的是一个名称,是一个目录的名字,也返回错误。
如果都没有错误,就读取文件,返回内容。其实说返回内容可能不是特别准确,比较准确的说法是,把产生的内容传递给后续的filter去处理。



