Nginx是當前最流行的HTTP Server之一,根據W3Techs的統計,目前世界排名(根據Alexa)前100萬的網站中,Nginx的占有率為6.8%。與Apache相比,Nginx在高并發情況下具有巨大的性能優勢。
Nginx屬于典型的微內核設計,其內核非常簡潔和優雅,同時具有非常高的可擴展性。Nginx最初僅僅主要被用于做反向代理,后來隨著HTTP核心的成熟和各種HTTP擴展模塊的豐富,Nginx越來越多被用來取代Apache而單獨承擔HTTP Server的責任,例如目前淘寶內各個部門正越來越多使用Nginx取代Apache,據筆者了解,在騰訊和新浪等公司也存在類似情況。
同時,大量的第三方擴展模塊也令Nginx越來越強大。例如,由淘寶的工程師清無(王曉哲)和春來(章亦春)所開發的nginx_lua_module可以將Lua語言嵌入到Nginx配置中,從而利用Lua極大增強了Nginx本身的編程能力,甚至可以不用配合其它腳本語言(如PHP或Python等),只靠Nginx本身就可以實現復雜業務的處理。而春來所開發的ngx_openresty更是通過集成LuaJIT等組件,將Nginx本身變成了一個完全的應用開發平臺。目前淘寶數據平臺與產品部量子統計的產品都是基于ngx_openresty所開發。對ngxin_lua_module或ngx_openresty感興趣的朋友可以參考我在關鍵詞上給出的鏈接,后續我也可能會寫一些與其有關的文章。
本文將會重點關注Nginx模塊開發入門及基礎。目前Nginx的學習資料非常少,而擴展模塊開發相關的資料幾乎只有《Emiller's Guide To Nginx Module Development》一文,此文十分經典,但是由于Nginx版本的演進,其中少許內容可能有點過時。本文是筆者在研讀這篇文章和Nginx源代碼的基礎上,對自己學習Nginx模塊開發的一個總結。本文將通過一個完整的模塊開發實例講解Nginx模塊開發的入門內容。
本文將基于Nginx最新的1.0.0版本,操作系統環境為Linux(Ubuntu10.10)。
前言
Nginx提要
Nginx在Linux下的安裝與運行
Nginx配置文件基本結構
Nginx模塊工作原理概述
Nginx模塊開發實戰
定義模塊配置結構
定義指令
創建合并配置信息
編寫Handler
組合Nginx Module
Nginx模塊的安裝
Nginx更深入的學習
Nginx參考文獻
Nginx提要
開發Nginx擴展當然首要前提是對Nginx有一定的了解,然而本文并不打算詳細闡述Nginx的方方面面,諸如Nginx的安裝和各種詳細配置等內容都可以在Nginx官網的Document中找到,本文在這里只會概括性描述一些后面可能會用到的原理和概念。
Nginx在Linux下的安裝與運行
使用Nginx的第一步是下載Nginx源碼包,例如1.0.0的下載地址為http://nginx.org/download/nginx-1.0.0.tar.gz。下載完后用tar命令解壓縮,進入目錄后安裝過程與Linux下通常步驟無異,例如我想講Nginx安裝到/usr/local/nginx下,則執行如下命令:
1
./configure --prefix=/usr/local/nginx
2
make
3
make install
安裝完成后可以直接使用下面命令啟動Nginx:
1
/usr/local/nginx/sbin/nginx
Nginx默認以Deamon進程啟動,輸入下列命令:
1
curl -i http://localhost/
就可以檢測Nginx是否已經成功運行。或者也可以在瀏覽器中輸入http://localhost/,應該可以看到Nginx的歡迎頁面了。啟動后如果想停止Nginx可以使用:
1
/usr/local/nginx/sbin/nginx -s stop
Nginx配置文件基本結構
配置文件可以看做是Nginx的靈魂,Nginx服務在啟動時會讀入配置文件,而后續幾乎一切動作行為都是按照配置文件中的指令進行的,因此如果將Nginx本身看做一個計算機,那么Nginx的配置文件可以看成是全部的程序指令。
下面是一個Nginx配置文件的實例:
01
#user nobody;
02
worker_processes 8;
03
error_log logs/error.log;
04
pid logs/nginx.pid;
05
events {
06
worker_connections 1024;
07
}
08
http {
09
include mime.types;
10
default_type application/octet-stream;
11
sendfile on;
12
#tcp_nopush on;
13
keepalive_timeout 65;
14
#gzip on;
15
server {
16
listen 80;
17
server_name localhost;
18
location / {
19
root /home/yefeng/www;
20
index index.html index.htm;
21
}
22
#error_page 404 /404.html;
23
# redirect server error pages to the static page /50x.html
24
#
25
error_page 500 502 503 504 /50x.html;
26
location = /50x.html {
27
root html;
28
}
29
}
30
}
Nginx配置文件是純文本文件,你可以用任何文本編輯器如vim或emacs打開它,通常它會在nginx安裝目錄的conf下,如我的nginx安裝在/usr/local/nginx,主配置文件默認放在/usr/local/nginx/conf/nginx.conf。
其中“#”表示此行是注釋,由于筆者為了學習擴展開發安裝了一個純凈的Nginx,因此配置文件沒有經過太多改動。
Nginx的配置文件是以block的形式組織的,一個block通常使用大括號“{}”表示。block分為幾個層級,整個配置文件為main層級,這是最大的層級;在main層級下可以有event、http等層級,而http中又會有server block,server block中可以包含location block。
每個層級可以有自己的指令(Directive),例如worker_processes是一個main層級指令,它指定Nginx服務的Worker進程數量。有的指令只能在一個層級中配置,如worker_processes只能存在于main中,而有的指令可以存在于多個層級,在這種情況下,子block會繼承父block的配置,同時如果子block配置了與父block不同的指令,則會覆蓋掉父block的配置。指令的格式是“指令名 參數1 參數2 … 參數N;”,注意參數間可用任意數量空格分隔,最后要加分號。
在開發Nginx HTTP擴展模塊過程中,需要特別注意的是main、server和location三個層級,因為擴展模塊通常允許指定新的配置指令在這三個層級中。
最后要提到的是配置文件是可以包含的,如上面配置文件中“include mime.types”就包含了mine.types這個配置文件,此文件指定了各種HTTP Content-type。
一般來說,一個server block表示一個Host,而里面的一個location則代表一個路由映射規則,這兩個block可以說是HTTP配置的核心。
下圖是Nginx配置文件通常結構圖示。
關于Nginx配置的更多內容請參看Nginx官方文檔。
Nginx模塊工作原理概述
(Nginx本身支持多種模塊,如HTTP模塊、EVENT模塊和MAIL模塊,本文只討論HTTP模塊)
Nginx本身做的工作實際很少,當它接到一個HTTP請求時,它僅僅是通過查找配置文件將此次請求映射到一個location block,而此location中所配置的各個指令則會啟動不同的模塊去完成工作,因此模塊可以看做Nginx真正的勞動工作者。通常一個location中的指令會涉及一個handler模塊和多個filter模塊(當然,多個location可以復用同一個模塊)。handler模塊負責處理請求,完成響應內容的生成,而filter模塊對響應內容進行處理。因此Nginx模塊開發分為handler開發和filter開發(本文不考慮load-balancer模塊)。下圖展示了一次常規請求和響應的過程。
Nginx模塊開發實戰
下面本文展示一個簡單的Nginx模塊開發全過程,我們開發一個叫echo的handler模塊,這個模塊功能非常簡單,它接收“echo”指令,指令可指定一個字符串參數,模塊會輸出這個字符串作為HTTP響應。例如,做如下配置:
1
location /echo {
2
echo "hello nginx";
3
}
則訪問http://hostname/echo時會輸出hello nginx。
直觀來看,要實現這個功能需要三步:1、讀入配置文件中echo指令及其參數;2、進行HTTP包裝(添加HTTP頭等工作);3、將結果返回給客戶端。下面本文將分部介紹整個模塊的開發過程。
定義模塊配置結構
首先我們需要一個結構用于存儲從配置文件中讀進來的相關指令參數,即模塊配置信息結構。根據Nginx模塊開發規則,這個結構的命名規則為ngx_http_[module-name]_[main|srv|loc]_conf_t。其中main、srv和loc分別用于表示同一模塊在三層block中的配置信息。這里我們的echo模塊只需要運行在loc層級下,需要存儲一個字符串參數,因此我們可以定義如下的模塊配置:
1
typedef struct {
2
ngx_str_t ed;
3
} ngx_http_echo_loc_conf_t;
其中字段ed用于存儲echo指令指定的需要輸出的字符串。注意這里ed的類型,在Nginx模塊開發中使用ngx_str_t類型表示字符串,這個類型定義在core/ngx_string中:
1
typedef struct {
2
size_t len;
3
u_char *data;
4
} ngx_str_t;
其中兩個字段分別表示字符串的長度和數據起始地址。注意在Nginx源代碼中對數據類型進行了別稱定義,如ngx_int_t為intptr_t的別稱,為了保持一致,在開發Nginx模塊時也應該使用這些Nginx源碼定義的類型而不要使用C原生類型。除了ngx_str_t外,其它三個常用的nginx type分別為:
1
typedef intptr_t ngx_int_t;
2
typedef uintptr_t ngx_uint_t;
3
typedef intptr_t ngx_flag_t;
具體定義請參看core/ngx_config.h。關于intptr_t和uintptr_t請參考C99中的stdint.h或http://linux.die.net/man/3/intptr_t。
定義指令
一個Nginx模塊往往接收一至多個指令,echo模塊接收一個指令“echo”。Nginx模塊使用一個ngx_command_t數組表示模塊所能接收的所有模塊,其中每一個元素表示一個條指令。ngx_command_t是ngx_command_s的一個別稱(Nginx習慣于使用“_s”后綴命名結構體,然后typedef一個同名“_t”后綴名稱作為此結構體的類型名),ngx_command_s定義在core/ngx_config_file.h中:
1
struct ngx_command_s {
2
ngx_str_t name;
3
ngx_uint_t type;
4
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
5
ngx_uint_t conf;
6
ngx_uint_t offset;
7
void *post;
8
};
其中name是詞條指令的名稱,type使用掩碼標志位方式配置指令參數,相關可用type定義在core/ngx_config_file.h中:
01
#define NGX_CONF_NOARGS 0x00000001
02
#define NGX_CONF_TAKE1 0x00000002
03
#define NGX_CONF_TAKE2 0x00000004
04
#define NGX_CONF_TAKE3 0x00000008
05
#define NGX_CONF_TAKE4 0x00000010
06
#define NGX_CONF_TAKE5 0x00000020
07
#define NGX_CONF_TAKE6 0x00000040
08
#define NGX_CONF_TAKE7 0x00000080
09
10
#define NGX_CONF_MAX_ARGS 8
11
12
#define NGX_CONF_TAKE12 (NGX_CONF_TAKE1|NGX_CONF_TAKE2)
13
#define NGX_CONF_TAKE13 (NGX_CONF_TAKE1|NGX_CONF_TAKE3)
14
15
#define NGX_CONF_TAKE23 (NGX_CONF_TAKE2|NGX_CONF_TAKE3)
16
17
#define NGX_CONF_TAKE123 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)
18
#define NGX_CONF_TAKE1234 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3 \
19
|NGX_CONF_TAKE4)
20
21
#define NGX_CONF_ARGS_NUMBER 0x000000ff
22
#define NGX_CONF_BLOCK 0x00000100
23
#define NGX_CONF_FLAG 0x00000200
24
#define NGX_CONF_ANY 0x00000400
25
#define NGX_CONF_1MORE 0x00000800
26
#define NGX_CONF_2MORE 0x00001000
27
#define NGX_CONF_MULTI 0x00002000
其中NGX_CONF_NOARGS表示此指令不接受參數,NGX_CON F_TAKE1-7表示精確接收1-7個,NGX_CONF_TAKE12表示接受1或2個參數,NGX_CONF_1MORE表示至少一個參數,NGX_CONF_FLAG表示接受“on|off”……
set是一個函數指針,用于指定一個參數轉化函數,這個函數一般是將配置文件中相關指令的參數轉化成需要的格式并存入配置結構體。Nginx預定義了一些轉換函數,可以方便我們調用,這些函數定義在core/ngx_conf_file.h中,一般以“_slot”結尾,例如ngx_conf_set_flag_slot將“on或off”轉換為“1或0”,再如ngx_conf_set_str_slot將裸字符串轉化為ngx_str_t。
conf用于指定Nginx相應配置文件內存其實地址,一般可以通過內置常量指定,如NGX_HTTP_LOC_CONF_OFFSET,offset指定此條指令的參數的偏移量。
下面是echo模塊的指令定義:
01
static ngx_command_t ngx_http_echo_commands[] = {
02
{ ngx_string("echo"),
03
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
04
ngx_http_echo,
05
NGX_HTTP_LOC_CONF_OFFSET,
06
offsetof(ngx_http_echo_loc_conf_t, ed),
07
NULL },
08
09
ngx_null_command
10
};
指令數組的命名規則為ngx_http_[module-name]_commands,注意數組最后一個元素要是ngx_null_command結束。
參數轉化函數的代碼為:
01
static char *
02
ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
03
{
04
ngx_http_core_loc_conf_t *clcf;
05
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
06
clcf->handler = ngx_http_echo_handler;
07
ngx_conf_set_str_slot(cf,cmd,conf);
08
09
return NGX_CONF_OK;
10
}
這個函數除了調用ngx_conf_set_str_slot轉化echo指令的參數外,還將修改了核心模塊配置(也就是這個location的配置),將其handler替換為我們編寫的handler:ngx_http_echo_handler。這樣就屏蔽了此location的默認handler,使用ngx_http_echo_handler產生HTTP響應。
創建合并配置信息
下一步是定義模塊Context。
這里首先需要定義一個ngx_http_module_t類型的結構體變量,命名規則為ngx_http_[module-name]_module_ctx,這個結構主要用于定義各個Hook函數。下面是echo模塊的context結構:
01
static ngx_http_module_t ngx_http_echo_module_ctx = {
02
NULL, /* preconfiguration */
03
NULL, /* postconfiguration */
04
05
NULL, /* create main configuration */
06
NULL, /* init main configuration */
07
08
NULL, /* create server configuration */
09
NULL, /* merge server configuration */
10
11
ngx_http_echo_create_loc_conf, /* create location configration */
12
ngx_http_echo_merge_loc_conf /* merge location configration */
13
};
可以看到一共有8個Hook注入點,分別會在不同時刻被Nginx調用,由于我們的模塊僅僅用于location域,這里將不需要的注入點設為NULL即可。其中create_loc_conf用于初始化一個配置結構體,如為配置結構體分配內存等工作;merge_loc_conf用于將其父block的配置信息合并到此結構體中,也就是實現配置的繼承。這兩個函數會被Nginx自動調用。注意這里的命名規則:ngx_http_[module-name]_[create|merge]_[main|srv|loc]_conf。
下面是echo模塊這個兩個函數的代碼:
01
static void *
02
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
03
{
04
ngx_http_echo_loc_conf_t *conf;
05
06
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
07
if (conf == NULL) {
08
return NGX_CONF_ERROR;
09
}
10
conf->ed.len = 0;
11
conf->ed.data = NULL;
12
13
return conf;
14
}
15
16
static char *
17
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
18
{
19
ngx_http_echo_loc_conf_t *prev = parent;
20
ngx_http_echo_loc_conf_t *conf = child;
21
22
ngx_conf_merge_str_value(conf->ed, prev->ed, "");
23
24
return NGX_CONF_OK;
25
}
其中ngx_pcalloc用于在Nginx內存池中分配一塊空間,是pcalloc的一個包裝。使用ngx_pcalloc分配的內存空間不必手工free,Nginx會自行管理,在適當是否釋放。
create_loc_conf新建一個ngx_http_echo_loc_conf_t,分配內存,并初始化其中的數據,然后返回這個結構的指針;merge_loc_conf將父block域的配置信息合并到create_loc_conf新建的配置結構體中。
其中ngx_conf_merge_str_value不是一個函數,而是一個宏,其定義在core/ngx_conf_file.h中:
01
#define ngx_conf_merge_str_value(conf, prev, default) \
02
if (conf.data == NULL) { \
03
if (prev.data) { \
04
conf.len = prev.len; \
05
conf.data = prev.data; \
06
} else { \
07
conf.len = sizeof(default) - 1; \
08
conf.data = (u_char *) default; \
09
} \
10
}
同時可以看到,core/ngx_conf_file.h還定義了很多merge value的宏用于merge各種數據。它們的行為比較相似:使用prev填充conf,如果prev的數據為空則使用default填充。
編寫Handler
下面的工作是編寫handler。handler可以說是模塊中真正干活的代碼,它主要有以下四項職責:
讀入模塊配置。
處理功能業務。
產生HTTP header。
產生HTTP body。
下面先貼出echo模塊的代碼,然后通過分析代碼的方式介紹如何實現這四步。這一塊的代碼比較復雜:
01
static ngx_int_t
02
ngx_http_echo_handler(ngx_http_request_t *r)
03
{
04
ngx_int_t rc;
05
ngx_buf_t *b;
06
ngx_chain_t out;
07
08
ngx_http_echo_loc_conf_t *elcf;
09
elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
10
11
if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
12
{
13
return NGX_HTTP_NOT_ALLOWED;
14
}
15
16
r->headers_out.content_type.len = sizeof("text/html") - 1;
17
r->headers_out.content_type.data = (u_char *) "text/html";
18
19
r->headers_out.status = NGX_HTTP_OK;
20
r->headers_out.content_length_n = elcf->ed.len;
21
22
if(r->method == NGX_HTTP_HEAD)
23
{
24
rc = ngx_http_send_header(r);
25
if(rc != NGX_OK)
26
{
27
return rc;
28
}
29
}
30
31
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
32
if(b == NULL)
33
{
34
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
35
return NGX_HTTP_INTERNAL_SERVER_ERROR;
36
}
37
out.buf = b;
38
out.next = NULL;
39
40
b->pos = elcf->ed.data;
41
b->last = elcf->ed.data + (elcf->ed.len);
42
b->memory = 1;
43
b->last_buf = 1;
44
rc = ngx_http_send_header(r);
45
if(rc != NGX_OK)
46
{
47
return rc;
48
}
49
return ngx_http_output_filter(r, &out);
50
}
handler會接收一個ngx_http_request_t指針類型的參數,這個參數指向一個ngx_http_request_t結構體,此結構體存儲了這次HTTP請求的一些信息,這個結構定義在http/ngx_http_request.h中:
01
struct ngx_http_request_s {
02
uint32_t signature; /* "HTTP" */
03
04
ngx_connection_t *connection;
05
06
void **ctx;
07
void **main_conf;
08
void **srv_conf;
09
void **loc_conf;
10
11
ngx_http_event_handler_pt read_event_handler;
12
ngx_http_event_handler_pt write_event_handler;
13
14
#if (NGX_HTTP_CACHE)
15
ngx_http_cache_t *cache;
16
#endif
17
18
ngx_http_upstream_t *upstream;
19
ngx_array_t *upstream_states;
20
/* of ngx_http_upstream_state_t */
21
22
ngx_pool_t *pool;
23
ngx_buf_t *header_in;
24
25
ngx_http_headers_in_t headers_in;
26
ngx_http_headers_out_t headers_out;
27
28
ngx_http_request_body_t *request_body;
29
30
time_t lingering_time;
31
time_t start_sec;
32
ngx_msec_t start_msec;
33
34
ngx_uint_t method;
35
ngx_uint_t http_version;
36
37
ngx_str_t request_line;
38
ngx_str_t uri;
39
ngx_str_t args;
40
ngx_str_t exten;
41
ngx_str_t unparsed_uri;
42
43
ngx_str_t method_name;
44
ngx_str_t http_protocol;
45
46
ngx_chain_t *out;
47
ngx_http_request_t *main;
48
ngx_http_request_t *parent;
49
ngx_http_postponed_request_t *postponed;
50
ngx_http_post_subrequest_t *post_subrequest;
51
ngx_http_posted_request_t *posted_requests;
52
53
ngx_http_virtual_names_t *virtual_names;
54
55
ngx_int_t phase_handler;
56
ngx_http_handler_pt content_handler;
57
ngx_uint_t access_code;
58
59
ngx_http_variable_value_t *variables;
60
61
/* ... */
62
}
由于ngx_http_request_s定義比較長,這里我只截取了一部分。可以看到里面有諸如uri,args和request_body等HTTP常用信息。這里需要特別注意的幾個字段是headers_in、headers_out和chain,它們分別表示request header、response header和輸出數據緩沖區鏈表(緩沖區鏈表是Nginx I/O中的重要內容,后面會單獨介紹)。
第一步是獲取模塊配置信息,這一塊只要簡單使用ngx_http_get_module_loc_conf就可以了。
第二步是功能邏輯,因為echo模塊非常簡單,只是簡單輸出一個字符串,所以這里沒有功能邏輯代碼。
第三步是設置response header。Header內容可以通過填充headers_out實現,我們這里只設置了Content-type和Content-length等基本內容,ngx_http_headers_out_t定義了所有可以設置的HTTP Response Header信息:
01
typedef struct {
02
ngx_list_t headers;
03
04
ngx_uint_t status;
05
ngx_str_t status_line;
06
07
ngx_table_elt_t *server;
08
ngx_table_elt_t *date;
09
ngx_table_elt_t *content_length;
10
ngx_table_elt_t *content_encoding;
11
ngx_table_elt_t *location;
12
ngx_table_elt_t *refresh;
13
ngx_table_elt_t *last_modified;
14
ngx_table_elt_t *content_range;
15
ngx_table_elt_t *accept_ranges;
16
ngx_table_elt_t *www_authenticate;
17
ngx_table_elt_t *expires;
18
ngx_table_elt_t *etag;
19
20
ngx_str_t *override_charset;
21
22
size_t content_type_len;
23
ngx_str_t content_type;
24
ngx_str_t charset;
25
u_char *content_type_lowcase;
26
ngx_uint_t content_type_hash;
27
28
ngx_array_t cache_control;
29
30
off_t content_length_n;
31
time_t date_time;
32
time_t last_modified_time;
33
} ngx_http_headers_out_t;
這里并不包含所有HTTP頭信息,如果需要可以使用agentzh(春來)開發的Nginx模塊HttpHeadersMore在指令中指定更多的Header頭信息。
設置好頭信息后使用ngx_http_send_header就可以將頭信息輸出,ngx_http_send_header接受一個ngx_http_request_t類型的參數。
第四步也是最重要的一步是輸出Response body。這里首先要了解Nginx的I/O機制,Nginx允許handler一次產生一組輸出,可以產生多次,Nginx將輸出組織成一個單鏈表結構,鏈表中的每個節點是一個chain_t,定義在core/ngx_buf.h:
1
struct ngx_chain_s {
2
ngx_buf_t *buf;
3
ngx_chain_t *next;
4
};
其中ngx_chain_t是ngx_chain_s的別名,buf為某個數據緩沖區的指針,next指向下一個鏈表節點,可以看到這是一個非常簡單的鏈表。ngx_buf_t的定義比較長而且很復雜,這里就不貼出來了,請自行參考core/ngx_buf.h。ngx_but_t中比較重要的是pos和last,分別表示要緩沖區數據在內存中的起始地址和結尾地址,這里我們將配置中字符串傳進去,last_buf是一個位域,設為1表示此緩沖區是鏈表中最后一個元素,為0表示后面還有元素。因為我們只有一組數據,所以緩沖區鏈表中只有一個節點,如果需要輸入多組數據可將各組數據放入不同緩沖區后插入到鏈表。下圖展示了Nginx緩沖鏈表的結構:
緩沖數據準備好后,用ngx_http_output_filter就可以輸出了(會送到filter進行各種過濾處理)。ngx_http_output_filter的第一個參數為ngx_http_request_t結構,第二個為輸出鏈表的起始地址&out。ngx_http_out_put_filter會遍歷鏈表,輸出所有數據。
以上就是handler的所有工作,請對照描述理解上面貼出的handler代碼。
組合Nginx Module
上面完成了Nginx模塊各種組件的開發下面就是將這些組合起來了。一個Nginx模塊被定義為一個ngx_module_t結構,這個結構的字段很多,不過開頭和結尾若干字段一般可以通過Nginx內置的宏去填充,下面是我們echo模塊的模塊主體定義:
01
ngx_module_t ngx_http_echo_module = {
02
NGX_MODULE_V1,
03
&ngx_http_echo_module_ctx, /* module context */
04
ngx_http_echo_commands, /* module directives */
05
NGX_HTTP_MODULE, /* module type */
06
NULL, /* init master */
07
NULL, /* init module */
08
NULL, /* init process */
09
NULL, /* init thread */
10
NULL, /* exit thread */
11
NULL, /* exit process */
12
NULL, /* exit master */
13
NGX_MODULE_V1_PADDING
14
};
開頭和結尾分別用NGX_MODULE_V1和NGX_MODULE_V1_PADDING 填充了若干字段,就不去深究了。這里主要需要填入的信息從上到下以依次為context、指令數組、模塊類型以及若干特定事件的回調處理函數(不需要可以置為NULL),其中內容還是比較好理解的,注意我們的echo是一個HTTP模塊,所以這里類型是NGX_HTTP_MODULE,其它可用類型還有NGX_EVENT_MODULE(事件處理模塊)和NGX_MAIL_MODULE(郵件模塊)。
這樣,整個echo模塊就寫好了,下面給出echo模塊的完整代碼:
001
/*
002
* Copyright (C) Eric Zhang
003
*/
004
005
#include <ngx_config.h>
006
#include <ngx_core.h>
007
#include <ngx_http.h>
008
009
/* Module config */
010
typedef struct {
011
ngx_str_t ed;
012
} ngx_http_echo_loc_conf_t;
013
014
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
015
static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
016
static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
017
018
/* Directives */
019
static ngx_command_t ngx_http_echo_commands[] = {
020
{ ngx_string("echo"),
021
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
022
ngx_http_echo,
023
NGX_HTTP_LOC_CONF_OFFSET,
024
offsetof(ngx_http_echo_loc_conf_t, ed),
025
NULL },
026
027
ngx_null_command
028
};
029
030
/* Http context of the module */
031
static ngx_http_module_t ngx_http_echo_module_ctx = {
032
NULL, /* preconfiguration */
033
NULL, /* postconfiguration */
034
035
NULL, /* create main configuration */
036
NULL, /* init main configuration */
037
038
NULL, /* create server configuration */
039
NULL, /* merge server configuration */
040
041
ngx_http_echo_create_loc_conf, /* create location configration */
042
ngx_http_echo_merge_loc_conf /* merge location configration */
043
};
044
045
/* Module */
046
ngx_module_t ngx_http_echo_module = {
047
NGX_MODULE_V1,
048
&ngx_http_echo_module_ctx, /* module context */
049
ngx_http_echo_commands, /* module directives */
050
NGX_HTTP_MODULE, /* module type */
051
NULL, /* init master */
052
NULL, /* init module */
053
NULL, /* init process */
054
NULL, /* init thread */
055
NULL, /* exit thread */
056
NULL, /* exit process */
057
NULL, /* exit master */
058
NGX_MODULE_V1_PADDING
059
};
060
061
/* Handler function */
062
static ngx_int_t
063
ngx_http_echo_handler(ngx_http_request_t *r)
064
{
065
ngx_int_t rc;
066
ngx_buf_t *b;
067
ngx_chain_t out;
068
069
ngx_http_echo_loc_conf_t *elcf;
070
elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
071
072
if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
073
{
074
return NGX_HTTP_NOT_ALLOWED;
075
}
076
077
r->headers_out.content_type.len = sizeof("text/html") - 1;
078
r->headers_out.content_type.data = (u_char *) "text/html";
079
080
r->headers_out.status = NGX_HTTP_OK;
081
r->headers_out.content_length_n = elcf->ed.len;
082
083
if(r->method == NGX_HTTP_HEAD)
084
{
085
rc = ngx_http_send_header(r);
086
if(rc != NGX_OK)
087
{
088
return rc;
089
}
090
}
091
092
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
093
if(b == NULL)
094
{
095
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
096
return NGX_HTTP_INTERNAL_SERVER_ERROR;
097
}
098
out.buf = b;
099
out.next = NULL;
100
101
b->pos = elcf->ed.data;
102
b->last = elcf->ed.data + (elcf->ed.len);
103
b->memory = 1;
104
b->last_buf = 1;
105
rc = ngx_http_send_header(r);
106
if(rc != NGX_OK)
107
{
108
return rc;
109
}
110
return ngx_http_output_filter(r, &out);
111
}
112
113
static char *
114
ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
115
{
116
ngx_http_core_loc_conf_t *clcf;
117
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
118
clcf->handler = ngx_http_echo_handler;
119
ngx_conf_set_str_slot(cf,cmd,conf);
120
121
return NGX_CONF_OK;
122
}
123
124
static void *
125
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
126
{
127
ngx_http_echo_loc_conf_t *conf;
128
129
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
130
if (conf == NULL) {
131
return NGX_CONF_ERROR;
132
}
133
conf->ed.len = 0;
134
conf->ed.data = NULL;
135
136
return conf;
137
}
138
139
static char *
140
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
141
{
142
ngx_http_echo_loc_conf_t *prev = parent;
143
ngx_http_echo_loc_conf_t *conf = child;
144
145
ngx_conf_merge_str_value(conf->ed, prev->ed, "");
146
147
return NGX_CONF_OK;
148
}
Nginx模塊的安裝
Nginx不支持動態鏈接模塊,所以安裝模塊需要將模塊代碼與Nginx源代碼進行重新編譯。安裝模塊的步驟如下:
1、編寫模塊config文件,這個文件需要放在和模塊源代碼文件放在同一目錄下。文件內容如下:
1
ngx_addon_name=模塊完整名稱
2
HTTP_MODULES="$HTTP_MODULES 模塊完整名稱"
3
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/源代碼文件名"
2、進入Nginx源代碼,使用下面命令編譯安裝
1
./configure --prefix=安裝目錄 --add-module=模塊源代碼文件目錄
2
make
3
make install
這樣就完成安裝了,例如,我的源代碼文件放在/home/yefeng/ngxdev/ngx_http_echo下,我的config文件為:
1
ngx_addon_name=ngx_http_echo_module
2
HTTP_MODULES="$HTTP_MODULES ngx_http_echo_module"
3
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_echo_module.c"
編譯安裝命令為:
1
./configure --prefix=/usr/local/nginx --add-module=/home/yefeng/ngxdev/ngx_http_echo
2
make
3
sudo make install
這樣echo模塊就被安裝在我的Nginx上了,下面測試一下,修改配置文件,增加以下一項配置:
1
location /echo {
2
echo "This is my first nginx module!!!";
3
}
然后用curl測試一下:
1
curl -i http://localhost/echo
結果如下:
可以看到模塊已經正常工作了,也可以在瀏覽器中打開網址,就可以看到結果:
更深入的學習
本文只是簡要介紹了Nginx模塊的開發過程,由于篇幅的原因,不能面面俱到。因為目前Nginx的學習資料很少,如果讀者希望更深入學習Nginx的原理及模塊開發,那么閱讀源代碼是最好的辦法。在Nginx源代碼的core/下放有Nginx的核心代碼,對理解Nginx的內部工作機制非常有幫助,http/目錄下有Nginx HTTP相關的實現,http/module下放有大量內置http模塊,可供讀者學習模塊的開發,另外在http://wiki.nginx.org/3rdPartyModules上有大量優秀的第三方模塊,也是非常好的學習資料。
如有意見建議或疑問歡迎發送郵件至ericzhang.buaa@gmail.com。希望本文對您有所幫助!!!
參考文獻
[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
本文基于署名-非商業性使用 3.0許可協議發布,歡迎轉載,演繹,但是必須保留本文的署名張洋(包含鏈接),且不得用于商業目的。如您有任何疑問或者授權方面的協商,請與我聯系。
分類: [06]unix&linux
標簽: nginx, http, http server
綠色通道:好文要頂關注我收藏該文與我聯系
T2噬菌體
關注 - 13
粉絲 - 534
榮譽:微軟社區精英,推薦博客
+加關注
20 0
(請您對文章做出評價)
? 上一篇:程序設計中的計算復用(Computational Reuse)
? 下一篇:使用SeaJS實現模塊化JavaScript開發
Categories: [06]unix&linux
Tags: nginx, http, http server
ADD YOUR COMMENT
32 條回復
#1樓 賀臣
2011-04-19 13:19
恩 不錯。大哥最好寫出一系列文章來,這個對于做互聯網的來說非常實用
回復 引用 查看
#2樓 12ddae4[未注冊用戶]
2011-04-19 13:35
終于更新了...博主涉及的東西真多..膜拜ing
回復 引用
#3樓 joylee
2011-04-19 16:04
最近幾天正好在研究這個,使用很簡單,配置方便,功能強大的好東西。
回復 引用 查看
#4樓 yjf512
2011-04-19 17:38
雖然沒有研究這個,但是早聽過nginx的大名~
回復 引用 查看
#5樓 Lanisle
2011-04-19 17:59
T2的文章總是老長老長精雕細琢的,Mark留位再看內容!
回復 引用 查看
#6樓 假正經哥哥
2011-04-19 19:53
先mark著,后面再看
回復 引用 查看
#7樓 testzhangsan
2011-04-19 20:44
高材生又發博了,不發則已,一發驚人!
回復 引用 查看
#8樓 guoqiao
2011-04-20 08:46
不錯
回復 引用 查看
#9樓 李錫遠
2011-04-20 09:27
這個確實不錯,靈活方便又強大!
回復 引用 查看
#10樓 Leepy
2011-04-20 10:58
非常好,拜讀下!最近也在研究Nginx
回復 引用 查看
#11樓 s3
2011-04-20 11:48
贊!
可不可以寫一些對客戶端進行驗證的,
我是想做一個驗證模塊(具體驗證信息是在后端的PHP系統內),
比如:用戶1,向 nginx請求,nginx接到請求后,驗證用戶1是否有權限(權限驗證可以存放在nginx中),驗證后跳轉到后端的PHP系統中。
ngx_http_auth_pam_module模塊可以實現,不過這個模塊沒有與后端的PHP系統進行交互。
回復 引用 查看
#12樓 Likwo
2011-04-20 12:37
雖然現在用不到,留著,收藏下,以后看。
比較喜歡博住把導航也寫上,方便找!
回復 引用 查看
#13樓 GUO Xingwang
2011-04-20 13:30
@s3
那就改改這個模塊唄
回復 引用 查看
#14樓 s3
2011-04-20 13:32
@GUO Xingwang
nginx內置的變量不清楚,數據跟蹤調試不方便。
有沒比較方便的數據跟蹤調整(比如咋個把變量數據輸出到日志文件內)
回復 引用 查看
#15樓[樓主] T2噬菌體
2011-04-20 13:37
@s3
你說的功能,春來的ngx_lua模塊支持access_by_lua可以用lua實現acl,不過PHP的暫時沒有模塊支持,你的想法挺不錯,等我有時間可能會實踐一下。另外吧變量輸出到日志用ngx_log_error就可以了,可以用DEBUG級別。
回復 引用 查看
#16樓 嘉興網站推廣[未注冊用戶]
2011-04-20 16:02
雖然知道博主分享的是好東西,可以還是看的有點暈。在多多學習中
回復 引用
#17樓 護膚品品牌排行榜[未注冊用戶]
2011-04-20 16:02
感謝博主熱心分享。
回復 引用
#18樓 鍋爐軟化水設備[未注冊用戶]
2011-04-20 16:03
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
015 static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
016 static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
這一段不是很明白?
回復 引用
#19樓 小狗吸塵器[未注冊用戶]
2011-04-20 16:04
我跟15樓的朋友一樣的問題、。
回復 引用
#20樓 s3
2011-04-20 17:57
@T2噬菌體
那我先了解下,以后就關注你了,這個問題難了我半年了,沒解決。
多交流!
回復 引用 查看
#21樓 tb-hz[未注冊用戶]
2011-04-22 15:58
很好很強大。頂起來。
回復 引用
#22樓 紫竹郎
2011-04-24 21:14
perfix=/usr/local/nginx
這里應該為prefix 一點小錯誤。
回復 引用 查看
#23樓[樓主] T2噬菌體
2011-04-24 22:37
@紫竹郎
改了,謝謝。
回復 引用 查看
#24樓 yangjun
2011-04-25 09:48
cool,先收藏,有機會用得上再參考,謝謝。話說,您的文章大部分對我都很幫助,再次感謝。
回復 引用 查看
#25樓 Leo
2011-04-25 10:23
文章真長!
回復 引用 查看
#26樓 Quains
2011-04-26 22:04
從樓主的各個系列的文章中學到很多東西,很多都是剛好感興趣的方面.thx
回復 引用 查看
#27樓 banana.totolv
2011-05-05 19:14
good
回復 引用 查看
#28樓 Raj23[未注冊用戶]
2011-05-12 12:46
文字寫得很好,學習大大了。圖也畫的很準確,而且美觀
請問LZ用什么工具畫那幾張結構圖
回復 引用
#29樓 liuhh
2011-05-14 15:50
呵呵,關注先。。。一直在聽說Nginx大名
回復 引用 查看
#30樓 Jonathan.yang
2011-05-21 11:04
看樓主的文章很過隱啊
回復 引用 查看
#31樓 heyjude[未注冊用戶]
2011-06-26 11:50
在什么場景下要用這個,這個又麻煩又不方便測試,一般情況下用php、java、python、ruby等加負載均衡不就夠了嗎?
回復 引用
#32樓[樓主] T2噬菌體
2011-06-26 14:04
引用
heyjude:在什么場景下要用這個,這個又麻煩又不方便測試,一般情況下用php、java、python、ruby等加負載均衡不就夠了嗎?
你的問題很難一言兩語回答清楚,總之需要用到擴展nginx的地方還是很多的。另外推薦個東西看看吧:openresty.org
回復 引用 查看