Nginx 模块开发(1)―― 一个稍稍能说明问题模块开发 Step By Step 过程
nginx做为HTTP服务器,有以下几项基本特性:
处理静态文件,索引文件以及自动索引;打开文件描述符缓冲.
无缓存的反向代理加速,简单的负载均衡和容错.
FastCGI,简单的负载均衡和容错.
模块化的结构。包括gzipping, byte ranges, chunked responses,以及 SSI-filter等filter。如果由FastCGI或其它代理服务器处理单页中存在的多个SSI,则这项处理可以并行运行,而不需要相互等待。
支持SSL 和 TLSSNI.
Nginx专为性能优化而开发,性能是其最重要的考量,实现上非常注重效率。它支持内核 epoll、kqueue 等高性能并发模型,能经受高负载的考验。
Nginx具有很高的稳定性。其它HTTP服务器,当遇到访问的峰值,或者有人恶意发起慢速连接时,也很可能会导致服务器物理内存耗尽频繁交换,失去响 应,只能重启服务器。例如当前apache一旦上到200个以上进程,web响应速度就明显非常缓慢了。而Nginx采取了分阶段资源分配技术,使得它的 CPU与内存占用率非常低。nginx官方表示保持10,000个没有活动的连接,它只占2.5M内存,所以类似DOS这样的攻击对nginx来说基本上 是毫无用处的。就稳定性而言,nginx比lighthttpd更胜一筹。
Nginx支持热部署。它的启动特别容易, 并且几乎可以做到7*24不间断运行,即使运行数个月也不需要重新启动。你还能够在不间断服务的情况下,对软件版本进行进行升级。
Nginx采用master-slave模型,能够充分利用SMP的优势,且能够减少工作进程在磁盘I/O的阻塞延迟。当采用select()/poll()调用时,还可以限制每个进程的连接数。
Nginx代码质量非常高,代码很规范,手法成熟, 模块扩展也很容易。特别值得一提的是强大的Upstream与Filter链。 Upstream为诸如reverse proxy,与其他服务器通信模块的编写奠定了很好的基础。而Filter链最酷的部分就是各个filter不必等待前一个filter执行完毕。它可以 把前一个filter的输出做为当前filter的输入,这有点像Unix的管线。这意味着,一个模块可以开始压缩从后端服务器发送过来的请求,且可以在 模块接收完后端服务器的整个请求之前把压缩流转向客户端。
当然,nginx还很年轻,多多少少存在一些问题,比如:Nginx是俄罗斯人创建,目前文档方面还不是很完善.因为文档大多是俄语,所以文档方面这也是个障碍.尽管nignx的模块比较多,但它们还不够完善。对脚本的支持力度不够。
这些问题,nginx的作者和社区都在努力解决,我们有理由相信nginx将继续以高速的增长率来分享轻量级HTTP服务器市场,会有一个更美好的未来。 2. 准备工作 去官方主页 http://nginx.org/ 下载最新的Nginx源码包,这里给出目前最新的源码包的直接连接: http://nginx.org/download/nginx-1.0.0.tar.gz
[zieckey@freebsd7.2 ~]$ mkdir nginx
[zieckey@freebsd7.2 ~]$ cd nginx
[zieckey@freebsd7.2 ~/nginx]$ wget http://nginx.org/download/nginx-1.0.0.tar.gz
[zieckey@freebsd7.2 ~/nginx]$ tar zxf nginx-1.0.0.tar.gz
[zieckey@freebsd7.2 ~/nginx]$ mkdir module_dev_urlquery
[zieckey@freebsd7.2 ~/nginx]$ cd module_dev_urlquery/
[zieckey@freebsd7.2 module_dev_urlquery]$
这里我们下载了 nginx-1.0.0 的源码,然后准备开发一个 module_dev_urlquery 的module嵌入到nginx中
[zieckey@freebsd7.2 module_dev_urlquery]$ vim config
ngx_addon_name=ngx_http_p2s_module
HTTP_MODULES="$HTTP_MODULES ngx_http_p2s_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_p2s_module.c"
这里三行内容稍稍解释下: 第一行是表示这个 nginx module 在程序中名字 第二行表示这是一个HTTP module,后面的名字与第一个行保存一致 第三是这个module的源文件路径,值得说明的是 $ngx_addon_dir 这个变量是 nginx 的内置脚本的内置变量,代表了这个 module 的文件夹的绝对路径,这里就是 /home/zieckey/nginx/module_dev_urlquery 4. 准备源代码文件 ngx_http_p2s_module.c 这里的文件名和路径必须与上面 config 文件中的一致 [zieckey@freebsd7.2 module_dev_urlquery]$ vim ngx_http_p2s_module.c 输入以下源程序内容:
#include <ngx_core.h>
#include <ngx_http.h>
#include <nginx.h>
typedef struct {
unsigned long consume;
char* ini_buf;
size_t buflen;//the lenght of the ini_buf
} ngx_http_p2s_conf_t;
static char *ngx_http_p2s_urlquery_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_p2s_create_conf(ngx_conf_t *cf);
static uint8_t* get_raw_http_body( ngx_http_request_t* r, size_t* body_len );
static ngx_command_t ngx_http_p2s_commands[] =
{/*{{{*/
{ ngx_string("p2s_urlquery"), //The command name, it MUST BE the same as nginx.conf location block's command
NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_p2s_urlquery_set,
0,
0,
NULL },
ngx_null_command
};/*}}}*/
static ngx_http_module_t ngx_http_p2s_module_ctx =
{/*{{{*/
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_p2s_create_conf, /* create location configration */
NULL /* merge location configration */
};/*}}}*/
ngx_module_t ngx_http_p2s_module =
{/*{{{*/
NGX_MODULE_V1,
&ngx_http_p2s_module_ctx, /* module context */
ngx_http_p2s_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
};/*}}}*/
/**
* @brief Get the HTTP body data from the ngx_http_request_t struct.
* @warning DONNOT release the return pointer.
* @param[in] ngx_http_request_t * r -
* The HTTP request of NGINX struct which holds the HTTP data.
* @param[out] size_t * body_len - The body data length will stored here.
* @return uint8_t* - A pointer to a memory where
* stored the HTTP body raw binary data.
* The memory is allocated from nginx memory pool,
* so the caller don't need to warry about the memory release work.
*/
static uint8_t* get_raw_http_body( ngx_http_request_t* r, size_t* body_len )
{/*{{{*/
printf( "%s\n", __PRETTY_FUNCTION__ );
ngx_chain_t* bufs = r->request_body->bufs;
*body_len = 0;
ngx_buf_t* buf = NULL;
uint8_t* data_buf = NULL;
size_t content_length = 0;
if ( r->headers_in.content_length == NULL )
{
return NULL;
}
// malloc space for data_buf
content_length = atoi( (char*)(r->headers_in.content_length->value.data) );
data_buf = ( uint8_t* )ngx_palloc( r->pool , content_length + 1 );
size_t buf_length = 0;
while ( bufs )
{
buf = bufs->buf;
bufs = bufs->next;
buf_length = buf->last - buf->pos ;
if( *body_len + buf_length > content_length )
{
memcpy( data_buf + *body_len, buf->pos, content_length - *body_len );
*body_len = content_length ;
break;
}
memcpy( data_buf + *body_len, buf->pos, buf->last - buf->pos );
*body_len += buf->last - buf->pos;
}
if ( *body_len )
{
data_buf[*body_len] = '\0';
}
return data_buf;
}/*}}}*/
/**
* Process the client request.
* The client post data has stored in <code>r</code>
*/
static void p2s_urlquery_process_handler(ngx_http_request_t *r)
{/*{{{*/
printf( "%s\n", __PRETTY_FUNCTION__ );
ngx_int_t rc = NGX_OK;
ngx_buf_t *b = NULL;
ngx_chain_t out;
ngx_http_p2s_conf_t *conf = NULL;
conf = (ngx_http_p2s_conf_t *)ngx_http_get_module_loc_conf(r, ngx_http_p2s_module);
if (conf == NULL)
{
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return ;
}
struct timeval tv;
gettimeofday(&tv, NULL);
size_t bodylen = 0;
uint8_t* contents = get_raw_http_body( r, &bodylen );
printf( "time=%f http body data len=%d:\n%s\n", (tv.tv_sec + tv.tv_usec/1000000.0f ), (int)bodylen, (char*)contents );
printf("----------------------http body data end-------------------\n");
/* Prepare for output, 128 is preserved for robust */
b = ngx_create_temp_buf( r->pool, 128 + conf->buflen );
if (b == NULL)
{
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return ;
}
out.buf = b;
out.next = NULL;
b->last = ngx_sprintf(b->pos, "%s", conf->ini_buf);
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = b->last - b->pos;
r->headers_out.content_type.len = sizeof("text/plain") - 1;
r->headers_out.content_type.data = (u_char *) "text/plain";
b->last_buf = 1;/* there will be no more buffers in the request */
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return ;
}
ngx_http_output_filter(r, &out);
ngx_http_finalize_request(r, 0);
}/*}}}*/
/**
* Reading data handler
* After read all the data from client we set a process handler
*/
static ngx_int_t
ngx_http_p2s_urlquery_handler(ngx_http_request_t *r)
{/*{{{*/
printf( "%s\n", __PRETTY_FUNCTION__ );
ngx_int_t rc = NGX_DONE;
rc = ngx_http_read_client_request_body( r, p2s_urlquery_process_handler );
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
return NGX_DONE;
}/*}}}*/
/**
* set the request reading data handler
*/
static char *
ngx_http_p2s_urlquery_set( ngx_conf_t *cf, ngx_command_t *cmd, void *conf )
{/*{{{*/
printf( "%s\n", __PRETTY_FUNCTION__ );
ngx_http_core_loc_conf_t *clcf;
clcf = (ngx_http_core_loc_conf_t *)ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_p2s_urlquery_handler;
return NGX_CONF_OK;
}/*}}}*/
static void *
ngx_http_p2s_create_conf(ngx_conf_t *cf)
{/*{{{*/
printf( "%s\n", __PRETTY_FUNCTION__ );
ngx_http_p2s_conf_t *conf;
conf = (ngx_http_p2s_conf_t *)ngx_pcalloc(cf->pool, sizeof(ngx_http_p2s_conf_t));
if (conf == NULL) {
return NGX_CONF_ERROR;
}
conf->consume = 0;
/* we open the nginx config file and send it back to client*/
FILE *fp = fopen( "../conf/nginx.conf", "r");
if( fp == NULL )
{
return NGX_CONF_ERROR;
}
fseek(fp, 0, SEEK_END);
long len = ftell( fp );
if ( len < 0 )
{
return NGX_CONF_ERROR;
}
conf->buflen = (size_t)(len + 1);
conf->ini_buf = (char *)ngx_palloc( cf->pool, len + 1 );
fseek(fp, 0, SEEK_SET);
fread(conf->ini_buf, 1, len, fp);
conf->ini_buf[len] = 0;
fclose(fp);
return conf;
}/*}}}*/
5. 编译运行 现在可以编译nginx和我们刚刚写好的模块了。
[zieckey@freebsd7.2 ~/nginx]$ cd
[zieckey@freebsd7.2 ~]$ cd nginx/
[zieckey@freebsd7.2 ~/nginx]$ mkdir bininstalled
[zieckey@freebsd7.2 ~/nginx]$ cd nginx-1.0.0
[zieckey@freebsd7.2 nginx-1.0.0]$ ./configure --add-module=/home/zieckey/nginx/module_dev_urlquery --prefix=/home/zieckey/nginx/bininstalled
./configure: error: the HTTP rewrite module requires the PCRE library.
You can either disable the module by using --without-http_rewrite_module
option, or install the PCRE library into the system, or build the PCRE library
statically from the source with nginx by using --with-pcre=<path> option.
第一次错误,我们现在这个freebsd系统上没有pcre库,太悲催了,不过还好,这里给出了提示,说可以通过 --without-http_rewrite_module 来禁用使用pcre库的 HTTP rewrite 模块,我们试一试:
[zieckey@freebsd7.2 nginx-1.0.0]$ ./configure --add-module=/home/zieckey/nginx/module_dev_urlquery --prefix=/home/zieckey/nginx/bininstalled --without-http_rewrite_module
adding module in /home/zieckey/nginx/module_dev_urlquery
+ ngx_http_p2s_module was configured
重点看到上面几行信息,说明我们自己写的模块module_dev_urlquery已经被nginx接纳,生成makefile成功。
[zieckey@freebsd7.2 nginx-1.0.0]$ make
[zieckey@freebsd7.2 nginx-1.0.0]$ make install
[zieckey@freebsd7.2 nginx-1.0.0]$ cd /home/zieckey/nginx/bininstalled/
[zieckey@freebsd7.2 bininstalled]$ ls
conf html logs sbin
[zieckey@freebsd7.2 bininstalled]$ cd conf
[zieckey@freebsd7.2 conf]$ vim ngi
nginx.conf nginx.conf.default
[zieckey@freebsd7.2 conf]$ vim nginx.conf
默认的配置文件是下面(将‘#’开头的注释行删除之后)
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}
}
我们在最上面添加如下选项:daemon off; 可以让程序不以daemon的方式运行,这样我们可以看到一些调试的打印信息。
另外,在 server 里面添加一个 URI :
location /urlquery {
p2s_urlquery;
}
最后,我们修改下http监听的端口号,从默认的80改为8088,因为有些时候,我们并没有权限在80端口上监听连接。
修改之后的配置文件全文如下:
daemon off;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8088;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /urlquery {
p2s_urlquery;
}
}
}
好了,到此为止,我们的所有工作就都准备好了,可以启动nginx
[zieckey@freebsd7.2 conf]$ cd /home/zieckey/nginx/bininstalled/sbin/
[zieckey@freebsd7.2 sbin]$ ./nginx
ngx_http_p2s_create_conf
ngx_http_p2s_create_conf
ngx_http_p2s_create_conf
ngx_http_p2s_create_conf
ngx_http_p2s_urlquery_set
新开一个终端,用curl来发起一个http post请求,post数据由-d参数指定:
[zieckey@freebsd7.2 ~]$ curl -d "user=zieckey&verifykey=123456" http://localhost:8088/urlquery
daemon off;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8088;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
location /urlquery {
p2s_urlquery;
}
}
}
[zieckey@freebsd7.2 ~]$
然后可以看看nginx服务器的一些输出信息:
[zieckey@freebsd7.2 sbin]$ ./nginx
ngx_http_p2s_create_conf
ngx_http_p2s_create_conf
ngx_http_p2s_create_conf
ngx_http_p2s_create_conf
ngx_http_p2s_urlquery_set
ngx_http_p2s_urlquery_handler
p2s_urlquery_process_handler
get_raw_http_body
time=1303552000.000000 http body data len=29:
user=zieckey&verifykey=123456
----------------------http body data end-------------------
6. 其他说明 6.1 配置文件和ngx_command_t的对应关系:
static ngx_command_t ngx_http_p2s_commands[] =
{/*{{{*/
{ ngx_string("p2s_urlquery"), // 命令名,请对照 nginx.conf 看这个
NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_p2s_urlquery_set,
0,
0,
NULL },
ngx_null_command
};/*}}}*/
nginx.conf 中的 location 字段的配置是这样的:
location /urlquery {
p2s_urlquer;
}
这里的 /urlquery 是 URL 请求里的 URI 部分, 例如 http://localhost/urlquery 这条URL的请求会被这条配置项处理, 这条配置项,会去找命令名为“p2s_urlquery”的 ngx_command 去处理。
6.2 模块名 代码中的模块变量名必须与config配置文件的名字一致 config 配置文件的内容:ngx_addon_name=ngx_http_p2s_module 代码中的内容:
ngx_module_t ngx_http_p2s_module =
{/*{{{*/
NGX_MODULE_V1,
&ngx_http_p2s_module_ctx, /* module context */
ngx_http_p2s_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
};/*}}}*/
模块的config配置文件会帮助 configure 生成一个ngx_modules.c文件,该文件里会引用 ngx_module_t ngx_http_p2s_module 这个外部定义的变量,所以必须保持一致。在执行 ./configure 完后,在nginx-1.0.0源码的根目录会多出来一个文件夹objs,在这里可以找到ngx_modules.c文件。 6.3 代码调用关系 nginx程序启动的时候,会去读配置文件 conf/nginx.conf ,每读到一个配置项,就会调用 module 注册的回调函数,所以这里可以看到nginx启动的时候调用 ngx_http_p2s_create_conf 函数好几次。 这个函数是由
static ngx_http_module_t ngx_http_p2s_module_ctx =
{/*{{{*/
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_p2s_create_conf, /* create location configration */
NULL /* merge location configration */
};/*}}}*/
到此,我们完成了一个很简单的模块的开发和调试工作,也不是那么困难。
同时对一些函数调用,配置文件关系等等比较含糊的地方做了详细说明。