Nginx模块编写初探

来源:互联网 发布:java怎么做界面设计 编辑:程序博客网 时间:2024/06/04 20:00

              编写一个Nginx的模块最少需要两个文件。config配置文件和C语言代码文件。config配置文件的文件名为”config”,C语言代码文件一般叫做ngx_http_模块的名字_module.c,我们实现的功能是在在Nginx的配置文件中的http块中的server块中添加一个location  /echo的配置块,在Nginx执行时用到配置是我们将指令后面的内容发送给客户端。

 

C语言文件的编写

下面开始介绍一个http模块的书写过程,首先我们新建一个文件”mytest”,在mytest新建一个ngx_http_echo_module.c的文件。

所有的功能都是从http_module_t、ngx_command_t、ngx_http_module_t三个数据结构中展示出来的。

 

Module模块信息

所有Nginx在执行的时候用到的module模块中所有的操作都是从http_module_t去找对应的执行的方法的。结构体一般命名为ngx_http_模块名_module。下面是结构体

structngx_module_s {    ngx_uint_t            ctx_index;    ngx_uint_t            index;    ngx_uint_t            spare0;    ngx_uint_t            spare1;    ngx_uint_t            spare2;    ngx_uint_t            spare3;    ngx_uint_t            version;     void                 *ctx;    ngx_command_t        *commands;    ngx_uint_t            type;      ngx_int_t           (*init_master)(ngx_log_t *log);         //初始化master    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);     //退出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;};


以上结构体中带有spare的成员都是保留字段。

ctx_index:  当前模块在同类模块中的位置。

index:当前模块在ngx_modules数组中的序号。在Nginx执行modules中事件顺序中,第几个被执行的。

version : Nginx模块版本,目前只有一种,暂定为1

对于整个结构体,前七项我们一般用用宏定义NGX_MODULE_V1

#defineNGX_MODULE_V1          0, 0, 0, 0, 0, 0,1   

后八项一般用NGX_MODULE_V1_PADDING

#defineNGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0,0, 0

ctx:指向ngx_http_module_t结构体,对于配置文件中关于当前配置的解析操作。

commands:配置文件中当前配置的每一个配置保存方法和配置对应的操作函数。

type:当前模块的类型,取值如下NGX_HTTP_MODULE,NGX_CORE_MODULE,

NGX_CONF_MODULE, NGX_EVENT_MODULE,NGX_MAIL_MODULE

     

我所写的代码是:

 ngx_module_tngx_http_echo_module = {

    NGX_MODULE_V1,    &ngx_http_echo_module_ctx,    ngx_http_echo_commands,    NGX_HTTP_MODULE,    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    NULL,    NGX_MODULE_V1_PADDING}; 

配置的创建和合并

ngx_http_module_t主要用于创建合并配置信息,

创建:就是当Nginx解析配置文件时候,出现了我们指定的配置时候,我们需要创建一个保存配置信息的结构体,这样我们引出另外一个需要我们自定义的结构体。

合并:就是当Nginx解析配置文件时候,中同一个配置出现多次的进行处理。

自定义结构体:结构的命名规则为ngx_http_[module-name]_[main|srv|loc]_conf_t。其中main、srv和loc分别用于表示同一模块在配置文件三层block中的配置信息。这里我们的echo模块只需要运行在loc层级下,需要存储一个字符串参数(如果一个有多个配置内容需要顶一个多个成员,变量的类型可以自己定义。)。因此结构体定义如下

typedef struct {    ngx_str_t ed;}ngx_http_echo_loc_conf_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);    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;

preconfiguration:解析配置文件前调用

postconfiguration:完成配置文件解析后调用

create_main_conf:当需要创建数据结构用户存储main级别的全局配置项时候调用

init_main_conf:初始化main级别配置项

create_srv_conf:当需要创建数据结构用户存储server级别的全局配置项时候调用

merge_srv_conf:  出现多个server的处理方法。

create_loc_conf:当需要创建数据结构用户存储location级别的全局配置项时候调用

merge_loc_conf: 出现多个location的处理方法。

 

下面是我具体的代码:

typedef struct {    ngx_str_t ed;}ngx_http_echo_loc_conf_t;

static void *ngx_http_echo_create_loc_conf(ngx_conf_t*cf){    ngx_http_echo_loc_conf_t *conf;    conf = ngx_pcalloc(cf->pool,sizeof(ngx_http_echo_loc_conf_t));    if(NULL == conf) {        return NGX_CONF_ERROR;    }    conf->ed.len = 0;    conf->ed.data = NULL;    return conf;}

static char *ngx_http_echo_merge_loc_conf(ngx_conf_t*cf, void *parent, void *child){    ngx_http_echo_loc_conf_t *prev = parent;    ngx_http_echo_loc_conf_t *conf = child;    ngx_conf_merge_str_value(conf->ed,prev->ed, "");    return NGX_CONF_OK;}


ngx_http_module_t

ngx_http_module_t用来定义Nginx可以接受的模块(配置文件中可以出现的配置命名)和可以出现的位置。

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


name:指令的名称,name是一个字符串,表示可以在Nginx配置文件中出现配置项的名字。

type:配置项类型(有几个参数或者可以在什么地方出现等),可以取的值。

type类型

解释

 

 

 

 

 

 

 

 

参数限制

NGX_CONF_NOARGS

配置项不带任何参数

NGX_CONF_TAKE1

配置项必须带一个参数

NGX_CONF_TAKE2

配置项必须带两个参数

NGX_CONF_TAKE3

配置项必须带三个参数

NGX_CONF_TAKE4

配置项必须带四个参数

NGX_CONF_TAKE5

配置项必须带五个参数

NGX_CONF_TAKE6

配置项必须带六个参数

NGX_CONF_TAKE7

配置项必须带七个参数

NGX_CONF_TAKE12

配置项可以带1或2个参数

NGX_CONF_TAKE13

配置项可以带1或4个参数

NGX_CONF_TAKE23

配置项可以带2或3个参数

NGX_CONF_TAKE123

配置项可以带1~3个参数

NGX_CONF_TAKE1234

配置项可以带1~4个参数

NGX_CONF_ARGS_NUMBER

保留

NGX_CONF_BLOCK

配置项定义额新的{}块

NGX_CONF_FLAG

配置项只能带一个参数,并且值为on或者off

NGX_CONF_ANY

不验证配置项携带的参数

NGX_CONF_1MORE

配置项的参数必须大于1个

NGX_CONF_2MORE

配置项的参数鼻血大于2个

NGX_CONF_MULTI

不大理解,

 

 

 

 

 

配置项在Nginx配置文件可以出现的位置

NGX_MAIN_CONF

配置项可以出现在全局变量中

NGX_EVENT_CONF

配置项可以出现在events{}中

NGX_MAIL_MAIN_CONF

配置项可以出现在mail{}块内或者imap{}块内

NGX_MAIL_SRV_CONF

配置项可以出现在server{}块内,然后该server块必须属于mail{}块或者imap{}块内

NGX_HTTP_MAIN_CONF

配置项可以出现在http{}块内

NGX_HTTP_SRV_CONF

配置项可以出现在 server{}块内,然而该块必须属于http{}块

NGX_HTTP_LOC_CONF

配置项可以出现在location{}块内,然而该块必须属于http{}块

NGX_HTTP_UPS_CONF

配置项可以出现在upstream{}块内,然而该upstream{}块必须属于http{}块

NGX_HTTP_SIF_CONF

NGX_HTTP_LIF_CONF

NGX_HTTP_LMT_CONF

不解释

 

set: 是一个函数指针,用于指定一个参数转化函数,这个函数一般是将配置文件中相关指令的参数转化成需要的格式并存入配置结构体。我们可以选用Nginx提供的一些函数,也可以自己写一个函数,使用Nginx提供的函数需要与offset成员一起配置

set函数测参数

   ngx_conf_t*cf:配置

   ngx_command_t *cmd:ngx_command_t数组

   void *conf: 当出现配置项需要调用的功能函数,即使此配置项对应的功能。

Nginx提供的解析配置文件的指令

函数原型

解释

ngx_conf_set_flag_slot

配置项只有一个参数,参数的值是off和on

ngx_conf_set_str_slot

配置项只有一个参数,参数的值是一个字符串

ngx_conf_set_str_array_slot

配置项可以出现多次,每次出现带一个参数。

ngx_conf_set_keyval_slot

配置项后面是一个键值对,两个参数

ngx_conf_set_num_slot

配置项只能有一参数,为数字

ngx_conf_set_size_slot

配置项后带一个参数,表示空间大小。一个数字跟上K,M,G或k、m、g

ngx_conf_set_msec_slot

ngx_conf_set_sec_slot

配置项后带一个参数,表示时间。可以单m(秒)、h(小时)、d(天)、w(星期)、M(月)、y,不同的是msec是毫秒,sec是秒

ngx_conf_set_sec_slot

ngx_conf_set_enum_slot

ngx_conf_set_bitmask_slot

ngx_conf_set_access_slot

ngx_conf_set_path_slot

 

ngx_conf_set_off_slot

配置项必须带一个参数,表示空间上的偏移量。

下面是我自己写的set函数

 

static char *ngx_http_echo(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_echo_handler; //指令对应的处理函数   ngx_conf_set_str_slot(cf, cmd, conf);   return NGX_CONF_OK;}


 

conf 用于指定Nginx相应配置文件内存其实地址,就是我们要使用的配置块在配置文件中的那个块内,在type中没有出现NGX_DIRECT_CONF和NGX_MAIN_CONF时才有效,一般可以通过内置常量指定,

NGX_HTTP_MAIN_CONF_OFFSET

需要访问的块是在main中创建,

NGX_HTTP_SRV_CONF_OFFSET

需要访问的块是在server中创建

NGX_HTTP_LOC_CONF_OFFSET

需要访问的块是location中创建

其实使用上面的那些值,只要看一下ngx_http_module_t就可以了,看看使用了那些creat函数创建了配置。

offset 表示需要将使用set函数转换的值要保存在自定义结构中那个成员的位置

post书上说有很多用处,是一个回调函数。

下面是我的ngx_command_t结构体,由于在使用的时候,一个配置块中可以有多个指令,所以在使用ngx_command_t是以数组形式出现,必须以ngx_null_command表示数组定义的结束。

static ngx_command_tngx_http_echo_commands[] = {    {ngx_string("echo"),     NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,     ngx_http_echo,     NGX_HTTP_LOC_CONF_OFFSET,     offsetof(ngx_http_echo_loc_conf_t, ed),     NULL },    ngx_null_command};


编写Handler

Handler就是指令对应的处理函数,有可能在一个模块中有多个指令,但是只需要一个Handler方法。我是在set函数(ngx_http_echo)中中指定的Handler处理函数,Handler可以说是模块中真正是实现我们功能的代码函数,它主要有以下四项职责:

读入模块配置。

处理功能业务。

产生HTTP header,发送头部

产生HTTP body

调用发送filter模块

下面是我的实现代码

static ngx_int_tngx_http_echo_handler(ngx_http_request_t *r){   ngx_int_t rc;   ngx_buf_t *b;   ngx_chain_t out;   ngx_http_echo_loc_conf_t *elcf;   elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);   if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST))) {       return NGX_HTTP_NOT_ALLOWED;    }   r->headers_out.content_type.len = sizeof("text/html");   r->headers_out.content_type.data = (u_char *)("text/html");   r->headers_out.status = NGX_HTTP_OK;   r->headers_out.content_length_n = elcf->ed.len;   if(NGX_HTTP_HEAD ==  r->method){       rc = ngx_http_send_header(r);       if(NGX_OK != rc) {           return rc;       }    }    b= ngx_pcalloc(r->pool, sizeof(ngx_buf_t));   if(NULL == b) {       ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed toallocate response buffer.");       return NGX_HTTP_INTERNAL_SERVER_ERROR;    }    out.buf = b;   out.next = NULL;   b->pos = elcf->ed.data;   b->last = elcf->ed.data + elcf->ed.len;   b->memory = 1;   b->last_buf =1;   rc = ngx_http_send_header(r);   if(NGX_OK != rc){       return rc;    }   return ngx_http_output_filter(r, &out);}


  C语言文件ngx_http_echo_module.c的所有代码如下:

#include <ngx_config.h>#include <ngx_core.h>#include <ngx_http.h> typedef struct {   ngx_str_t ed;}ngx_http_echo_loc_conf_t; static char *ngx_http_echo(ngx_conf_t *cf,ngx_command_t *cmd, void *conf);static void*ngx_http_echo_create_loc_conf(ngx_conf_t *cf);static char*ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);static ngx_int_t ngx_http_echo_handler(ngx_http_request_t*r); static ngx_command_tngx_http_echo_commands[] = {    {ngx_string("echo"),     NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,     ngx_http_echo,     NGX_HTTP_LOC_CONF_OFFSET,     offsetof(ngx_http_echo_loc_conf_t, ed),     NULL },    ngx_null_command}; static ngx_http_module_tngx_http_echo_module_ctx = {   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   ngx_http_echo_create_loc_conf,   ngx_http_echo_merge_loc_conf,}; ngx_module_t ngx_http_echo_module = {   NGX_MODULE_V1,   &ngx_http_echo_module_ctx,   ngx_http_echo_commands,   NGX_HTTP_MODULE,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NULL,   NGX_MODULE_V1_PADDING}; static char * ngx_http_echo(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_echo_handler;   ngx_conf_set_str_slot(cf, cmd, conf);   return NGX_CONF_OK;}  static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf){   ngx_http_echo_loc_conf_t *conf;   conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));   if(NULL == conf) {       return NGX_CONF_ERROR;    }   conf->ed.len = 0;   conf->ed.data = NULL;   return conf;} static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child){   ngx_http_echo_loc_conf_t *prev = parent;   ngx_http_echo_loc_conf_t *conf = child;   ngx_conf_merge_str_value(conf->ed, prev->ed, "");   return NGX_CONF_OK;} static ngx_int_tngx_http_echo_handler(ngx_http_request_t *r){   ngx_int_t rc;   ngx_buf_t *b;   ngx_chain_t out;   ngx_http_echo_loc_conf_t *elcf;   elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);   if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST))) {       return NGX_HTTP_NOT_ALLOWED;    }   r->headers_out.content_type.len = sizeof("text/html");   r->headers_out.content_type.data = (u_char *)("text/html");   r->headers_out.status = NGX_HTTP_OK;   r->headers_out.content_length_n = elcf->ed.len;   if(NGX_HTTP_HEAD ==  r->method){       rc = ngx_http_send_header(r);       if(NGX_OK != rc) {           return rc;       }    }    b= ngx_pcalloc(r->pool, sizeof(ngx_buf_t));   if(NULL == b) {       ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed toallocate response buffer.");       return NGX_HTTP_INTERNAL_SERVER_ERROR;    }    out.buf = b;   out.next = NULL;   b->pos = elcf->ed.data;   b->last = elcf->ed.data + elcf->ed.len;   b->memory = 1;   b->last_buf =1;   rc = ngx_http_send_header(r);   if(NGX_OK != rc){       return rc;    }   return ngx_http_output_filter(r, &out);} 


 

config文件的编写

Nginx不支持像Apache那样的动态链接模块,所以安装模块需要在config的时候将编译模块的命令写入到Nginxmakefile中,然后进行重新编译。安装模块的步骤如下:

1. config的书写 

  在mytest目录中,新建一个名字为config的文件,文件内容如下:

ngx_addon_name=模块完整名称HTTP_MODULES="$HTTP_MODULES模块完整名称"NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/源代码文件名"

我写的代码如下

ngx_addon_name=ngx_http_echo_moduleHTTP_MODULES="$HTTP_MODULES ngx_http_echo_module"NGX_ADDON_SRCS="$NGX_ADDON_SRCS$ngx_addon_dir/ngx_http_echo_module.c"

2.   编译安装命令为

./configure --prefix=/usr/local/nginx --add-module=/home/yefeng/ngxdev/ngx_http_echomakemake install   

 

运行结果

 

1.  修改Nginx的配置文件

在http块中的server块中添加如下内容。

location /echo {            echo  "hello world!";}


2.    运行Nginx,使用curi查看运行的结果。

[root@reagemytest]# curl -i http://localhost/echoHTTP/1.1200 OKServer:nginx/1.5.2Date:Wed, 17 Jul 2013 13:00:54 GMTContent-Type:text/htmlContent-Length:12Connection:keep-alive helloworld! [root@reage mytest]# 

blog: http://blog.csdn.net/rentiansheng/article/details/9360419

website:http://www.rhttp.cn

原创粉丝点击