熟悉Nginx,为Nginx编写插件(二)

前面介绍了Nginx,现在准备为它写一个名叫“helloworld”的插件,它提供helloworld命令。
前面提到过,Nginx的插件可以提供命令,命令在插件里本身是关联到一个处理函数的,这个处理函数直接处理request,并把结果返回到链表里。可以在配置文件里配置什么时候执行插件的相关命令,这样就可以调度插件了。
本文地址:http://www.hoverlees.com/blog/?p=352

按上面和前一篇文章提的,我们可以加一个location叫/helloworld,如下:

location /helloworld{
	helloworld;
}

这样用户访问/helloworld的时候,就可以执行我们的模块了。

但是还不能急,一般模块都会涉及到一些数据结构,像PHP的模块,需要模块结构,函数表结构等,类似的,Nginx的模块也需要模块结构,模块结构又相关到命令结构和上下文结构。思路都是差不多的。

首先是命令结构,模块要提供些什么命令,是初始化一个命令结构体数组去告诉内核的,命令结构如下:

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

此结构在ngx_core.h中被定义为ngx_command_t,其它结构也是类似。其中字段:

  • name 命令名,如gzip,listen等
  • type 命令类型,设置命令可以作用的配置路径,同时设置命令可带的参数,具体可以设置如NGX_HTTP_MAIN_CONF(在main块下执行),NGX_HTTP_SRV_CONF(在server块下执行),可以OR上NGX_CONF_NOARGS(不带参数),NGS_CONF_TAKE1(带1个参数,可以TAKE1-7),也可以不定参数的配置,如NGX_CONF_TAKE1 | NGX_CONF_TAKE2 表示可以有一个或两个参数。以此类推。具体可参考core/ngx_conf_file.h头文件。
  • set 设置命令,这个函数将由内核调用,我们需要实现这样的函数以告诉内核命令对应的真正处理函数。
  • conf 表示是否要保存配置的区域,可以是定义的NGX_HTTP_MAIN_CONF_OFFSET等定义。
  • offset 表求要写到配置文件的哪个域
  • post 模块可能需要的数据

conf,offset,post大多数时候可以留空。

接下来是上下文结构,要让模块知道自己在个什么形势下运行,上下文就是很重要的,不像windows的应用程序,系统给每个程序4G的虚拟地址空间,这种就是完全独立而不需要了解上下文,什么时候被注入了dll,地址空间里的数据被修改了都不知道。注意,我没说windows这种不好,它非常好。只是拿它对比上下文而已。
明确地说,上下文是一个函数表,即是一系列的由内核通知模块一些事情的回调函数,内核给你机会去改变内核的运行状态。
如果要写HTTP模块,那么上下文就是一个ngx_http_module_t结构,它在/http/ngx_http_config.h中定义。

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;

我们可以去实现其中的部分我们感兴趣的函数,如果没什么兴趣,也可以一个都不实现。
最后是表示模块本身的模块结构体:

struct ngx_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; //前面一大块都用NGX_MODULE_V1实例

    void                 *ctx; //模块上下文指针
    ngx_command_t        *commands; //命令集指针,指向一个ngx_command_t数组
    ngx_uint_t            type; //模块类型,一般是NGX_HTTP_MODULE
//下面都是些回调函数,我认为函数名很直观,没必要作注。有兴趣的话就可以实现它们。
    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);
//下面也可以直接用NGX_MODULE_V1_PADDING实例化
    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;
};

这个struct ngx_module_s也被定义为ngx_module_t。

上面的内容基本上可以组成一个简单的Nginx模块了。下面就写一个HelloWorld的模块,这个模块其实是从Nginx现有的模块里复制出来的,这样可以省我们很多事。

/*
 * Nginx hello world module
 * by Hoverlees http://www.hoverlees.com
 *
 */

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
//采用Nginx的模块命名规范
u_char ngx_helloworld_string[] = "Hello, World!";

//声明命令初始化函数
char* ngx_http_helloworld_setup(ngx_conf_t *cf, ngx_command_t *cmd,void *conf);

static ngx_command_t ngx_http_helloworld_commands[] = {
	{
		ngx_string("helloworld"),
		NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
		ngx_http_helloworld_setup, //设置我们的命令初始函数
		0,
		0,
		NULL
	},

    ngx_null_command
};//实例化命令数组,以ngx_null_command结尾

static ngx_http_module_t ngx_http_helloworld_module_ctx = {
    NULL,                          /* preconfiguration */
    NULL,                          /* postconfiguration */

    NULL,                          /* create main configuration */
    NULL,                          /* init main configuration */

    NULL,                          /* create server configuration */
    NULL,                          /* merge server configuration */

    NULL,                          /* create location configuration */
    NULL                           /* merge location configuration */
};
//实例化上下文结构,可以一个函数也不管。

ngx_module_t ngx_http_helloworld_module = {
    NGX_MODULE_V1,
    &ngx_http_helloworld_module_ctx,	/* module context */
    ngx_http_helloworld_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
};//实例化模块对象

//helloworld命令的真正处理函数,参数是ngx_http_request_t即一个请求
//关于ngx_http_request_t就不具体写出来了,懂HTTP协议的人都了解的。
//此函数实现的也是基本处理流程
static ngx_int_t ngx_http_helloworld_handler(ngx_http_request_t *r){
    ngx_int_t    rc;
    ngx_buf_t   *b;
    ngx_chain_t  out;

	//完善HTTP头
    /* set the 'Content-type' header */
    r->headers_out.content_type.len = sizeof("text/html") - 1;
    r->headers_out.content_type.data = (u_char *) "text/html";

	//分配输出内存空间
    /* allocate a buffer */
    b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
	//输出缓存附加到输出链表上
    /* attach buffer to the buffer chain */
    out.buf = b;
    out.next = NULL;

	//填写输出缓存内容
    /* adjust the pointers of the buffer */
    b->pos = ngx_helloworld_string;  /* the begin offset of the buffer */
    b->last = ngx_helloworld_string + sizeof(ngx_helloworld_string) - 1; /* the end offset of the buffer */
    b->memory = 1;    /* this buffer is in memory */
    b->last_buf = 1;  /* this is the last buffer in the buffer chain */

	//还是完善HTTP头
    /* set the status line */
    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = sizeof(ngx_helloworld_string) - 1;

	//输出HTTP头
    /* send the headers of your response */
    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
}
//输出内容
    /* send the buffer chain of your response */
    return ngx_http_output_filter(r, &out);
}

//实现helloworld命令的初始化函数,此函数指定命令的真正处理函数为ngx_http_helloworld_handler
char* ngx_http_helloworld_setup(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_helloworld_handler; /* handler to process the 'helloworld' directive */

    return NGX_CONF_OK;
}

这样一个简单的模块就实现了。接下来是编译。
在Linux下,建议用户在src目录下创建一个ext目录,然后每个模块再单独放到ext目录下的各自目录下。每个目录可以单独创建一个config文件,写入config内容.下面的内容是helloworld的config文件:

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

其中ngx_http_helloworld_module是ngx_module_t的实例名,其它的看着文件改。

然后是configure:

./configure –add-module=ext/helloword

make;make install

最后就是调用模块了。按我以前提过的原理,配置文件增加如下:

location /helloworld{
    helloworld;
}

这下只要访问http://服务器地址/helloworld就可以看到我们模块输出的Hello, World!


Join the Conversation

5 Comments

Your email address will not be published. Required fields are marked *

  1. 你好,错误指出的是没有指令helloworld,检查一下你启动的nginx程序是不是带模块编译生成的文件.
    还有种可能就是nginx升级后改变了一些调用方式.
    你可以先尝试下载例子的整个源代码编译试试(例子使用的nginx0.9.3). 代码在四篇文章中下载.

  2. 你好~~我尝试了一下helloworld,发现有个错误,想请教一下您。我说说我做的步骤:
    1.mkdir创建了/usr/local/src/ext/helloworld,helloworld内有两个文件:config和ngx_http_helloworld_module.c。ngx_http_helloworld_module.c是直接copy你的helloworld模块的c文件,config文件也是copy你的。
    2../configure –add-module=/usr/local/src/ext/helloworld
    3.make make install都没有出现问题,显示也添加了helloworld模块
    4.改了/usr/local/nginx/nginx.conf的内容,在server{}中添加了location /helloworld { helloworld; }
    5.运行/usr/local/nginx/nginx,然后就弹出 nginx: [emerg] unknown directive “helloworld” in /usr/local/nginx/nginx.conf。
    不知道如何解决,希望得到您帮助,不胜感激