nginx使用一个多进程模型来对外提供服务,其中一个master进程,多个worker进程。master进程负责管理nginx本身和其他worker进程。
所有实际上的业务处理逻辑都在worker进程的ngx_worker_process_cycle()函数中处理。
这个函数在src/os/unix/目录下的ngx_process_cycle.c文件中定义。
在ngx_worker_process_cycle()函数中对一次请求的处理流程如下:
以http的一次request为例,一个HTTP Request的处理过程涉及到以下几个阶段:
一个phase handler通常执行的任务如下:
当nginx读取到一个HTTP Request的header的时候,nginx首先查找与这个请求关联的虚拟主机的配置。如果找到了这个虚拟主机的配置,那么通常情况下,这个HTTP Request将会经过以下几个阶段的处理:
在内容产生阶段,为了给一个request产生正确的响应,nginx必须把这个request交给一个合适的content handler去处理。如果这个request对应的location在配置文件中被明确指定了一个content handler,那么nginx就可以通过对location的匹配,直接找到这个对应的handler,并把这个request交给这个content handler去处理。这样的配置指令包括像,perl,flv,proxy_pass,mp4等。
如果一个request对应的location并没有直接有配置的content handler,那么nginx会依次尝试如下步骤:
内容产生阶段完成以后,生成的输出会被传递到filter模块去进行处理。filter模块也是与location相关的。所有的fiter模块都被组织成一条链。输出会依次穿越所有的filter,直到有一个filter模块的返回值表明已经处理完成。
常见的filter模块有:
Handler模块就是接受来自客户端的请求并产生输出的模块。
配置文件中使用location指令可以配置content handler模块,当Nginx系统启动的时候,每个handler模块都有一次机会把自己关联到对应的location上。
如果有多个handler模块都关联了同一个location,那么实际上只有一个handler模块真正会起作用。
所以在开发阶段应该避免多个handler模块关联同一个location的情况发生。
handler模块处理的结果通常有三种情况:
Nginx中每个模块都会提供一些指令,以便于用户通过配置去控制该模块的行为。
Nginx的配置信息分成了几个作用域(scope,有时也称作上下文)。作用域有main, server, 以及location。
位置: src/core 目录下 ngx_conf_file.h
原型:
typedef struct ngx_command_s ngx_command_t;
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_HTTP_MODULE类型模块所用
(我们编写的基本上都是NGX_HTTP_MOUDLE,只有一些nginx核心模块是非NGX_HTTP_MODULE),
该字段指定当前配置项存储的内存位置。实际上是使用哪个内存池的问题。
因为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。*/
ngx_uint_t offset; /*指定该配置项值的精确存放位置,一般指定为某一个结构体变量的字段偏移。
因为对于配置信息的存储,一般我们都是定义个结构体来存储的。
那么比如我们定义了一个结构体A,该项配置的值需要存储到该结构体的b字段。
那么在这里就可以填写为offsetof(A, b)。
对于有些配置项,它的值不需要保存或者是需要保存到更为复杂的结构中时,这里可以设置为0。*/
void *post; /* 该字段存储一个指针。可以指向任何一个在读取配置过程中需要的数据,
以便于进行配置读取的处理。大多数时候,都不需要,所以简单地设为0即可。*/
};
对type字段的补充说明:
nginx提供了很多预定义的属性值(一些宏定义),通过逻辑或运算符可组合在一起,形成对这个配置指令的详细的说明。
属性值可以有:
nginx还提供了一些定义,使用起来更简洁:
下面介绍一组说明配置指令作用域的相关属性:
对set字段的补充说明:
这是一个函数指针,当nginx在解析配置的时候,如果遇到这个配置指令,将会把读取到的值传递给这个函数进行分解处理。因为具体每个配置指令的值如何处理,只有定义这个配置指令的人是最清楚的。
// 处理成功时,返回NGX_OK,否则返回NGX_CONF_ERROR或者是一个自定义的错误信息的字符串。
/* cf: 该参数里面保存从配置文件读取到的原始字符串以及相关的一些信息。特别注意的是这个参数的args字段是一个ngx_str_t类型的数组,
该数组的首个元素是这个配置指令本身,第二个元素是指令的第一个参数,第三个元素是第二个参数,依次类推。*/
/* cmd: 这个配置指令对应的ngx_command_t结构。*/
/* conf: 就是定义的存储这个配置值的结构体,用户在处理的时候可以使用类型转换,转换成自己知道的类型,再进行字段的赋值。*/
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
为了更加方便的实现对配置指令参数的读取,nginx已经默认提供了对一些标准类型的参数进行读取的函数,可以直接赋值给set字段使用。
下面列出这些已经实现的set类型函数:
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_null_command // 每个模块配置后必不可少的
// 它的原型是 #define ngx_null_command { ngx_null_string, 0, NULL, 0, 0, NULL }
};
我们定义的配置指令,一个叫hello_string可以接收一个或零个参数,可以出现在http server块里面的location配置指令里。该配置的处理函数是ngx_http_hello_string。该配置作用域在NGX_HTTP_LOC_CONF_OFFSET(location中)。该配置信息保存在自定义结构体ngx_http_hello_loc_conf_t 的hello_string字段。
1. 首先我们在src/core/nginx.c文件中找到ngx_core_commands数组。
4. 重新编译install:可参考 https://blog.csdn.net/Stars____/article/details/129137175?spm=1001.2014.3001.5501
这是一个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); /* 调用该函数创建本模块位于http block的配置信息存储结构。
该函数成功的时候,返回创建的配置对象。失败的话,返回NULL。*/
char *(*init_main_conf)(ngx_conf_t *cf, void *conf); /* 调用该函数初始化本模块位于http block的配置信息存储结构。
该函数成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。*/
void *(*create_srv_conf)(ngx_conf_t *cf); /* 调用该函数创建本模块位于http server block的配置信息存储结构,
每个server block会创建一个。该函数成功的时候,返回创建的配置对象。失败的话,返回NULL。*/
char *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf); /* 因为有些配置指令既可以出现在http block,
也可以出现在http server block中。
那么遇到这种情况,每个server都会有自己存储结构来存储该server的配置,
但是在这种情况下http block中的配置与server block中的配置信息发生冲突的时候,就需要调用此函数进行合并,
该函数并非必须提供,当预计到绝对不会发生需要合并的情况的时候,就无需提供。当然为了安全起见还是建议提供。
该函数执行成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。*/
void *(*create_loc_conf)(ngx_conf_t *cf); /* 调用该函数创建本模块位于location block的配置信息存储结构。
每个在配置中指明的location创建一个。该函数执行成功,返回创建的配置对象。失败的话,返回NULL。*/
char *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf); /* 与merge_srv_conf类似,这个也是进行配置值合并的地方。
该函数成功的时候,返回NGX_CONF_OK。失败的话,返回NGX_CONF_ERROR或错误字符串。*/
} ngx_http_module_t;
#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
对于开发一个模块来说,我们都需要定义一个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;
char *name;
ngx_uint_t spare0;
ngx_uint_t spare1;
ngx_uint_t version;
const char *signature;
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;
};
模块可以提供一些回调函数给nginx,当nginx在创建进程线程或者结束进程线程时进行调用。但大多数模块在这些时刻并不需要做什么,所以都简单赋值为NULL。
除了上面模块的基本结构以外,handler模块必须提供一个真正的处理函数,这个函数负责对来自客户端请求的真正处理。这个函数的处理,既可以选择自己直接生成内容,也可以选择拒绝处理,由后续的handler去进行处理,或者是选择丢给后续的filter进行处理。
这个处理函数的原型如下:
// r是http的请求,里面包含请求所有的信息
// 该函数处理成功返回NGX_OK,处理发生错误返回NGX_ERROR,拒绝处理(留给后续的handler进行处理)返回NGX_DECLINE。
// 返回NGX_OK也就代表给客户端的响应已经生成好了,否则返回NGX_ERROR就发生错误了。
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
handler模块真正的处理函数通过两种方式挂载到处理过程中:
按处理阶段挂载:为了更精细地控制对于客户端请求的处理过程,nginx把这个处理过程划分成了11个阶段。
一般情况下,我们自定义的模块,大多数是挂载在NGX_HTTP_CONTENT_PHASE阶段的。挂载的动作一般是在模块上下文调用的postconfiguration函数中。
按需挂载:
#include
#include
#include
// 该结构体存放两条模块配置指令
typedef struct
{
ngx_str_t hello_string;
ngx_int_t hello_counter;
}ngx_http_hello_loc_conf_t;
// 一些自定义函数的声明
static ngx_int_t ngx_http_hello_init(ngx_conf_t *cf);
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
// 定义一个ngx_command_t数组来存放配置指令
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 // 记得一定要有的结尾元素
};
static u_char ngx_hello_default_string[] = "Hello, world! (Default String)";
static int ngx_hello_visited_times = 0;
// 定义模块上下文结构
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 location configuration */
NULL /* merge location configuration */
};
// 定义模块,在该模块中设置自定义的模块上下文和模块指令
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
};
// 定义自定义模块的真实处理函数
static ngx_int_t ngx_http_hello_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_hello_loc_conf_t* my_conf; // 自定义的结构体,用来接收自定义配置信息
u_char ngx_hello_string[1024] = {0};
ngx_uint_t content_length = 0;
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "ngx_http_hello_handler is called!");
// 在请求报文中凭借自定义模块ngx_http_hello_module去解析配置信息
my_conf = ngx_http_get_module_loc_conf(r, ngx_http_hello_module);
if (my_conf->hello_string.len == 0 )
{
// 若没有输入字符串使用默认字符信息
ngx_str_set(&(my_conf->hello_string), ngx_hello_default_string);
}
if (my_conf->hello_counter == NGX_CONF_UNSET || my_conf->hello_counter == 0)
{
ngx_sprintf(ngx_hello_string, "%s", my_conf->hello_string.data);
}
else
{
ngx_sprintf(ngx_hello_string, "%s Visited Times:%d", my_conf->hello_string.data,
++ngx_hello_visited_times);
}
ngx_log_error(NGX_LOG_EMERG, r->connection->log, 0, "hello_string:%s", ngx_hello_string);
content_length = ngx_strlen(ngx_hello_string);
// 我们只响应“GET”和“HEAD”请求
if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD)))
{
return NGX_HTTP_NOT_ALLOWED;
}
// 丢弃请求正文,因为这里不需要它
rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK) {
return rc;
}
// 设置 “内容类型” 标头
ngx_str_set(&r->headers_out.content_type, "text/html");
// 如果请求类型为http “HEAD”,则仅发送标头
if (r->method == NGX_HTTP_HEAD) {
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
return ngx_http_send_header(r);
}
// 为响应主体分配缓冲区
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if (b == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 将此缓冲区连接到缓冲链
out.buf = b;
out.next = NULL;
// 调整缓冲区的指针
b->pos = ngx_hello_string;
b->last = ngx_hello_string + content_length;
b->memory = 1; // 这个缓冲区在内存中
b->last_buf = 1; // 这是缓冲链中的最后一个缓冲区
// 设置状态行
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = content_length;
// 发送响应的标题
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
return rc;
}
// 发送响应的缓冲链
return ngx_http_output_filter(r, &out);
}
// 创建本模块位于location block的配置信息存储结构
static void *ngx_http_hello_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_hello_loc_conf_t* local_conf = NULL;
local_conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_hello_loc_conf_t));
if (local_conf == NULL)
{
return NULL;
}
ngx_str_null(&local_conf->hello_string);
local_conf->hello_counter = NGX_CONF_UNSET; // 默认未初始化的值
return local_conf;
}
// hello_string指令回调函数
static char *ngx_http_hello_string(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_hello_loc_conf_t* local_conf;
ngx_str_t* value;
local_conf = conf;
value = cf->args->elts;
ngx_str_set(&(local_conf->hello_string), value[1].data);
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_string:%s", local_conf->hello_string.data);
return NGX_CONF_OK;
}
// hello_counter指令回调函数
static char *ngx_http_hello_counter(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_hello_loc_conf_t* local_conf;
ngx_str_t* value;
unsigned char* val = NULL;
local_conf = conf;
value = cf->args->elts;
val = value[1].data;
local_conf->hello_counter = strncmp((const char*)val, "on", sizeof("on")) == 0 ? 1 : 0;
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "hello_counter:%d", local_conf->hello_counter);
return NGX_CONF_OK;
}
// 在创建和读取该模块的配置信息之后被调用
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;
}
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_handler.c"
./configure --prefix=/usr/local/nginx --add-module=/home/vv/nginx-1.22.1/src/http/handlerTest
make
make install
./nginx
1. 自定义模块的编码步骤:
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module
make
make install
2. 模块中涉及到的回调函数的调用顺序:以上述案例为例
ngx_http_module_t 结构中回调的调用顺序主要参考各个回调函数的调用时机(上文中有列出)。