这里主要通过一个简单的 hello world 示例学习一下 nginx 中 http 模块(Nginx 还支持 event 模块,mail模块等)开发。
我们下面就是开发一个名叫 mytest 的模块,该模块很简单,就是输出 hello world!,如下配置:
location /mytest {
ngx_mytest_module;
}
当访问 curl -i http://localhost:38080/mytest 时输出如下:
[root@dev nginx]# curl -i http://localhost:38080/mytest
HTTP/1.1 200 OK
Server: nginx/1.4.1
Date: Thu, 04 Jul 2013 07:26:49 GMT
Content-Type: text/plain
Content-Length: 13
Connection: keep-alive
hello, world!
1,定义指令
/* Commands */
static ngx_command_t ngx_http_mytest_commands[] = {
{ ngx_string("ngx_mytest_module"),
NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
ngx_http_mytest,
NGX_HTTP_LOC_CONF_OFFSET,
0,
NULL },
ngx_null_command
};
这里定义了指令名为 ngx_mytest_module。其中 ngx_command_t 的结构定义如下:
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:使用掩码位方式配置指令参数,可用配置 types 定义在 core/ngx_conf_file.h 和 http/ngx_http_config.h 中,如下:
------------------------
core/ngx_conf_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_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 0x00000000 /* compatibility */
#define NGX_DIRECT_CONF 0x00010000
#define NGX_MAIN_CONF 0x01000000
#define NGX_ANY_CONF 0x0F000000
------------------------
http/ngx_http_config.h
#define NGX_HTTP_MAIN_CONF 0x02000000
#define NGX_HTTP_SRV_CONF 0x04000000
#define NGX_HTTP_LOC_CONF 0x08000000
#define NGX_HTTP_UPS_CONF 0x10000000
#define NGX_HTTP_SIF_CONF 0x20000000
#define NGX_HTTP_LIF_CONF 0x40000000
#define NGX_HTTP_LMT_CONF 0x80000000
- set 函数指针,用于指定一个参数转化函数,一般主要用于将配置文件中的相关指令参数转化成需要的格式并存入配置结构体中,因我的示例里没有参数,只是简单的注册了 handler 函数指针
static char* ngx_http_mytest(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf = ngx_http_conf_get_module_loc_conf(cf, \
ngx_http_core_module);
fprintf(stderr, "ngx_http_mytest pid=%d\n", getpid());
/* register hanlder */
clcf->handler = ngx_http_mytest_handler;
return NGX_CONF_OK;
}
2,定义模块 Context
static ngx_http_module_t ngx_http_mytest_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 configration */
NULL /* merge location configration */
};
3,编写 Handler
handler 一般主要有 4 个职责:
- 读入模块配置
- 处理业务功能
- 生成 HTTP Header
- 生成 HTTP Body
static ngx_int_t ngx_http_mytest_handler(ngx_http_request_t *r)
{
if (!(r->method & (NGX_HTTP_GET | NGX_HTTP_HEAD)))
{
return NGX_HTTP_NOT_ALLOWED;
}
ngx_int_t rc = ngx_http_discard_request_body(r);
if (rc != NGX_OK)
{
return rc;
}
ngx_str_t type = ngx_string("text/plain");
ngx_str_t response = ngx_string("hello, world!");
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = response.len;
r->headers_out.content_type = type;
// 发送响应头
rc = ngx_http_send_header(r);
if (rc == NGX_ERROR || rc > NGX_OK || r->header_only)
{
return rc;
}
// 生成响应体
ngx_buf_t *b;
b = ngx_create_temp_buf(r->pool, response.len);
if (b == NULL)
{
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
ngx_memcpy(b->pos, response.data, response.len);
b->last = b->pos + response.len;
b->last_buf = 1;
ngx_chain_t out;
out.buf = b;
out.next = NULL;
// 通过 filter 输出(缓冲区中的)响应
return ngx_http_output_filter(r, &out);
}
对上面用到的几个重要数据结构简单了解了一下:
1) handler 接收到的 http 请求消息结构定义(ngx_http_request_t,定义于 http/ngx_http_request.h):
struct ngx_http_request_s {
uint32_t signature; /* "HTTP" */
ngx_connection_t *connection;
void **ctx;
void **main_conf;
void **srv_conf;
void **loc_conf;
ngx_http_event_handler_pt read_event_handler;
ngx_http_event_handler_pt write_event_handler;
#if (NGX_HTTP_CACHE)
ngx_http_cache_t *cache;
#endif
ngx_http_upstream_t *upstream;
ngx_array_t *upstream_states;
/* of ngx_http_upstream_state_t */
ngx_pool_t *pool;
ngx_buf_t *header_in;
ngx_http_headers_in_t headers_in;
ngx_http_headers_out_t headers_out;
ngx_http_request_body_t *request_body;
time_t lingering_time;
time_t start_sec;
ngx_msec_t start_msec;
ngx_uint_t method;
ngx_uint_t http_version;
ngx_str_t request_line;
ngx_str_t uri;
ngx_str_t args;
ngx_str_t exten;
ngx_str_t unparsed_uri;
ngx_str_t method_name;
ngx_str_t http_protocol;
ngx_chain_t *out;
ngx_http_request_t *main;
ngx_http_request_t *parent;
ngx_http_postponed_request_t *postponed;
ngx_http_post_subrequest_t *post_subrequest;
ngx_http_posted_request_t *posted_requests;
ngx_int_t phase_handler;
ngx_http_handler_pt content_handler;
ngx_uint_t access_code;
ngx_http_variable_value_t *variables;
#if (NGX_PCRE)
ngx_uint_t ncaptures;
int *captures;
u_char *captures_data;
#endif
size_t limit_rate;
/* used to learn the Apache compatible response length without a header */
size_t header_size;
off_t request_length;
ngx_http_upstream_t *upstream;
ngx_array_t *upstream_states;
/* of ngx_http_upstream_state_t */
ngx_pool_t *pool;
ngx_buf_t *header_in;
ngx_http_headers_in_t headers_in;
ngx_http_headers_out_t headers_out;
ngx_http_request_body_t *request_body;
... ...
}
这个请求结构爆长(超过110个成员变量),只列出了部分,可以通过这个结构访问 url,args,request_body等等常见 HTTP 信息。而这里还需要注意 headers_in,headers_out 和 chain 等字段,它们分别用于表示 request_header,response header 以及 输出数据缓冲区链表。
2) 在返回 HTTP 响应时主要是设置 headers_out 字段来实现,我们示例只设置了 Content-Type 和 Content-Length 等基本信息,headers_out 类型 ngx_http_headers_out_t 还定义了其他字段:
typedef struct {
ngx_list_t headers;
ngx_uint_t status;
ngx_str_t status_line;
ngx_table_elt_t *server;
ngx_table_elt_t *date;
ngx_table_elt_t *content_length;
ngx_table_elt_t *content_encoding;
ngx_table_elt_t *location;
ngx_table_elt_t *refresh;
ngx_table_elt_t *last_modified;
ngx_table_elt_t *content_range;
ngx_table_elt_t *accept_ranges;
ngx_table_elt_t *www_authenticate;
ngx_table_elt_t *expires;
ngx_table_elt_t *etag;
ngx_str_t *override_charset;
size_t content_type_len;
ngx_str_t content_type;
ngx_str_t charset;
u_char *content_type_lowcase;
ngx_uint_t content_type_hash;
ngx_array_t cache_control;
off_t content_length_n;
time_t date_time;
time_t last_modified_time;
} ngx_http_headers_out_t;
设置好响应头信息后,可以通过 ngx_http_send_header 来输出头信息。
3) 下面就是要发送响应body,这里主要涉及了 ngx_chain_t 结构:
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
看结构,可以知道 ngx_chain_t 就是一个单链表,每个节点是 ngx_chain_t 类型,而 buf 为指向实际数据的缓冲区指针,ngx_buf_t 结构定义如下:
struct ngx_buf_s {
u_char *pos;
u_char *last;
off_t file_pos;
off_t file_last;
u_char *start; /* start of buffer */
u_char *end; /* end of buffer */
ngx_buf_tag_t tag;
ngx_file_t *file;
ngx_buf_t *shadow;
... ...
/* STUB */ int num;
};
这里用到主要是 pos 和 last 指针,分别表示数据在缓冲区中的起始地址与结束地址。如例子中的数据 "hello, world!"。
4) 下面就是通过 filter 遍历指定的链表(ngx_chain_t)来输出响应体内容
4,组合 Module
ngx_module_t ngx_http_mytest_module = {
NGX_MODULE_V1,
&ngx_http_mytest_module_ctx, /* module context */
ngx_http_mytest_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
};
5,模块的编译安装
1) 模块 config 文件,该文件需要与模块源文件放在同一路径下,如下:
[root@dev ngx_http_mytest]# ll
总用量 8
-rw-r--r--. 1 root root 164 2013-07-04 10:39 config
-rw-r--r--. 1 root root 2521 2013-07-04 15:17 ngx_http_mytest_module.c
[root@dev ngx_http_mytest]#
[root@dev ngx_http_mytest]# more config
ngx_addon_name=ngx_http_mytest_module
HTTP_MODULES="$HTTP_MODULES ngx_http_mytest_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_mytest_module.c"
2) 进入 Nginx 源码路径,使用下面命令编译安装:
./configure --prefix=/root/apps/nginx --add-module=/root/test/ngx_http_mytest
make
sudo make install
参考资料:
nginx 模块开发向导:http://www.evanmiller.org/nginx-modules-guide.html
可以参考学习的第三方模块:http://wiki.nginx.org/3rdPartyModules
Nginx 模块开发入门:http://kb.cnblogs.com/page/98352/