前言
Nginx是當(dāng)前最流行的HTTP Server之一旁振,根據(jù)W3Techs的統(tǒng)計(jì)谓罗,目前世界排名(根據(jù)Alexa)前100萬的網(wǎng)站中域那,Nginx的占有率為6.8%活逆。與Apache相比,Nginx在高并發(fā)情況下具有巨大的性能優(yōu)勢剪决。
Nginx屬于典型的微內(nèi)核設(shè)計(jì)灵汪,其內(nèi)核非常簡潔和優(yōu)雅檀训,同時(shí)具有非常高的可擴(kuò)展性。Nginx最初僅僅主要被用于做反向代理享言,后來隨著HTTP核心的成熟和各種HTTP擴(kuò)展模塊的豐富峻凫,Nginx越來越多被用來取代Apache而單獨(dú)承擔(dān)HTTP Server的責(zé)任,例如目前淘寶內(nèi)各個(gè)部門正越來越多使用Nginx取代Apache览露,據(jù)筆者了解蔚晨,在騰訊和新浪等公司也存在類似情況。
同時(shí)肛循,大量的第三方擴(kuò)展模塊也令Nginx越來越強(qiáng)大。例如银择,由淘寶的工程師清無(王曉哲)和春來(章亦春)所開發(fā)的nginx_lua_module可以將Lua語言嵌入到Nginx配置中多糠,從而利用Lua極大增強(qiáng)了Nginx本身的編程能力,甚至可以不用配合其它腳本語言(如PHP或Python等)浩考,只靠Nginx本身就可以實(shí)現(xiàn)復(fù)雜業(yè)務(wù)的處理夹孔。而春來所開發(fā)的ngx_openresty更是通過集成LuaJIT等組件,將Nginx本身變成了一個(gè)完全的應(yīng)用開發(fā)平臺(tái)析孽。目前淘寶數(shù)據(jù)平臺(tái)與產(chǎn)品部量子統(tǒng)計(jì)的產(chǎn)品都是基于ngx_openresty所開發(fā)搭伤。對(duì)ngxin_lua_module或ngx_openresty感興趣的朋友可以參考我在關(guān)鍵詞上給出的鏈接,后續(xù)我也可能會(huì)寫一些與其有關(guān)的文章袜瞬。
本文將會(huì)重點(diǎn)關(guān)注Nginx模塊開發(fā)入門及基礎(chǔ)怜俐。目前Nginx的學(xué)習(xí)資料非常少,而擴(kuò)展模塊開發(fā)相關(guān)的資料幾乎只有《Emiller's Guide To Nginx Module Development》一文邓尤,此文十分經(jīng)典拍鲤,但是由于Nginx版本的演進(jìn),其中少許內(nèi)容可能有點(diǎn)過時(shí)汞扎。本文是筆者在研讀這篇文章和Nginx源代碼的基礎(chǔ)上季稳,對(duì)自己學(xué)習(xí)Nginx模塊開發(fā)的一個(gè)總結(jié)。本文將通過一個(gè)完整的模塊開發(fā)實(shí)例講解Nginx模塊開發(fā)的入門內(nèi)容澈魄。
本文將基于Nginx最新的1.0.0版本景鼠,操作系統(tǒng)環(huán)境為Linux(Ubuntu10.10)。
Nginx提要
開發(fā)Nginx擴(kuò)展當(dāng)然首要前提是對(duì)Nginx有一定的了解痹扇,然而本文并不打算詳細(xì)闡述Nginx的方方面面铛漓,諸如Nginx的安裝和各種詳細(xì)配置等內(nèi)容都可以在Nginx官網(wǎng)的Document中找到,本文在這里只會(huì)概括性描述一些后面可能會(huì)用到的原理和概念鲫构。
Nginx在Linux下的安裝與運(yùn)行
使用Nginx的第一步是下載Nginx源碼包票渠,例如1.0.0的下載地址為http://nginx.org/download/nginx-1.0.0.tar.gz。下載完后用tar命令解壓縮芬迄,進(jìn)入目錄后安裝過程與Linux下通常步驟無異问顷,例如我想將Nginx安裝到/usr/local/nginx下,則執(zhí)行如下命令:
./configure --prefix=/usr/local/nginx
make
make install
安裝完成后可以直接使用下面命令啟動(dòng)Nginx:
/usr/local/nginx/sbin/nginx
Nginx默認(rèn)以Deamon進(jìn)程啟動(dòng),輸入下列命令:
curl -i http://localhost/
就可以檢測Nginx是否已經(jīng)成功運(yùn)行杜窄〕β妫或者也可以在瀏覽器中輸入http://localhost/,應(yīng)該可以看到Nginx的歡迎頁面了塞耕。啟動(dòng)后如果想停止Nginx可以使用:
/usr/local/nginx/sbin/nginx -s stop
Nginx配置文件基本結(jié)構(gòu)
配置文件可以看做是Nginx的靈魂蚀腿,Nginx服務(wù)在啟動(dòng)時(shí)會(huì)讀入配置文件,而后續(xù)幾乎一切動(dòng)作行為都是按照配置文件中的指令進(jìn)行的扫外,因此如果將Nginx本身看做一個(gè)計(jì)算機(jī)莉钙,那么Nginx的配置文件可以看成是全部的程序指令。
下面是一個(gè)Nginx配置文件的實(shí)例:
#user nobody;
worker_processes 8;
error_log logs/error.log;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
server {
listen 80;
server_name localhost;
location / {
root /home/yefeng/www;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
Nginx配置文件是純文本文件筛谚,你可以用任何文本編輯器如vim或emacs打開它磁玉,通常它會(huì)在nginx安裝目錄的conf下,如我的nginx安裝在/usr/local/nginx驾讲,主配置文件默認(rèn)放在/usr/local/nginx/conf/nginx.conf蚊伞。
其中“#”表示此行是注釋,由于筆者為了學(xué)習(xí)擴(kuò)展開發(fā)安裝了一個(gè)純凈的Nginx吮铭,因此配置文件沒有經(jīng)過太多改動(dòng)时迫。
Nginx的配置文件是以block的形式組織的,一個(gè)block通常使用大括號(hào)“{}”表示谓晌。block分為幾個(gè)層級(jí)掠拳,整個(gè)配置文件為main層級(jí),這是最大的層級(jí)纸肉;在main層級(jí)下可以有event碳想、http等層級(jí),而http中又會(huì)有server block毁靶,server block中可以包含location block胧奔。
每個(gè)層級(jí)可以有自己的指令(Directive),例如worker_processes是一個(gè)main層級(jí)指令预吆,它指定Nginx服務(wù)的Worker進(jìn)程數(shù)量龙填。有的指令只能在一個(gè)層級(jí)中配置,如worker_processes只能存在于main中拐叉,而有的指令可以存在于多個(gè)層級(jí)岩遗,在這種情況下,子block會(huì)繼承父block的配置凤瘦,同時(shí)如果子block配置了與父block不同的指令宿礁,則會(huì)覆蓋掉父block的配置。指令的格式是“指令名 參數(shù)1 參數(shù)2 … 參數(shù)N;”蔬芥,注意參數(shù)間可用任意數(shù)量空格分隔梆靖,最后要加分號(hào)控汉。
在開發(fā)Nginx HTTP擴(kuò)展模塊過程中,需要特別注意的是main返吻、server和location三個(gè)層級(jí)姑子,因?yàn)閿U(kuò)展模塊通常允許指定新的配置指令在這三個(gè)層級(jí)中。
最后要提到的是配置文件是可以包含的测僵,如上面配置文件中“include mime.types”就包含了mine.types這個(gè)配置文件街佑,此文件指定了各種HTTP Content-type。
一般來說捍靠,一個(gè)server block表示一個(gè)Host沐旨,而里面的一個(gè)location則代表一個(gè)路由映射規(guī)則,這兩個(gè)block可以說是HTTP配置的核心榨婆。
下圖是Nginx配置文件通常結(jié)構(gòu)圖示磁携。
關(guān)于Nginx配置的更多內(nèi)容請(qǐng)參看Nginx官方文檔。
Nginx模塊工作原理概述
(Nginx本身支持多種模塊纲辽,如HTTP模塊、EVENT模塊和MAIL模塊璃搜,本文只討論HTTP模塊)
Nginx本身做的工作實(shí)際很少拖吼,當(dāng)它接到一個(gè)HTTP請(qǐng)求時(shí),它僅僅是通過查找配置文件將此次請(qǐng)求映射到一個(gè)location block这吻,而此location中所配置的各個(gè)指令則會(huì)啟動(dòng)不同的模塊去完成工作吊档,因此模塊可以看做Nginx真正的勞動(dòng)工作者。通常一個(gè)location中的指令會(huì)涉及一個(gè)handler模塊和多個(gè)filter模塊(當(dāng)然唾糯,多個(gè)location可以復(fù)用同一個(gè)模塊)怠硼。handler模塊負(fù)責(zé)處理請(qǐng)求,完成響應(yīng)內(nèi)容的生成移怯,而filter模塊對(duì)響應(yīng)內(nèi)容進(jìn)行處理香璃。因此Nginx模塊開發(fā)分為handler開發(fā)和filter開發(fā)(本文不考慮load-balancer模塊)。下圖展示了一次常規(guī)請(qǐng)求和響應(yīng)的過程舟误。
Nginx模塊開發(fā)實(shí)戰(zhàn)
下面本文展示一個(gè)簡單的Nginx模塊開發(fā)全過程葡秒,我們開發(fā)一個(gè)叫echo的handler模塊,這個(gè)模塊功能非常簡單嵌溢,它接收“echo”指令眯牧,指令可指定一個(gè)字符串參數(shù),模塊會(huì)輸出這個(gè)字符串作為HTTP響應(yīng)赖草。例如学少,做如下配置:
location /echo {
echo "hello nginx";
}
則訪問http://hostname/echo時(shí)會(huì)輸出hello nginx。
直觀來看秧骑,要實(shí)現(xiàn)這個(gè)功能需要三步:1版确、讀入配置文件中echo指令及其參數(shù)扣囊;2、進(jìn)行HTTP包裝(添加HTTP頭等工作)阀坏;3如暖、將結(jié)果返回給客戶端。下面本文將分部介紹整個(gè)模塊的開發(fā)過程忌堂。
定義模塊配置結(jié)構(gòu)
首先我們需要一個(gè)結(jié)構(gòu)用于存儲(chǔ)從配置文件中讀進(jìn)來的相關(guān)指令參數(shù)盒至,即模塊配置信息結(jié)構(gòu)。根據(jù)Nginx模塊開發(fā)規(guī)則士修,這個(gè)結(jié)構(gòu)的命名規(guī)則為ngx_http_[module-name]_[main|srv|loc]_conf_t枷遂。其中main、srv和loc分別用于表示同一模塊在三層block中的配置信息棋嘲。這里我們的echo模塊只需要運(yùn)行在loc層級(jí)下酒唉,需要存儲(chǔ)一個(gè)字符串參數(shù),因此我們可以定義如下的模塊配置:
typedef struct {
ngx_str_t ed;
} ngx_http_echo_loc_conf_t;
其中字段ed用于存儲(chǔ)echo指令指定的需要輸出的字符串沸移。注意這里ed的類型痪伦,在Nginx模塊開發(fā)中使用ngx_str_t類型表示字符串,這個(gè)類型定義在core/ngx_string中:
typedef struct {
size_t len;
u_char *data;
} ngx_str_t;
其中兩個(gè)字段分別表示字符串的長度和數(shù)據(jù)起始地址雹锣。注意在Nginx源代碼中對(duì)數(shù)據(jù)類型進(jìn)行了別稱定義网沾,如ngx_int_t為intptr_t的別稱,為了保持一致蕊爵,在開發(fā)Nginx模塊時(shí)也應(yīng)該使用這些Nginx源碼定義的類型而不要使用C原生類型辉哥。除了ngx_str_t外,其它三個(gè)常用的nginx type分別為:
typedef intptr_t ngx_int_t;
typedef uintptr_t ngx_uint_t;
typedef intptr_t ngx_flag_t;
具體定義請(qǐng)參看core/ngx_config.h攒射。關(guān)于intptr_t和uintptr_t請(qǐng)參考C99中的stdint.h或http://linux.die.net/man/3/intptr_t醋旦。
定義指令
一個(gè)Nginx模塊往往接收一至多個(gè)指令,echo模塊接收一個(gè)指令“echo”会放。Nginx模塊使用一個(gè)ngx_command_t數(shù)組表示模塊所能接收的所有模塊饲齐,其中每一個(gè)元素表示一個(gè)條指令。ngx_command_t是ngx_command_s的一個(gè)別稱(Nginx習(xí)慣于使用“_s”后綴命名結(jié)構(gòu)體咧最,然后typedef一個(gè)同名“_t”后綴名稱作為此結(jié)構(gòu)體的類型名)箩张,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使用掩碼標(biāo)志位方式配置指令參數(shù)窗市,相關(guān)可用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表示此指令不接受參數(shù)先慷,NGX_CON F_TAKE1-7表示精確接收1-7個(gè),NGX_CONF_TAKE12表示接受1或2個(gè)參數(shù)咨察,NGX_CONF_1MORE表示至少一個(gè)參數(shù)论熙,NGX_CONF_FLAG表示接受“on|off”……
set是一個(gè)函數(shù)指針,用于指定一個(gè)參數(shù)轉(zhuǎn)化函數(shù)摄狱,這個(gè)函數(shù)一般是將配置文件中相關(guān)指令的參數(shù)轉(zhuǎn)化成需要的格式并存入配置結(jié)構(gòu)體脓诡。Nginx預(yù)定義了一些轉(zhuǎn)換函數(shù)无午,可以方便我們調(diào)用,這些函數(shù)定義在core/ngx_conf_file.h中祝谚,一般以“_slot”結(jié)尾宪迟,例如ngx_conf_set_flag_slot將“on或off”轉(zhuǎn)換為“1或0”,再如ngx_conf_set_str_slot將裸字符串轉(zhuǎn)化為ngx_str_t交惯。
conf用于指定Nginx相應(yīng)配置文件內(nèi)存其實(shí)地址次泽,一般可以通過內(nèi)置常量指定,如NGX_HTTP_LOC_CONF_OFFSET席爽,offset指定此條指令的參數(shù)的偏移量意荤。
下面是echo模塊的指令定義:
static ngx_command_t ngx_http_echo_commands[] = {
{ ngx_string("echo"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_echo,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_echo_loc_conf_t, ed),
NULL },
ngx_null_command
};
指令數(shù)組的命名規(guī)則為ngx_http_[module-name]_commands,注意數(shù)組最后一個(gè)元素要是ngx_null_command結(jié)束只锻。
參數(shù)轉(zhuǎn)化函數(shù)的代碼為:
static char *
ngx_http_echo(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_echo_handler;
ngx_conf_set_str_slot(cf,cmd,conf);
return NGX_CONF_OK;
}
這個(gè)函數(shù)除了調(diào)用ngx_conf_set_str_slot轉(zhuǎn)化echo指令的參數(shù)外玖像,還將修改了核心模塊配置(也就是這個(gè)location的配置),將其handler替換為我們編寫的handler:ngx_http_echo_handler齐饮。這樣就屏蔽了此location的默認(rèn)handler捐寥,使用ngx_http_echo_handler產(chǎn)生HTTP響應(yīng)。
創(chuàng)建合并配置信息
下一步是定義模塊Context祖驱。
這里首先需要定義一個(gè)ngx_http_module_t類型的結(jié)構(gòu)體變量握恳,命名規(guī)則為ngx_http_[module-name]_module_ctx,這個(gè)結(jié)構(gòu)主要用于定義各個(gè)Hook函數(shù)羹膳。下面是echo模塊的context結(jié)構(gòu):
static ngx_http_module_t ngx_http_echo_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_echo_create_loc_conf, /* create location configration */
ngx_http_echo_merge_loc_conf /* merge location configration */
};
可以看到一共有8個(gè)Hook注入點(diǎn)睡互,分別會(huì)在不同時(shí)刻被Nginx調(diào)用根竿,由于我們的模塊僅僅用于location域陵像,這里將不需要的注入點(diǎn)設(shè)為NULL即可。其中create_loc_conf用于初始化一個(gè)配置結(jié)構(gòu)體寇壳,如為配置結(jié)構(gòu)體分配內(nèi)存等工作醒颖;merge_loc_conf用于將其父block的配置信息合并到此結(jié)構(gòu)體中,也就是實(shí)現(xiàn)配置的繼承乃摹。這兩個(gè)函數(shù)會(huì)被Nginx自動(dòng)調(diào)用渠概。注意這里的命名規(guī)則:ngx_http_[module-name][create|merge][main|srv|loc]_conf锰提。
下面是echo模塊這個(gè)兩個(gè)函數(shù)的代碼:
static void *
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_echo_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
if (conf == NULL) {
return NGX_CONF_ERROR;
}
conf->ed.len = 0;
conf->ed.data = NULL;
return conf;
}
static char *
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_echo_loc_conf_t *prev = parent;
ngx_http_echo_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->ed, prev->ed, "");
return NGX_CONF_OK;
}
其中ngx_pcalloc用于在Nginx內(nèi)存池中分配一塊空間,是pcalloc的一個(gè)包裝腰耙。使用ngx_pcalloc分配的內(nèi)存空間不必手工free,Nginx會(huì)自行管理铲球,在適當(dāng)是否釋放挺庞。
create_loc_conf新建一個(gè)ngx_http_echo_loc_conf_t,分配內(nèi)存稼病,并初始化其中的數(shù)據(jù)选侨,然后返回這個(gè)結(jié)構(gòu)的指針掖鱼;merge_loc_conf將父block域的配置信息合并到create_loc_conf新建的配置結(jié)構(gòu)體中。
其中ngx_conf_merge_str_value不是一個(gè)函數(shù)援制,而是一個(gè)宏戏挡,其定義在core/ngx_conf_file.h中:
#define ngx_conf_merge_str_value(conf, prev, default) \
if (conf.data == NULL) { \
if (prev.data) { \
conf.len = prev.len; \
conf.data = prev.data; \
} else { \
conf.len = sizeof(default) - 1; \
conf.data = (u_char *) default; \
} \
}
同時(shí)可以看到,core/ngx_conf_file.h還定義了很多merge value的宏用于merge各種數(shù)據(jù)晨仑。它們的行為比較相似:使用prev填充conf褐墅,如果prev的數(shù)據(jù)為空則使用default填充。
編寫Handler
下面的工作是編寫handler寻歧。handler可以說是模塊中真正干活的代碼掌栅,它主要有以下四項(xiàng)職責(zé):
讀入模塊配置。
處理功能業(yè)務(wù)码泛。
產(chǎn)生HTTP header猾封。
產(chǎn)生HTTP body。
下面先貼出echo模塊的代碼噪珊,然后通過分析代碼的方式介紹如何實(shí)現(xiàn)這四步晌缘。這一塊的代碼比較復(fù)雜:
static ngx_int_t
ngx_http_echo_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_echo_loc_conf_t *elcf;
elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_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->ed.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->ed.data;
b->last = elcf->ed.data + (elcf->ed.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);
}
handler會(huì)接收一個(gè)ngx_http_request_t指針類型的參數(shù),這個(gè)參數(shù)指向一個(gè)ngx_http_request_t結(jié)構(gòu)體痢站,此結(jié)構(gòu)體存儲(chǔ)了這次HTTP請(qǐng)求的一些信息磷箕,這個(gè)結(jié)構(gòu)定義在
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_http_virtual_names_t *virtual_names;
ngx_int_t phase_handler;
ngx_http_handler_pt content_handler;
ngx_uint_t access_code;
ngx_http_variable_value_t *variables;
/* ... */
}
由于ngx_http_request_s定義比較長,這里我只截取了一部分阵难≡兰希可以看到里面有諸如uri,args和request_body等HTTP常用信息呜叫。這里需要特別注意的幾個(gè)字段是headers_in空繁、headers_out和chain,它們分別表示request header朱庆、response header和輸出數(shù)據(jù)緩沖區(qū)鏈表(緩沖區(qū)鏈表是Nginx I/O中的重要內(nèi)容盛泡,后面會(huì)單獨(dú)介紹)。
第一步是獲取模塊配置信息娱颊,這一塊只要簡單使用ngx_http_get_module_loc_conf就可以了傲诵。
第二步是功能邏輯,因?yàn)閑cho模塊非常簡單箱硕,只是簡單輸出一個(gè)字符串拴竹,所以這里沒有功能邏輯代碼。
第三步是設(shè)置response header剧罩。Header內(nèi)容可以通過填充headers_out實(shí)現(xiàn)栓拜,我們這里只設(shè)置了Content-type和Content-length等基本內(nèi)容,ngx_http_headers_out_t定義了所有可以設(shè)置的HTTP Response Header信息:
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;
這里并不包含所有HTTP頭信息,如果需要可以使用agentzh(春來)開發(fā)的Nginx模塊HttpHeadersMore在指令中指定更多的Header頭信息菱属。
設(shè)置好頭信息后使用ngx_http_send_header就可以將頭信息輸出钳榨,ngx_http_send_header接受一個(gè)ngx_http_request_t類型的參數(shù)。
第四步也是最重要的一步是輸出Response body纽门。這里首先要了解Nginx的I/O機(jī)制薛耻,Nginx允許handler一次產(chǎn)生一組輸出,可以產(chǎn)生多次赏陵,Nginx將輸出組織成一個(gè)單鏈表結(jié)構(gòu)饼齿,鏈表中的每個(gè)節(jié)點(diǎn)是一個(gè)chain_t,定義在core/ngx_buf.h:
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
其中ngx_chain_t是ngx_chain_s的別名蝙搔,buf為某個(gè)數(shù)據(jù)緩沖區(qū)的指針缕溉,next指向下一個(gè)鏈表節(jié)點(diǎn),可以看到這是一個(gè)非常簡單的鏈表吃型。ngx_buf_t的定義比較長而且很復(fù)雜证鸥,這里就不貼出來了,請(qǐng)自行參考core/ngx_buf.h勤晚。ngx_but_t中比較重要的是pos和last枉层,分別表示要緩沖區(qū)數(shù)據(jù)在內(nèi)存中的起始地址和結(jié)尾地址,這里我們將配置中字符串傳進(jìn)去赐写,last_buf是一個(gè)位域鸟蜡,設(shè)為1表示此緩沖區(qū)是鏈表中最后一個(gè)元素,為0表示后面還有元素挺邀。因?yàn)槲覀冎挥幸唤M數(shù)據(jù)揉忘,所以緩沖區(qū)鏈表中只有一個(gè)節(jié)點(diǎn),如果需要輸入多組數(shù)據(jù)可將各組數(shù)據(jù)放入不同緩沖區(qū)后插入到鏈表端铛。下圖展示了Nginx緩沖鏈表的結(jié)構(gòu):
緩沖數(shù)據(jù)準(zhǔn)備好后泣矛,用ngx_http_output_filter就可以輸出了(會(huì)送到filter進(jìn)行各種過濾處理)。ngx_http_output_filter的第一個(gè)參數(shù)為ngx_http_request_t結(jié)構(gòu)沦补,第二個(gè)為輸出鏈表的起始地址&out乳蓄。ngx_http_out_put_filter會(huì)遍歷鏈表咪橙,輸出所有數(shù)據(jù)夕膀。
以上就是handler的所有工作,請(qǐng)對(duì)照描述理解上面貼出的handler代碼美侦。
組合Nginx Module
上面完成了Nginx模塊各種組件的開發(fā)下面就是將這些組合起來了产舞。一個(gè)Nginx模塊被定義為一個(gè)ngx_module_t結(jié)構(gòu),這個(gè)結(jié)構(gòu)的字段很多菠剩,不過開頭和結(jié)尾若干字段一般可以通過Nginx內(nèi)置的宏去填充易猫,下面是我們echo模塊的模塊主體定義:
ngx_module_t ngx_http_echo_module = {
NGX_MODULE_V1,
&ngx_http_echo_module_ctx, /* module context */
ngx_http_echo_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
};
開頭和結(jié)尾分別用NGX_MODULE_V1和NGX_MODULE_V1_PADDING 填充了若干字段,就不去深究了具壮。這里主要需要填入的信息從上到下以依次為context准颓、指令數(shù)組哈蝇、模塊類型以及若干特定事件的回調(diào)處理函數(shù)(不需要可以置為NULL),其中內(nèi)容還是比較好理解的攘已,注意我們的echo是一個(gè)HTTP模塊炮赦,所以這里類型是NGX_HTTP_MODULE,其它可用類型還有NGX_EVENT_MODULE(事件處理模塊)和NGX_MAIL_MODULE(郵件模塊)样勃。
這樣吠勘,整個(gè)echo模塊就寫好了,下面給出echo模塊的完整代碼:
/*
* Copyright (C) Eric Zhang
*/
#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
/* Module config */
typedef struct {
ngx_str_t ed;
} ngx_http_echo_loc_conf_t;
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
/* Directives */
static ngx_command_t ngx_http_echo_commands[] = {
{ ngx_string("echo"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_echo,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_echo_loc_conf_t, ed),
NULL },
ngx_null_command
};
/* Http context of the module */
static ngx_http_module_t ngx_http_echo_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_echo_create_loc_conf, /* create location configration */
ngx_http_echo_merge_loc_conf /* merge location configration */
};
/* Module */
ngx_module_t ngx_http_echo_module = {
NGX_MODULE_V1,
&ngx_http_echo_module_ctx, /* module context */
ngx_http_echo_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
};
/* Handler function */
static ngx_int_t
ngx_http_echo_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_echo_loc_conf_t *elcf;
elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_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->ed.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->ed.data;
b->last = elcf->ed.data + (elcf->ed.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);
}
static char *
ngx_http_echo(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_echo_handler;
ngx_conf_set_str_slot(cf,cmd,conf);
return NGX_CONF_OK;
}
static void *
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_echo_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
if (conf == NULL) {
return NGX_CONF_ERROR;
}
conf->ed.len = 0;
conf->ed.data = NULL;
return conf;
}
static char *
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_echo_loc_conf_t *prev = parent;
ngx_http_echo_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->ed, prev->ed, "");
return NGX_CONF_OK;
}
Nginx模塊的安裝
Nginx不支持動(dòng)態(tài)鏈接模塊峡眶,所以安裝模塊需要將模塊代碼與Nginx源代碼進(jìn)行重新編譯剧防。安裝模塊的步驟如下:
1、編寫模塊config文件辫樱,這個(gè)文件需要放在和模塊源代碼文件放在同一目錄下峭拘。文件內(nèi)容如下:
ngx_addon_name=模塊完整名稱
HTTP_MODULES="$HTTP_MODULES 模塊完整名稱"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/源代碼文件名"
2、進(jìn)入Nginx源代碼狮暑,使用下面命令編譯安裝
./configure --prefix=安裝目錄 --add-module=模塊源代碼文件目錄
make
make install
這樣就完成安裝了棚唆,例如,我的源代碼文件放在/home/yefeng/ngxdev/ngx_http_echo下心例,我的config文件為:
ngx_addon_name=ngx_http_echo_module
HTTP_MODULES="$HTTP_MODULES ngx_http_echo_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_echo_module.c"
編譯安裝命令為:
./configure --prefix=/usr/local/nginx --add-module=/home/yefeng/ngxdev/ngx_http_echo
make
sudo make install
這樣echo模塊就被安裝在我的Nginx上了宵凌,下面測試一下,修改配置文件止后,增加以下一項(xiàng)配置:
location /echo {
echo "This is my first nginx module!!!";
}
然后用curl測試一下:
curl -i http://localhost/echo
結(jié)果如下:
可以看到模塊已經(jīng)正常工作了瞎惫,也可以在瀏覽器中打開網(wǎng)址,就可以看到結(jié)果:
更深入的學(xué)習(xí)
本文只是簡要介紹了Nginx模塊的開發(fā)過程译株,由于篇幅的原因瓜喇,不能面面俱到。因?yàn)槟壳癗ginx的學(xué)習(xí)資料很少歉糜,如果讀者希望更深入學(xué)習(xí)Nginx的原理及模塊開發(fā)乘寒,那么閱讀源代碼是最好的辦法。在Nginx源代碼的core/下放有Nginx的核心代碼匪补,對(duì)理解Nginx的內(nèi)部工作機(jī)制非常有幫助伞辛,http/目錄下有Nginx HTTP相關(guān)的實(shí)現(xiàn),http/module下放有大量內(nèi)置http模塊夯缺,可供讀者學(xué)習(xí)模塊的開發(fā)蚤氏,另外在http://wiki.nginx.org/3rdPartyModules上有大量優(yōu)秀的第三方模塊,也是非常好的學(xué)習(xí)資料踊兜。
如有意見建議或疑問歡迎發(fā)送郵件至ericzhang.buaa@gmail.com竿滨。希望本文對(duì)您有所幫助!!于游!
參考文獻(xiàn)
[1] Evan Miller, Emiller's Guide To Nginx Module Development. http://www.evanmiller.org/nginx-modules-guide.html, 2009
[2] http://wiki.nginx.org/Configuration
[3] Clément Nedelcu, Nginx Http Server. Packt Publishing, 2010
分享自: codinglabs毁葱。