博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【Nginx】开发一个简单的HTTP模块
阅读量:6230 次
发布时间:2019-06-21

本文共 7446 字,大约阅读时间需要 24 分钟。

首先来分析一下HTTP模块是怎样介入Nginx的。

当master进程fork出若干个workr子进程后,每一个worker子进程都会在自己的for死循环中不断调用事件模块:

for ( ;; ) {        ....        ngx_process_events_and_timers(cycle);   /* 调用事件模块 */        ....    }

事件模块检測是否有TCP连接请求,当收到一个SYN包后,由事件模块建立一条TCP连接。连接建立成功后,交由HTTP框架处理,HTTP框架负责接收HTTP头部,并依据头部信息将HTTP请求分发到不同的HTTP模块。最常见的分发策略就是依据HTTP头部的URI和配置文件nginx.conf里的location配置项的匹配度来决定怎样分发。如果配置文件里有例如以下配置块:
location /name {    test;}
并如果server域名为www.nestle.com。那么,当client发来的URL请求为“www.nestle.com/name”时,HTTP框架就会依据配置文件调用test模块,将这个HTTP请求交给test模块处理。
HTTP模块在处理完请求后,会自己主动依次调用HTTP过滤模块对准备返回的响应信息做预处理,比方是否压缩。下面是HTTP模块调用的大致流程图:
以下開始解说怎样定义一个HTTP模块。
凡是模块,都须要一个模块结构体,这个结构体是ngx_module_t,它的定义例如以下:
struct ngx_module_s {    ngx_uint_t            ctx_index;    /* 当前模块在同类模块中的序号 */    ngx_uint_t            index;        /* 当前模块在ngx_modules数组中的序号 */     ngx_uint_t            spare0;       /* 保留 */    ngx_uint_t            spare1;       /* 保留 */    ngx_uint_t            spare2;       /* 保留 */    ngx_uint_t            spare3;       /* 保留 */     ngx_uint_t            version;      /* 模块版本号,眼下为1 */     void                 *ctx;          /* 指向特定类型模块的公共接口 */    ngx_command_t        *commands;     /* 用于处理配置文件nginx.conf中的配置项 */    ngx_uint_t            type;         /* 当前模块类型 */     /* 下面7个函数指针表示7个运行点,这些运行点将在Nginx启动和退出过程中被调用     * 假设不须要则设置为NULL     */    ngx_int_t           (*init_master)(ngx_log_t *log);     /* 从未被调用,设为NULL */     ngx_int_t           (*init_module)(ngx_cycle_t *cycle); /* 启动worker子进程前调用 */     ngx_int_t           (*init_process)(ngx_cycle_t *cycle);/* 启动worker子进程后调用 */    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle); /* 从未被调用,设为NULL */    void                (*exit_thread)(ngx_cycle_t *cycle); /* 从未被调用,设为NULL */    void                (*exit_process)(ngx_cycle_t *cycle);/* worker子进程推出前调用 */     void                (*exit_master)(ngx_cycle_t *cycle); /* master进程退出前调用 */     /* 下面全为保留字段 */    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;};
HTTP模块的type字段必须设置为NGX_HTTP_MODULE,以表明此模块为HTTP模块。
HTTP模块的ctx指针必须指向ngx_http_module_t结构体,它表示HTTP模块所定义的通用接口,结构体定义例如以下:
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{}的配置项的结构体    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);  // 初始化main级别配置项     void       *(*create_srv_conf)(ngx_conf_t *cf);     // 创建存储直属于srv{}的配置项的结构体    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);  // 合并main级别和srv级别的同名配置项     void       *(*create_loc_conf)(ngx_conf_t *cf);     // 创建存储直属于loc{}的配置项的结构体    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);  // 合并srv级别和loc级别的同名配置项} ngx_http_module_t;
HTTP框架会在启动过程中调用上述结构体中的各个阶段函数,设为NULL则不会调用它们。
HTTP模块的commands数组由若干ngx_command_t链接而成。当Nginx在解析配置文件的一个配置项时,会遍历全部模块的commands数组,以找到对该配置项感兴趣的ngx_command_t结构体,从而找到对应的模块。ngx_command_t结构体定义例如以下:
struct ngx_command_s {    ngx_str_t             name;     // 配置项名称    ngx_uint_t            type;     // 配置项类型,包含该配置项能够出现的位置和能够携带參数的个数     // 出现name配置项后,调用此方法解析配置项參数    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);    ngx_uint_t            conf;     // 配置文件里的偏移量,确定将该配置项放入哪个存储结构体中    ngx_uint_t            offset;   // 将该配置项放在存储结构体的哪个字段    void                 *post;     // 配置项读取后的处理方法};
对于一个HTTP请求的处理,多个模块能够在不同阶段介入,关于阶段的定义和介入方法见“HTTP请求的11个处理阶段”。但对于NGX_HTTP_CONTENT_PHASE阶段还有另外一个介入的方法:把希望处理请求的ngx_http_handler_pt方法放入保存location块配置项的ngx_http_core_loc_t结构体的handler指针中。我们的模块就是运用这样的方法使得该模块在NGX_HTTP_CONTENT_PHASE介入一个HTTP请求。
如今来写Nginx模块,也就是填充ngx_module_t结构体。
1、定义ngx_module_t结构体中的commands数组:
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,        // 出现mytest配置项时,ngx_http_mytest函数被调用        NGX_HTTP_LOC_CONF_OFFSET,        0,        NULL,    },    ngx_null_command            // 以一个空的ngx_command_t作为结尾};
以下是当配置文件里出现“mytest”配置项时会解析该配置项的方法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;      // 找到mytest配置项所属的配置块    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);      // 设置处理请求的方法,HTTP框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时    // 假设主机域名、URI和mytest模块所在配置块名称同样,就会调用函数ngx_http_mytest_handler    clcf->handler = ngx_http_mytest_handler;      return NGX_CONF_OK;}
处理HTTP请求的ngx_http_mytest_handler方法先跳过。
2、定义ngx_module_t结构体中的ctx接口:
static ngx_http_module_t ngx_http_mytest_module_ctx = {    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,};
HTTP框架在初始化时会调用每一个HTTP模块的这8个方法,我们没有什么方法须要调用,所以设为NULL。
定义完上述两个最重要的结构体成员,如今模块的定义例如以下:
ngx_module_t ngx_http_mytest_module = {    NGX_MODULE_V1,      // 0,0,0,0,0,0,1    &ngx_http_mytest_module_ctx,    ngx_http_mytest_commands,    NGX_HTTP_MODULE,    // 定义模块类型     /* Nginx在启动和退出时会调用以下7个回调方法 */    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    NGX_MODULE_V1_PADDING,  // 0,0,0,0,0,0,0,0,保留字段};
ngx_module_t结构体中開始的7个字段不须要在定义时赋值,所以由一个宏NGX_MODULE_V1来进行集体初始化。
最后一个任务就是处理HTTP请求,以下就是处理请求并返回响应的函数ngx_http_mytest_handler:
// 请求的全部信息都存入ngx_http_request_t结构体中static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r){    // 请求的方法必须为GET或者HEAD    if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))        return NGX_HTTP_NOT_ALLOWED;      // 丢弃请求中的包体    ngx_int_t rc = ngx_http_discard_request_body(r);    if (rc != NGX_OK)        return rc;      ngx_str_t type = ngx_string("text/plain");    ngx_str_t response = ngx_string("Hello World!");    // 包体内容      // 设置响应的HTTP头部    r->headers_out.status = NGX_HTTP_OK;           // 返回的响应码    r->headers_out.content_length_n = response.len;    // 响应包体长度    r->headers_out.content_type = type;                // Content-Type      rc = ngx_http_send_header(r); // 发送HTTP头部    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)        return rc;     // 假设响应不包括包体,则在此处能够直接返回rc     // 分配响应包体空间,由于是异步发送,所以不要从栈中获得空间    ngx_buf_t *b = ngx_create_temp_buf(r->pool, response.len);      if (b == NULL)        return NGX_HTTP_INTERNAL_SERVER_ERROR;      ngx_memcpy(b->pos, response.data, response.len);    b->last = b->pos + response.len;  // 指向数据末尾    b->last_buf = 1;                    // 声明这是最后一块缓冲区      ngx_chain_t out;    out.buf = b;    out.next = NULL;      return ngx_http_output_filter(r, &out);   // 向用户发送响应包}
以下说明怎样让写好的模块编译进Nginx。
1、在和源文件同样文件夹下创建一个名为config的文件,文件内容例如以下:
ngx_addon_name=ngx_http_mytest_moduleHTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
  • ngx_addon_name:模块名称
  • HTTP_MODULES:保存全部的HTTP模块名称,以空格符分隔
  • NGX_ADDON_SRCS:新增模块的源码路径,变量ngx_addon_dir稍后说明
路径例如以下所看到的:
2、在configure时加入自己定义模块的路径:
./configure --add-module=/work/nginx/modules/mytest
当中,路径/work/nginx/modules/mytest就相应了config文件里的ngx_addon_dir变量。如今configure程序便知道了我们定义的模块的绝对路径。
3、make
4、make install
终于会在/usr/local/nginx/sbin文件夹下生成可运行文件ngxin。
5、在/usr/local/nginx/conf/nginx.conf配置文件里加入须要的配置项:
6、启动nginx,并在client浏览器中输入Nginxserver的主机名:
能够看到,Nginx成功返回了我们设置的字符串。注意URI必须和/nestle全然匹配,由于location配置块后面是一个“=”符号,表示必须全然一致。
參考:
《深入理解Nginx》第三章。
你可能感兴趣的文章
列表运用和copy详解
查看>>
Nginx上部署HTTPS + HTTP2
查看>>
awk
查看>>
踩过的坑
查看>>
如何使用 tomcat !!!
查看>>
计算机网络参考模型
查看>>
戴尔的对比
查看>>
8款高质量小程序推荐:(工具类、电影类、阅读类)
查看>>
看图了解RocksDB
查看>>
python整数和变量
查看>>
深入探讨下Linux下修改hostname的五个问题(一)
查看>>
使用HCL模拟器配置DHCP相关项目
查看>>
OC中的NSSet(集合)
查看>>
为什么C++所有程序员都值得一学?
查看>>
如何隐藏IP地址
查看>>
网络yum源
查看>>
WSUS规划部署(一)安装部署WSUS
查看>>
谷歌浏览器不能通过页面打开摄像头的处理
查看>>
mysql+heartbeat双主高可用
查看>>
输入框字数统计(通过键盘输入和拷贝粘贴皆可)
查看>>