Nginx学习笔记:模块开发

要开发一个叫“Hello”的handler模​​块,这个模块功能非常简单,它接收指令hello,该指令可指定一个字符串参数,模块会输出这个字符串作为HTTP响应。

例如,本地Nginx做如下配置:

location /hello {
    hello "nginx";
}

访问http://hostname/hello返回:

hello nginx

直观来看,要实现这个功能需要三步:

  • 1、读入配置文件中hello指令及其参数
  • 2、进行HTTP包装(添加HTTP头等工作)
  • 3、将结果返回给客户端。

相关头文件引用

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

定义配置结构

首先需要一个结构用于存储从配置文件中读进来的相关指令参数,即模块配置信息结构。根据Nginx模块开发规则,这个结构的命名规则为ngx_http_[module-name]_[main|srv|loc]_conf_t。其中 main、srv、loc 分别用于表示同一模块在三层block中的配置信息。这里,Hello模块只需要运行在 loc 层级下,需要存储一个字符串参数,因此我们可以定义如下的模块配置:

typedef struct
{
    ngx_str_t hello_string;
} ngx_http_hello_loc_conf_t;

其中字段hello_string用于存储hello指令指定的需要输出的字符串。

注意:Nginx规定模块中使用ngx_str_t类型表示字符串,这个类型定义具体在core/ngx_string中。

定义指令

一个Nginx模块往往接收一至多个指令,Hello模块接收一个指令hello。Nginx模块使用一个ngx_command_t数组表示模块所能接收的所有模块,其中每一个元素表示一个条指令。ngx_command_tngx_command_s的一个别称(Nginx习惯于使用“_s”后缀命名结构体,然后typedef使用一个同名“_t”后缀名称作为此结构体的类型名)。

ngx_command_s定义在core/ngx_config_file.h中。

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

其中name是词条指令的名称,type使用掩码标志位方式配置指令参数,相关可用type定义在core/ngx_config_file.h中:

#define NGX_CONF_NOARGS       0x00000001
#define NGX_CONF_TAKE1        0x00000002
#define NGX_CONF_TAKE2        0x00000004
#define NGX_CONF_TAKE3        0x00000008
#define NGX_CONF_TAKE4        0x00000010
#define NGX_CONF_TAKE5        0x00000020
#define NGX_CONF_TAKE6        0x00000040
#define NGX_CONF_TAKE7        0x00000080
#define NGX_CONF_MAX_ARGS     8
#define NGX_CONF_TAKE12       ( NGX_CONF_TAKE1 | NGX_CONF_TAKE2 )
#define NGX_CONF_TAKE13       ( NGX_CONF_TAKE1 | NGX_CONF_TAKE3 )
#define NGX_CONF_TAKE23       ( NGX_CONF_TAKE2 | NGX_CONF_TAKE3 )
#define NGX_CONF_TAKE123      ( NGX_CONF_TAKE1 | NGX_CONF_TAKE2 | NGX_CONF_TAKE3 )
#define NGX_CONF_TAKE1234     ( NGX_CONF_TAKE1 | NGX_CONF_TAKE2 | NGX_CONF_TAKE3 \
        | NGX_CONF_TAKE4 )
#define NGX_CONF_ARGS_NUMBER 0x000000ff
#define NGX_CONF_BLOCK        0x00000100
#define NGX_CONF_FLAG         0x00000200
#define NGX_CONF_ANY          0x00000400
#define NGX_CONF_1MORE        0x00000800
#define NGX_CONF_2MORE        0x00001000
#define NGX_CONF_MULTI        0x00002000

NGX_CONF_NOARGS表示不接受参数
NGX_CON F_TAKE1-7表示精确接收1~7个
NGX_CONF_TAKE12表示接受1或2个参数
NGX_CONF_1MORE表示至少一个参数
NGX_CONF_FLAG表示接受“on|off”

set是一个函数指针,用于指定一个参数转化函数,这个函数一般是将配置文件中相关指令的参数转化成需要的格式并存入配置结构体。Nginx预定义了一些转换函数,可以方便我们调用,这些函数定义在core/ngx_conf_file.h中,一般以“_slot”结尾。

例如ngx_conf_set_flag_slot将“on或off”转换为“1或0”,ngx_conf_set_str_slot将裸字符串转化为ngx_str_t

下面定义Hello模块的指令hello

static ngx_command_t ngx_http_hello_commands[] =
{
    {
        ngx_string("hello"),
        NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
        ngx_http_hello,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_hello_loc_conf_t, hello_string),
        NULL
    },
    ngx_null_command
};

定义Context

定义一个ngx_http_module_t类型的结构体变量,命名规则为ngx_http_[module-name]_module_ctx,这个结构主要用于定义各个Hook函数。下面是Hello模块的context结构:

static ngx_http_module_t ngx_http_hello_module_ctx =
{
    NULL, /* preconfiguration */
    NULL, /* 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 configration */
    ngx_http_hello_merge_loc_conf            /* merge location configration */
};

可以看到一共有8个Hook注入点,分别会在不同时刻被Nginx调用。由于Hello模块仅仅用于location域,这里将不需要的注入点设为NULL即可。

其中,

  • create_loc_conf用于初始化一个配置结构体,如为配置结构体分配内存等工作
  • merge_loc_conf用于将其父block的配置信息合并到此结构体中,也就是实现配置的继承

这两个函数会被Nginx自动调用。

注意:命名规则是ngx_http_[module-name]_[create|merge]_[main|srv|loc]_conf

下面是这个两个函数的代码:

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

其中ngx_pcalloc用于在Nginx内存池中分配一块空间,是pcalloc的一个包装。使用ngx_pcalloc分配的内存空间不必手工Free,Nginx会自行管理,在适当是否释放。create_loc_conf新建一个gx_http_hello_loc_conf_t,分配内存,并初始化其中的数据,然后返回这个结构的指针。merge_loc_conf则将父block域的配置信息合并到create_loc_conf新建的配置结构体中。

static char * ngx_http_hello_merge_loc_conf(ngx_conf_t * cf, void * parent, void * child)
{
    ngx_http_hello_loc_conf_t * prev=parent ;
    ngx_http_hello_loc_conf_t * conf=child ;
    ngx_conf_merge_str_value(conf->hello_string, prev->hello_string, "");
    return NGX_CONF_OK;
}

编写Handler

  • 读入模块配置
  • 理功能业务
  • 产生HTTPHeader
  • 产生HTTPBody

Handler可以说是模块中真正干活的代码,它主要有以上四项职责:

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 * elcf;
    elcf=ngx_http_get_module_loc_conf(r, ngx_http_hello_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" )-1;
    r->headers_out.content_type.data=(u_char *) "text/html";
    r->headers_out.status=NGX_HTTP_OK;
    r->headers_out.content_length_n=elcf->hello_string.len;
    if(r->method==NGX_HTTP_HEAD)
    {
        rc=ngx_http_send_header(r);
        if(rc!=NGX_OK) {
            return rc;
        }
    }
    b=ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if(b==NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    out.buf=b ;
    out.next=NULL ;
    b->pos=elcf->hello_string.data ;
    b->last=elcf ->hello_string.data + (elcf->hello_string.len);
    b->memory=1;
    b->last_buf=1;
    rc=ngx_http_send_header(r);
    if(rc!=NGX_OK) {
    return rc;
    }
    return ngx_http_output_filter(r , &out);
}

组合Nginx_Module

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

标签: Linux, Nginx, 笔记, 开发, 模块

添加新评论

仅有一条评论

  1. RAKsmart优惠码 RAKsmart优惠码

    也准备学习这个