前面介绍了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!
我也偷了只兔子,表介意,啊哈哈
./configure –add-module=ext/helloword
应该是./configure –add-module=src/ext/helloword
关键看你的扩展目录是创建在跟src平级的目录下,还是创建在src目录下了
你好~~我尝试了一下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。
不知道如何解决,希望得到您帮助,不胜感激
你好,错误指出的是没有指令helloworld,检查一下你启动的nginx程序是不是带模块编译生成的文件.
还有种可能就是nginx升级后改变了一些调用方式.
你可以先尝试下载例子的整个源代码编译试试(例子使用的nginx0.9.3). 代码在四篇文章中下载.