Ngnix handler模块编写

Nginx handler模块编写

在编写完的模块编译成功后,将会被加载进objs目录下ngx_modules.c文件中的ngx_modules[]数组中。此数组中的模块将被顺序执行。

实现模块功能

统计每个客户端的访问次数,并创建页面展示

module.c

定义ngx_module变量

​ 我们将module的名字命名为ngx_http_pagecount_module,这个名字在编译后将被加载到ngx_modules.c中的ngx_modules[]数组中。

​ 首先,在使用初始化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;	//与ctx相对应

    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;
}

​ 在这个结构体中,前七个参数为版本等相关信息,都是比较固定的写法,可以直接使用NGX_MODULE_V1来填充。NGX_MODULE_V1在Nginx中的定义如下所示:

#define NGX_MODULE_V1                                    \
    NGX_MODULE_UNSET_INDEX, NGX_MODULE_UNSET_INDEX,      \
    NULL, 0, 0, nginx_version, NGX_MODULE_SIGNATURE

​ 这其中有七个字段,分别对应了ngx_module_t中的ctx_indexindexnamespare0spare1versionsignature

​ 由于我们写的模块为http相关的模块,所以ctx为ngx_http_module_t类型的值,因此在配置文件中,我们的模块应用于http{}的模块下。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;

​ 其中有八个函数指针,当在conf文件中定义http module时,这个模块将被定义,其中三组函数分别对应自身模块、server模块和location模块。在加载配置文件时,每遇到一层,调用响应的函数。

​ 如果不使用相关的函数功能,可以将这八个函数全部写为空。

static ngx_http_module_t ngx_pagecount_module_ctx = {
    NULL,
    NULL,
    
    NULL,
    NULL,

    NULL,
    NULL,

    NULL,
    NULL,
};

​ 接下来是command字段,同样,command类型的定义如下所示:

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_uint_t            offset;
    void                 *post;
};

​ 因此我们同样可以定义我们的command:

//注意此处count_commands为一个数组,表示此模块可以定义多个命令来操作
static ngx_command_t count_commands[] = {
    //处理第一条操作命令:count
    {
        ngx_string("count"),
        NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,    //count命令在location命令中且不带任何参数(带3个参数的宏定义为NGX_CONF_TAKE3,以此类推)
        ngx_http_count_module_set,              //设置当遇到"count"命令时执行哪个函数
        NGX_HTTP_LOC_CONF_OFFSET,               //偏移位置
        0, NULL
    },
    ngx_null_command                            //数组结尾标志
};

​ 接下来的type字段,定义为http的type:NGX_HTTP_MODULE

​ 然后是一组函数,七个函数可以全部设置为NULL,然后结尾再以NGX_MODULE_V1_PADDING填充。于是整个ngx_http_pagecount_module的定义如下:

ngx_module_t ngx_http_pagecount_module = {

    NGX_MODULE_V1,                              //nginx自带的版本信息
    ngx_pagecount_module_ctx,

    //command
    count_commands,
    NGX_HTTP_MODULE,                            //
    NULL, 
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,

    NGX_MODULE_V1_PADDING
};

实现ngx_http_count_module_set

​ 此函数为ngx_http_pagecount_module定义中的的函数,函数定义如下:

static char *ngx_http_count_module_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf){
	//获取module location的配置文件
    //ngx_http_core_module为ngx最核心的模块,对http功能的实现
    ngx_http_core_loc_conft *corecf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

    //将handler设置为目标函数
    corecf->handler = ngx_http_count_module_handler;

    return NGX_CONF_OK;

}

实现ngx_http_count_module_handler

​ 该函数用来处理业务功能相关实现,取出IP地址,并使用数组存储,再组织成HTML页面并发送。

​ 首先我们定义一个包含ip地址和访问次数的结构体,并将其定义为数组:

typedef struct {
    int count;              //访问次数
    struct in_addr addr;    //ip
} ngx_pv_table;

ngx_pv_table pv_table[256];

​ 我们再定义一个用来组织http页面的函数:

static int ngx_encode_http_page(char *html){

    sprint(html, "<h1>hello world</h1>");
    strcat(html, "<h2>");

    int i = 0;
    for (i = 0; i < 256; i++){
        if (pv_table[i].count != 0){

            char str[INET_ADDRSTRLEN] = {0};
            char buffer[128] = {0};

            sprintf(buffer, "req from : %s, count: %d<br/>",
                    inet_ntop(AF_INET, &pv_table[i].addr, str, sizeof(str)), pv_table[i].count);

            strcat(html, buffer);

        }
    }
    return 0;

​ 接下来我们就开始编写handler了:

static ngx_init_t ngx_http_count_module_handler(ngx_http_request_t *r){
    u_char html[WEBPAGE_LENGTH] = {0};
    int len = sizeof(html);

    //取出ip地址
    struct sockaddr_in *client_addr = (struct sockaddr_in *)r->connetion->sockaddr;
    //使用数组存储

     //取得ip地址点分十进制的最后一位
    int idx = client_addr->sin_addr.s_addr >> 24;  

    pv_table[idx].count++;
    memcpy(&pv_table[idx].addr, &client_addr->sin_addr, sizeof(client_addr->sin_addr));

    //组织html页面(或者其他操作)
    ngx_encode_http_page((char*)html);

    //send
    //header
    r->headers_out.status = 200;
    ngx_str_set(&r->headers_out.content_type, "text/html");
    ngx_http_send_header(r);

    //body
    //使用ngx_chain_t
    //struct ngx_chain_s {
    //    ngx_buf_t    *buf;
    //    ngx_chain_t  *next;
    //};
    //
    //注意,此处chain中的buffer为ngx_buf_s这个结构体:
    //struct ngx_buf_s {
    //    u_char          *pos;
    //    u_char          *last;
    //    off_t            file_pos;
    //    off_t            file_last;
    //
    //    u_char          *start;         /* start of buffer */
    //    u_char          *end;           /* end of buffer */
    //    ngx_buf_tag_t    tag;
    //    ngx_file_t      *file;
    //    ngx_buf_t       *shadow;
    //
    //
    //    /* the buf's content could be changed */
    //    unsigned         temporary:1;
    //
    //    /*
    //     * the buf's content is in a memory cache or in a read only memory
    //     * and must not be changed
    //     */
    //    unsigned         memory:1;
    //
    //    /* the buf's content is mmap()ed and must not be changed */
    //    unsigned         mmap:1;
    //
    //    unsigned         recycled:1;
    //    unsigned         in_file:1;
    //    unsigned         flush:1;
    //    unsigned         sync:1;
    //    unsigned         last_buf:1;
    //    unsigned         last_in_chain:1;
    //
    //    unsigned         last_shadow:1;
    //    unsigned         temp_file:1;
    //
    //    /* STUB */ int   num;
    //};
    //
    //在这个结构体中,我们需要对pos、last、memory和last_buf进行初始化
    ngx_buf_t *b = ngx_pcalloc(r->pool, sizeof(ngx_bug_t));

    ngx_chain_t out;
    out.buf = b;
    out.next = NULL;

    b->pos = html;
    b->last = html + len;
    b->memory = 1;
    b->last_buf = 1;

    //通过过滤器将out发送出去
    return ngx_http_output_filter(r, &out);

}

使用模块

在使用模块时,我们可以在该模块同目录下添加config文件,并写下:

ngx_addon_name=ngx_http_pagecount_module                          //此处的名字要和定义的ngx_module_t结构体变量的名字相同
HTTP_MODULES="$HTTP_MODULES ngx_http_pagecount_modules"           //将模块加入http模块集中
NGX_ADDON_SRS="$NGX_ADDON_SRCS $ngx_addon_dir/pagecount_module.c" //配置模块相对应的文件位置,其中ngx_addon_dir为编译时--add-module中制定的路径

在编译时需要带上 --add-module=ngx_addon_dir参数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值