开发一个HTTP模块

Posted Flytiger1220

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了开发一个HTTP模块相关的知识,希望对你有一定的参考价值。

1. 前言

首先来分析一下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模块调用的大致流程图:

2. HTTP模块数据结构

2.1 ngx_module_t结构体

HTTP模块有一个结构体ngx_module_t,它的定义例如以下(省略了一些不常用的参数):

typedef struct ngx_module_s ngx_module_t;
struct ngx_module_s 

    ngx_uint_t            ctx_index;    /* 当前模块在同类模块中的序号 */
    ngx_max_module = 0;
    for (i = 0; ngx_module[i]; i++) 
    	ngx_module[i] -> index = ngx_max_module++;
    
    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;          /* ctx用于指向一类模块的上下文结构体,指向特定类型模块的公共
    接口,在http模块中,ctx需要指向ngx_http_module_t结构体 */
    ngx_command_t        *commands;     /* 用于处理配置文件nginx.conf中的配置项 */
    ngx_uint_t            type;         /* 当前模块类型,http模块的类型是NGX_HTTP_MODULE */
 
    /* 下面7个函数指针表示7个运行点,这些运行点将在Nginx启动和退出过程中被调用
     * 假设不须要则设置为NULL
     */
    ngx_int_t           (*init_master)(ngx_log_t *log);     /* 字面意思上是master启动时调用
    init_master,但框架从未调用过init_master,这里设为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); /* nginx暂不支持多线程模式,init_thread
    也从未被调用,设为NULL */
    void                (*exit_thread)(ngx_cycle_t *cycle); /* 同init_thread,exit_thread
    也从未被调用,设为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;
;

2.2 ngx_module_t结构体说明

  • 对于以下回调方法:init_module、init_process、exit_process、exit_master,调用它们的是nginx框架代码。也就是说,这四个回调方法与HTTP框架无关;即使nginx.conf中没有配置http…这种开启HTTP功能的配置项,这些回调方法仍然会被调用。
  • HTTP模块的type字段必须设置为NGX_HTTP_MODULE,以表明此模块为HTTP模块。
  • HTTP模块的ctx指针必须指向ngx_http_module_t结构体,它表示HTTP模块所定义的通用接口;
  • HTTP模块的commands数组由若干ngx_command_t链接而成。当Nginx在解析配置文件的一个配置项时,会遍历全部模块的commands数组,以找到对该配置项感兴趣的ngx_command_t结构体,从而找到对应的模块。数组结尾用ngx_null_command表示。

2.3 ngx_http_module_t结构体

HTTP框架在读取、重载配置文件时定义了由ngx_http_module_t接口描述的8个阶段,HTTP框架在启动过程中会在每个阶段调用ngx_http_module_t中相应的方法。

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的配置项的结构体 */
    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则不会调用它们;
nginx启动过程中,以上八个阶段的调用顺序和定义的顺序是不同的;

2.4 ngx_command_t结构体

ngx_command_t结构体定义例如以下:

typedef struct ngx_command_s ngx_command_t;
struct ngx_command_s 
    ngx_str_t             name;     /* 配置项名称 */
    ngx_uint_t            type;     /* 配置项类型,包含该配置项能够出现的位置和能够携带參数的个数;
    例如,出现在server或location中,以及它可以携带的参数个数 */
    
    char                  *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);     /* 出现
    name配置项后,调用此方法解析配置项參数 */
    ngx_uint_t            conf;     /* 配置文件里的偏移量,确定将该配置项放入哪个存储结构体中 */
    ngx_uint_t            offset;   /* 将该配置项放在存储结构体的哪个字段,与conf结合使用 */
    void                 *post;     /* 配置项读取后的处理方法 */
;

ngx_null_command只是一个空的ngx_command_t,如下所示:
#define ngx_null_command ngx_null_string, 0, NULL, 0, 0, NULL

3. 定义自己的HTTP模块

3.1 新建源文件

在/home/nginx/src/test/下,新建一个源文件,ngx_http_tiger_module.c,内容如下:

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>

static ngx_int_t ngx_http_tiger_handler(ngx_http_request_t *r);
static char *ngx_http_tiger(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
//定义ngx_module_t结构体中的commands数组
static ngx_command_t ngx_http_tiger_commands[] = 
        
                ngx_string("tiger"), //配置项名称
                NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_NOARGS, //类型 type
                ngx_http_tiger, //set 函数,出现tiger配置时,ngx_http_tiger会被调用
                NGX_HTTP_LOC_CONF_OFFSET,//偏移
                0,
                NULL
        ,
        ngx_null_command// 以一个空的ngx_command_t作为结尾
;

//回调函数,出现tiger配置项后调用
static char *ngx_http_tiger(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)

        ngx_http_core_loc_conf_t *clcf;
        //找到tiger所属配置块
        clcf=ngx_http_conf_get_module_loc_conf(cf,ngx_http_core_module);

        //主机域名、URI与mytest所在配置块匹配时,将调用handler
        clcf->handler=ngx_http_tiger_handler;
        return NGX_CONF_OK;

//定义ngx_module_t结构体中的ctx接口,模块上下文
//如果没有什么工作是必须在HTTP框架初始化时完成,则不必实现8个回调
static ngx_http_module_t ngx_http_tiger_module_ctx=
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL,
        NULL
;
//定义完上述两个最重要的结构体成员ngx_command_t和ngx_http_module_t之后,如今模块的定义例如以下:
ngx_module_t ngx_http_tiger_module = 
    NGX_MODULE_V1,      // 0,0,0,0,0,0,1
    &ngx_http_tiger_module_ctx,
    ngx_http_tiger_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_http_request_t结构体中
static ngx_int_t ngx_http_tiger_handler(ngx_http_request_t *r)

        //请求方法必须是GET或者HEAD,否则返回405 \\NOT ALLOWED
        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(NGX_OK!=rc)
        
                return rc;
        

        //设置返回的Content-Type ngx_string是一个宏可以初始化data字段和len字段
        ngx_str_t type=ngx_string("text/plain");
        ngx_str_t response=ngx_string("Hello World");
        //响应包体内容和状态码设置
        r->headers_out.status=NGX_HTTP_OK;
        r->headers_out.content_length_n=response.len;
        r->headers_out.content_type=type;

        //发送http头部
        rc=ngx_http_send_header(r);
        if(rc==NGX_ERROR || rc>NGX_OK || r->header_only)
        
                return rc;
        
        //构造ngx_buf_t结构体准备发送报文
        ngx_buf_t *b;
        b=ngx_create_temp_buf(r->pool,response.len);
        if(NULL==b)
        
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
        
        //拷贝响应报文
        ngx_memcpy(b->pos,response.data,response.len);
        //设置好last指针,指向数据末尾
        b->last=b->pos+response.len;
        //声明这是最后一块缓冲区
        b->last_buf=1;

        //构造发送时的ngx_chain_t结构体
        ngx_chain_t out;
        out.buf=b;
        out.next=NULL;
        //发送响应,结束后HTTP框架会调用ngx_http_finalize_request方法结束请求
        return ngx_http_output_filter(r,&out);


3.2 新建config文件

在和源文件同样文件夹下创建一个名为config的文件,文件内容例如以下:

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

ngx_addon_name:模块名称
HTTP_MODULES:保存全部的HTTP模块名称,以空格符分隔
NGX_ADDON_SRCS:新增模块的源码路径,变量ngx_addon_dir稍后说明

3.3 编译HTTP模块到nginx中

1、在configure时加入自己定义模块的路径:

./configure --add-module=/home/nginx/src/test

当中,路径/home/nginx/src/test就相应了config文件里的ngx_addon_dir变量。如今configure程序便知道了我们定义的模块的绝对路径。

2、make&make install

3、在/usr/local/nginx/conf/nginx.conf配置文件里加入须要的配置项:

location / 
             tiger;
             #root   html;
             #index  index.html index.htm;
         

4、启动nginx,并在浏览器访问nginx所在主机的IP:

4. 遇到的问题

问题描述:

自定义http_module之后,执行了如下编译命令./configure --add-module=/home/nginx/src/test,启动nginx一直报nginx.conf的
tiger;
这一行有语法错误:nginx: [emerg] unknown directive tiger

问题解决思路:

1、执行nginx -V | grep adbcc查看nginx已安装的模块,发现没有自定义的模块;
2、查看nginx安装目录/objs/ngx_modules.c文件,发现里面也没有自定义的模块;
3、尝试修改nginx.conf,发现还是不行;

问题原因:

最终原因是安装完nginx之后,编译安装指定了特定目录: ./configure --prefix=/usr/local/webserver/nginx;
在执行./configure --add-module=/home/nginx/src/test时,将模块安装到nginx的默认安装目录/usr/local/nginx中了,启动/usr/local/webserver/nginx这里的配置,也就无法加载到自定义的http模块;

解决思路:

将以上两个编译命令合并执行:
./configure --prefix=/usr/local/webserver/nginx --add-module=/home/nginx/src/test
重新启动nginx即可

5. 参考

【Nginx】开发一个简单的HTTP模块
nginx查看已安装模块
如何查看Nginx安装了哪些模块

以上是关于开发一个HTTP模块的主要内容,如果未能解决你的问题,请参考以下文章

比较字符串忽略开头或结尾处的空格

Nginx核心配置文件介绍

nginx之文件配置

nginx入门:配置文档结构

在第一个空格之后直到字符串结尾

在正则表达式中,我只需要在中间允许空格,并在开头和结尾防止空格