Handler模块就是接受来自客户端的请求并产生输出的模块.
处理由 Handler 产生的输出的 Filter(滤波器)模块
Load-balancer 负责决定将请求发送给哪个后端服务器.Nginx 目前支持两种 Load-balancer 模块:round-robin (轮询,处理请求就像打扑克时发牌那样)和"IP hash" method(众多请求时,保证来自同一 IP 的请求被分发的同一个后端服务器)
配置文件中使用location指令可以配置content handler模块,当Nginx系统启动的时候,每个handler模块都有一次机会把自己关联到对应的location上。
,,,,,,,,
Nginx_handler模块发开(hello模块结构解析) - PaulWeiHan - 博客园
notes: 模块定义,模块上下文定义(ctx),模块配置结构(commands),Handler处理函数.
上面图里包括了我对这个入门模块的结构的理解,解释如下:
实现一个handler的步骤:
- 编写模块基本结构。包括模块的定义,模块上下文结构,模块的配置结构等。
- 如果有loc_conf,需要定义自己loc_conf结构,以及conf结构创建,赋值函数。
- 实现handler的挂载函数。根据模块的需求选择正确的挂载方式。
- 编写handler处理函数。模块的功能主要通过这个函数来完成。
这些结构域函数之间的包含关系见上面的图。下面说说我个人对这些模块和函数的理解。
Num 1:模块定义结构
1 ngx_module_t ngx_http_hello_module;
对于开发一个模块来说,我们都需要定义一个ngx_module_t类型的变量来说明这个模块本身的信息,从某种意义上来说,这是这个模块最重要的一个信息,它告诉了nginx这个模块的一些信息,上面定义的配置信息,还有模块上下文信息,都是通过这个结构来告诉nginx系统的,也就是加载模块的上层代码,都需要通过定义的这个结构,来获取这些信息。
这个模块中最主要要定义的就是CTX上下文结构与commands配置结构数组。
Num 2:上下文结构
1 ngx_http_module_t ngx_http_hello_module_ctx;
这是一个ngx_http_module_t类型的静态变量。这个变量实际上是提供一组回调函数指针,这些函数有在创建存储配置信息的对象的函数,也有在创建前和创建后会调用的函数。这些函数都将被nginx在合适的时间进行调用。
这个例子中,该结构最重要的是
- 调用了一个handler挂载函数ngx_http_hello_init
- 调用了一个loc_conf创建函数ngx_http_hello_create_loc_conf
Num 3:模块的配置结构
1 ngx_command_t ngx_http_hello_commands[];
在我的理解里,这个数组实现的功能是将我们在nginx.conf文件中定义的配置读取并存储进loc_conf结构中,conf结构已经在上下文结构中被创建。
Num 4:loc_conf与其创建赋值函数
本例子中,定义了一个结构体:
1 typedef struct
2 {
3 ngx_str_t hello_string;
4 ngx_int_t hello_counter;
5 }ngx_http_hello_loc_conf_t;
该结构体储存了本模块的配置变量。通过上下文结构中的调用的ngx_http_hello_create_loc_conf函数创建,通过配置结构中的ngx_http_hello_string以及ngx_http_hello_counter函数赋值。(具体实现代码不贴了,请读者自行查询hello模块源码)
Num 5:handler 挂载函数
1 ngx_int_t ngx_http_hello_init;
该函数在上下文结构中被调用,决定了handler函数具体在11个PHASE中的哪个PHASE被调用。关于PHASE定义以及函数具体实现参看网站以及hello模块源码。
Num 6:handler函数
1 static ngx_int_t
2 ngx_http_hello_handler(ngx_http_request_t *r)
3 {
4 }
关于这个函数,没什么好说的,他实现了对于整个request的处理,根据loc_conf的不同,处理方式也不同。
待续:上面只介绍了hello模块涉及的数据结构以及函数的关系以及其相关功能,理清了hello模块是如何工作的:具体体现在,在哪些结构中调用了哪些函数。
对于主要的两个数据结构ngx_conf_t ngx_http_request_t是如何传递和实现的并未解释。同时,并未联系上nginx的main函数进行整体的分析。
函数的挂载分为两种方式:
一种方式就是按处理阶段挂载;另外一种挂载方式就是按需挂载。tengine.taobao.org 中使用的挂载方式是按处理阶段挂载,而深入理解一书中的挂载方式是按需求挂载。
handler_init就是handler函数的挂载函数,该函数在上下文结构中的postconfiguration字段被调用,决定handler函数在哪里被挂载。
ngx_http_hello_module_ctx
static ngx_http_module_t ngx_http_hello_module_ctx = {
NULL,
ngx_http_hello_init,
NULL,
NULL,
NULL,
NULL,
ngx_http_hello_create_loc_conf,
NUL
ngx_http_hello_module_ctx
static ngx_http_module_t ngx_http_hello_module_ctx = {
NULL,
ngx_http_hello_init,
NULL,
NULL,
NULL,
NULL,
ngx_http_hello_create_loc_conf,
NUL
使用这种方式挂载的handler也被称为 content phase handlers。
当一个请求进来以后,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。
mytest这个例子在配置结构中,直接调用了content handler函数,名为ngx_http_mytest,该函数直接实现了真正的handler函数的挂载执行:
static ngx_command_t ngx_http_mytest_commands[] = {
{
ngx_string("mytest"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS,
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL
},
ngx_null_command
};
content handler函数 ngx_http_mytest的定义如下:
static char *
ngx_http_mytest(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_mytest_handler;
return NGX_CONF_OK;
}
定义十分简单,主要就是一个ngx_http_conf_get_module_loc_conf函数的调用,以及一个函数指针的赋值。函数指针的赋值实现了真正的handler函数的挂载。
那么,我们来看看被调用的ngx_http_conf_get_module_loc_con函数的定义:
#define ngx_http_conf_get_module_loc_conf(cf, module)
((ngx_http_conf_ctx_t *) cf->ctx)->loc_conf[module.ctx_index]
只是一个宏定义,主要作用是将之前存储起来的conf内容调出,赋值给clcf。
,,,,,,,,,,,,,,,,,,,,,
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去处理。



