Nginx屬于典型的微核心設計,其核心非常簡潔和優雅,同時具有非常高的可擴充性。Nginx最初僅僅主要被用于做反向代理,後來随着HTTP核心的成熟和各種HTTP擴充子產品的豐富,Nginx越來越多被用來取代Apache而單獨承擔HTTP Server的責任,例如目前淘寶内各個部門正越來越多使用Nginx取代Apache,據筆者了解,在騰訊和新浪等公司也存在類似情況。
開發Nginx擴充當然首要前提是對Nginx有一定的了解,然而本文并不打算詳細闡述Nginx的方方面面,諸如Nginx的安裝和各種詳細配置等内容都可以在Nginx官網的Document中找到,本文在這裡隻會概括性描述一些後面可能會用到的原理和概念。
1
2
3
<code>.</code><code>/configure</code>
<code>--prefix=</code><code>/usr/local/nginx</code>
<code>make</code>
<code>install</code>
安裝完成後可以直接使用下面指令啟動Nginx:
<code>/usr/local/nginx/sbin/nginx</code>
Nginx預設以Deamon程序啟動,輸入下列指令:
<code>curl -i http:</code><code>//localhost/</code>
<code>-s stop</code>
配置檔案可以看做是Nginx的靈魂,Nginx服務在啟動時會讀入配置檔案,而後續幾乎一切動作行為都是按照配置檔案中的指令進行的,是以如果将Nginx本身看做一個計算機,那麼Nginx的配置檔案可以看成是全部的程式指令。
下面是一個Nginx配置檔案的執行個體:
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<code>#user nobody;</code>
<code>worker_processes 8;</code>
<code>error_log logs</code><code>/error</code><code>.log;</code>
<code>pid logs</code><code>/nginx</code><code>.pid;</code>
<code>events {</code>
<code> </code><code>worker_connections 1024;</code>
<code>}</code>
<code>http {</code>
<code> </code><code>include mime.types;</code>
<code> </code><code>default_type application</code><code>/octet-stream</code><code>;</code>
<code> </code><code>sendfile on;</code>
<code> </code><code>#tcp_nopush on;</code>
<code> </code><code>keepalive_timeout 65;</code>
<code> </code><code>#gzip on;</code>
<code> </code><code>server {</code>
<code> </code><code>listen 80;</code>
<code> </code><code>server_name localhost;</code>
<code> </code><code>location / {</code>
<code> </code><code>root </code><code>/home/yefeng/www</code><code>;</code>
<code> </code><code>index index.html index.htm;</code>
<code> </code><code>}</code>
<code> </code><code>#error_page 404 /404.html;</code>
<code> </code><code># redirect server error pages to the static page /50x.html</code>
<code> </code><code>#</code>
<code> </code><code>error_page 500 502 503 504 </code><code>/50x</code><code>.html;</code>
<code> </code><code>location =</code><code>/50x</code><code>.html {</code>
<code> </code><code>root html;</code>
<code> </code><code>}</code>
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配置檔案通常結構圖示。
<a href="http://www.codinglabs.org/wp-content/uploads/2011/10/image.png"></a>
關于Nginx配置的更多内容請參看Nginx官方文檔。
(Nginx本身支援多種子產品,如HTTP子產品、EVENT子產品和MAIL子產品,本文隻讨論HTTP子產品)
Nginx本身做的工作實際很少,當它接到一個HTTP請求時,它僅僅是通過查找配置檔案将此次請求映射到一個location block,而此location中所配置的各個指令則會啟動不同的子產品去完成工作,是以子產品可以看做Nginx真正的勞動工作者。通常一個location中的指令會涉及一個handler子產品和多個filter子產品(當然,多個location可以複用同一個子產品)。handler子產品負責處理請求,完成響應内容的生成,而filter子產品對響應内容進行處理。是以Nginx子產品開發分為handler開發和filter開發(本文不考慮load-balancer子產品)。下圖展示了一次正常請求和響應的過程。
<a href="http://www.codinglabs.org/wp-content/uploads/2011/10/110.png"></a>
下面本文展示一個簡單的Nginx子產品開發全過程,我們開發一個叫echo的handler子產品,這個子產品功能非常簡單,它接收“echo”指令,指令可指定一個字元串參數,子產品會輸出這個字元串作為HTTP響應。例如,做如下配置:
<code>location</code><code>/echo</code>
<code>{</code>
<code> </code><code>echo</code>
<code>"hello nginx"</code><code>;</code>
直覺來看,要實作這個功能需要三步:1、讀入配置檔案中echo指令及其參數;2、進行HTTP包裝(添加HTTP頭等工作);3、将結果傳回給用戶端。下面本文将分部介紹整個子產品的開發過程。
首先我們需要一個結構用于存儲從配置檔案中讀進來的相關指令參數,即子產品配置資訊結構。根據Nginx子產品開發規則,這個結構的命名規則為ngx_http_[module-name]_[main|srv|loc]_conf_t。其中main、srv和loc分别用于表示同一子產品在三層block中的配置資訊。這裡我們的echo子產品隻需要運作在loc層級下,需要存儲一個字元串參數,是以我們可以定義如下的子產品配置:
<code>typedef</code>
<code>struct</code> <code>{</code>
<code> </code><code>ngx_str_t ed;</code>
<code>} ngx_http_echo_loc_conf_t;</code>
其中字段ed用于存儲echo指令指定的需要輸出的字元串。注意這裡ed的類型,在Nginx子產品開發中使用ngx_str_t類型表示字元串,這個類型定義在core/ngx_string中:
<code> </code><code>size_t</code>
<code>len;</code>
<code> </code><code>u_char *data;</code>
<code>} ngx_str_t;</code>
其中兩個字段分别表示字元串的長度和資料起始位址。注意在Nginx源代碼中對資料類型進行了别稱定義,如ngx_int_t為intptr_t的别稱,為了保持一緻,在開發Nginx子產品時也應該使用這些Nginx源碼定義的類型而不要使用C原生類型。除了ngx_str_t外,其它三個常用的nginx type分别為:
<code>intptr_t</code> <code>ngx_int_t;</code>
<code>uintptr_t</code> <code>ngx_uint_t;</code>
<code>intptr_t</code> <code>ngx_flag_t;</code>
一個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中:
<code>struct</code>
<code>ngx_command_s {</code>
<code> </code><code>ngx_str_t name;</code>
<code> </code><code>ngx_uint_t type;</code>
<code> </code><code>char</code>
<code>*(*set)(ngx_conf_t *cf, ngx_command_t *cmd,</code><code>void</code>
<code>*conf);</code>
<code> </code><code>ngx_uint_t conf;</code>
<code> </code><code>ngx_uint_t offset;</code>
<code> </code><code>void</code>
<code>*post;</code>
<code>};</code>
其中name是詞條指令的名稱,type使用掩碼标志位方式配置指令參數,相關可用type定義在core/ngx_config_file.h中:
<code>#define NGX_CONF_NOARGS 0x00000001</code>
<code>#define NGX_CONF_TAKE1 0x00000002</code>
<code>#define NGX_CONF_TAKE2 0x00000004</code>
<code>#define NGX_CONF_TAKE3 0x00000008</code>
<code>#define NGX_CONF_TAKE4 0x00000010</code>
<code>#define NGX_CONF_TAKE5 0x00000020</code>
<code>#define NGX_CONF_TAKE6 0x00000040</code>
<code>#define NGX_CONF_TAKE7 0x00000080</code>
<code>#define NGX_CONF_MAX_ARGS 8</code>
<code>#define NGX_CONF_TAKE12 (NGX_CONF_TAKE1|NGX_CONF_TAKE2)</code>
<code>#define NGX_CONF_TAKE13 (NGX_CONF_TAKE1|NGX_CONF_TAKE3)</code>
<code>#define NGX_CONF_TAKE23 (NGX_CONF_TAKE2|NGX_CONF_TAKE3)</code>
<code>#define NGX_CONF_TAKE123 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3)</code>
<code>#define NGX_CONF_TAKE1234 (NGX_CONF_TAKE1|NGX_CONF_TAKE2|NGX_CONF_TAKE3 \</code>
<code> </code><code>|NGX_CONF_TAKE4)</code>
<code>#define NGX_CONF_ARGS_NUMBER 0x000000ff</code>
<code>#define NGX_CONF_BLOCK 0x00000100</code>
<code>#define NGX_CONF_FLAG 0x00000200</code>
<code>#define NGX_CONF_ANY 0x00000400</code>
<code>#define NGX_CONF_1MORE 0x00000800</code>
<code>#define NGX_CONF_2MORE 0x00001000</code>
<code>#define NGX_CONF_MULTI 0x00002000<</code>
其中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子產品的指令定義:
<code>static</code>
<code>ngx_command_t ngx_http_echo_commands[] = {</code>
<code> </code><code>{ ngx_string(</code><code>"echo"</code><code>),</code>
<code> </code><code>NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,</code>
<code> </code><code>ngx_http_echo,</code>
<code> </code><code>NGX_HTTP_LOC_CONF_OFFSET,</code>
<code> </code><code>offsetof(ngx_http_echo_loc_conf_t, ed),</code>
<code> </code><code>NULL },</code>
<code> </code><code>ngx_null_command</code>
指令數組的命名規則為ngx_http_[module-name]_commands,注意數組最後一個元素要是ngx_null_command結束。
參數轉化函數的代碼為:
<code>char</code> <code>*</code>
<code>ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd,</code><code>void</code>
<code>*conf)</code>
<code> </code><code>ngx_http_core_loc_conf_t *clcf;</code>
<code> </code><code>clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);</code>
<code> </code><code>clcf->handler = ngx_http_echo_handler;</code>
<code> </code><code>ngx_conf_set_str_slot(cf,cmd,conf);</code>
<code> </code><code>return</code>
<code>NGX_CONF_OK;</code>
這個函數除了調用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結構:
<code>ngx_http_module_t ngx_http_echo_module_ctx = {</code>
<code> </code><code>NULL, </code><code>/* preconfiguration */</code>
<code> </code><code>NULL, </code><code>/* postconfiguration */</code>
<code> </code><code>NULL, </code><code>/* create main configuration */</code>
<code> </code><code>NULL, </code><code>/* init main configuration */</code>
<code> </code><code>NULL, </code><code>/* create server configuration */</code>
<code> </code><code>NULL, </code><code>/* merge server configuration */</code>
<code> </code><code>ngx_http_echo_create_loc_conf, </code><code>/* create location configration */</code>
<code> </code><code>ngx_http_echo_merge_loc_conf </code><code>/* merge location configration */</code>
可以看到一共有8個Hook注入點,分别會在不同時刻被Nginx調用,由于我們的子產品僅僅用于location域,這裡将不需要的注入點設為NULL即可。其中create_loc_conf用于初始化一個配置結構體,如為配置結構體配置設定記憶體等工作;merge_loc_conf用于将其父block的配置資訊合并到此結構體中,也就是實作配置的繼承。這兩個函數會被Nginx自動調用。注意這裡的命名規則:ngx_http_[module-name]_
[create|merge]_[main|srv|loc]_conf。
下面是echo子產品這個兩個函數的代碼:
<code>void</code> <code>*</code>
<code>ngx_http_echo_create_loc_conf(ngx_conf_t *cf)</code>
<code> </code><code>ngx_http_echo_loc_conf_t *conf;</code>
<code> </code><code>conf = ngx_pcalloc(cf->pool,</code><code>sizeof</code><code>(ngx_http_echo_loc_conf_t));</code>
<code> </code><code>if</code>
<code>(conf == NULL) {</code>
<code> </code><code>return</code>
<code>NGX_CONF_ERROR;</code>
<code> </code><code>conf->ed.len = 0;</code>
<code> </code><code>conf->ed.data = NULL;</code>
<code>conf;</code>
<code>ngx_http_echo_merge_loc_conf(ngx_conf_t *cf,</code><code>void</code>
<code>*parent,</code><code>void</code>
<code>*child)</code>
<code> </code><code>ngx_http_echo_loc_conf_t *prev = parent;</code>
<code> </code><code>ngx_http_echo_loc_conf_t *conf = child;</code>
<code> </code><code>ngx_conf_merge_str_value(conf->ed, prev->ed,</code><code>""</code><code>);</code>
其中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中:
<code>#define ngx_conf_merge_str_value(conf, prev, default) \</code>
<code>(conf.data == NULL) { \</code>
<code> </code><code>if</code>
<code>(prev.data) { \</code>
<code> </code><code>conf.len = prev.len; \</code>
<code> </code><code>conf.data = prev.data; \</code>
<code> </code><code>}</code><code>else</code>
<code>{ \</code>
<code> </code><code>conf.len =</code><code>sizeof</code><code>(</code><code>default</code><code>) - 1; \</code>
<code> </code><code>conf.data = (u_char *)</code><code>default</code><code>; \</code>
<code> </code><code>} \</code>
同時可以看到,core/ngx_conf_file.h還定義了很多merge value的宏用于merge各種資料。它們的行為比較相似:使用prev填充conf,如果prev的資料為空則使用default填充。
下面的工作是編寫handler。handler可以說是子產品中真正幹活的代碼,它主要有以下四項職責:
讀入子產品配置。
處理功能業務。
産生HTTP header。
産生HTTP body。
下面先貼出echo子產品的代碼,然後通過分析代碼的方式介紹如何實作這四步。這一塊的代碼比較複雜:
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<code>ngx_int_t</code>
<code>ngx_http_echo_handler(ngx_http_request_t *r)</code>
<code> </code><code>ngx_int_t rc;</code>
<code> </code><code>ngx_buf_t *b;</code>
<code> </code><code>ngx_chain_t out;</code>
<code> </code><code>ngx_http_echo_loc_conf_t *elcf;</code>
<code> </code><code>elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);</code>
<code> </code><code>if</code><code>(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))</code>
<code> </code><code>{</code>
<code>NGX_HTTP_NOT_ALLOWED;</code>
<code> </code><code>r->headers_out.content_type.len =</code><code>sizeof</code><code>(</code><code>"text/html"</code><code>) - 1;</code>
<code> </code><code>r->headers_out.content_type.data = (u_char *)</code><code>"text/html"</code><code>;</code>
<code> </code><code>r->headers_out.status = NGX_HTTP_OK;</code>
<code> </code><code>r->headers_out.content_length_n = elcf->ed.len;</code>
<code> </code><code>if</code><code>(r->method == NGX_HTTP_HEAD)</code>
<code> </code><code>rc = ngx_http_send_header(r);</code>
<code> </code><code>if</code><code>(rc != NGX_OK)</code>
<code> </code><code>{</code>
<code> </code><code>return</code>
<code>rc;</code>
<code> </code><code>b = ngx_pcalloc(r->pool,</code><code>sizeof</code><code>(ngx_buf_t));</code>
<code> </code><code>if</code><code>(b == NULL)</code>
<code> </code><code>ngx_log_error(NGX_LOG_ERR, r->connection-></code><code>log</code><code>, 0,</code><code>"Failed to allocate response buffer."</code><code>);</code>
<code>NGX_HTTP_INTERNAL_SERVER_ERROR;</code>
<code> </code><code>out.buf = b;</code>
<code> </code><code>out.next = NULL;</code>
<code> </code><code>b->pos = elcf->ed.data;</code>
<code> </code><code>b->last = elcf->ed.data + (elcf->ed.len);</code>
<code> </code><code>b->memory = 1;</code>
<code> </code><code>b->last_buf = 1;</code>
<code> </code><code>rc = ngx_http_send_header(r);</code>
<code> </code><code>if</code><code>(rc != NGX_OK)</code>
<code>ngx_http_output_filter(r, &out);</code>
handler會接收一個ngx_http_request_t指針類型的參數,這個參數指向一個ngx_http_request_t結構體,此結構體存儲了這次HTTP請求的一些資訊,這個結構定義在http/ngx_http_request.h中:
51
52
53
54
55
56
57
58
59
60
61
62
<code>ngx_http_request_s {</code>
<code> </code><code>uint32_t signature; </code><code>/* "HTTP" */</code>
<code> </code><code>ngx_connection_t *connection;</code>
<code> </code><code>void</code>
<code>**ctx;</code>
<code>**main_conf;</code>
<code>**srv_conf;</code>
<code>**loc_conf;</code>
<code> </code><code>ngx_http_event_handler_pt read_event_handler;</code>
<code> </code><code>ngx_http_event_handler_pt write_event_handler;</code>
<code>#if (NGX_HTTP_CACHE)</code>
<code> </code><code>ngx_http_cache_t *cache;</code>
<code>#endif</code>
<code> </code><code>ngx_http_upstream_t *upstream;</code>
<code> </code><code>ngx_array_t *upstream_states;</code>
<code> </code><code>/* of ngx_http_upstream_state_t */</code>
<code> </code><code>ngx_pool_t *pool;</code>
<code> </code><code>ngx_buf_t *header_in;</code>
<code> </code><code>ngx_http_headers_in_t headers_in;</code>
<code> </code><code>ngx_http_headers_out_t headers_out;</code>
<code> </code><code>ngx_http_request_body_t *request_body;</code>
<code> </code><code>time_t</code>
<code>lingering_time;</code>
<code>start_sec;</code>
<code> </code><code>ngx_msec_t start_msec;</code>
<code> </code><code>ngx_uint_t method;</code>
<code> </code><code>ngx_uint_t http_version;</code>
<code> </code><code>ngx_str_t request_line;</code>
<code> </code><code>ngx_str_t uri;</code>
<code> </code><code>ngx_str_t args;</code>
<code> </code><code>ngx_str_t exten;</code>
<code> </code><code>ngx_str_t unparsed_uri;</code>
<code> </code><code>ngx_str_t method_name;</code>
<code> </code><code>ngx_str_t http_protocol;</code>
<code> </code><code>ngx_chain_t *out;</code>
<code> </code><code>ngx_http_request_t *main;</code>
<code> </code><code>ngx_http_request_t *parent;</code>
<code> </code><code>ngx_http_postponed_request_t *postponed;</code>
<code> </code><code>ngx_http_post_subrequest_t *post_subrequest;</code>
<code> </code><code>ngx_http_posted_request_t *posted_requests;</code>
<code> </code><code>ngx_http_virtual_names_t *virtual_names;</code>
<code> </code><code>ngx_int_t phase_handler;</code>
<code> </code><code>ngx_http_handler_pt content_handler;</code>
<code> </code><code>ngx_uint_t access_code;</code>
<code> </code><code>ngx_http_variable_value_t *variables;</code>
<code> </code><code>/* ... */</code>
由于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資訊:
<code> </code><code>ngx_list_t headers;</code>
<code> </code><code>ngx_uint_t status;</code>
<code> </code><code>ngx_str_t status_line;</code>
<code> </code><code>ngx_table_elt_t *server;</code>
<code> </code><code>ngx_table_elt_t *date;</code>
<code> </code><code>ngx_table_elt_t *content_length;</code>
<code> </code><code>ngx_table_elt_t *content_encoding;</code>
<code> </code><code>ngx_table_elt_t *location;</code>
<code> </code><code>ngx_table_elt_t *refresh;</code>
<code> </code><code>ngx_table_elt_t *last_modified;</code>
<code> </code><code>ngx_table_elt_t *content_range;</code>
<code> </code><code>ngx_table_elt_t *accept_ranges;</code>
<code> </code><code>ngx_table_elt_t *www_authenticate;</code>
<code> </code><code>ngx_table_elt_t *expires;</code>
<code> </code><code>ngx_table_elt_t *etag;</code>
<code> </code><code>ngx_str_t *override_charset;</code>
<code> </code><code>size_t</code>
<code>content_type_len;</code>
<code> </code><code>ngx_str_t content_type;</code>
<code> </code><code>ngx_str_t charset;</code>
<code> </code><code>u_char *content_type_lowcase;</code>
<code> </code><code>ngx_uint_t content_type_hash;</code>
<code> </code><code>ngx_array_t cache_control;</code>
<code> </code><code>off_t content_length_n;</code>
<code>date_time;</code>
<code>last_modified_time;</code>
<code>} ngx_http_headers_out_t;</code>
設定好頭資訊後使用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:
<code>ngx_chain_s {</code>
<code> </code><code>ngx_buf_t *buf;</code>
<code> </code><code>ngx_chain_t *next;</code>
其中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緩沖連結清單的結構:
<a href="http://www.codinglabs.org/wp-content/uploads/2011/10/image10.png"></a>
緩沖資料準備好後,用ngx_http_output_filter就可以輸出了(會送到filter進行各種過濾處理)。ngx_http_output_filter的第一個參數為ngx_http_request_t結構,第二個為輸對外連結表的起始位址&out。ngx_http_out_put_filter會周遊連結清單,輸出所有資料。
以上就是handler的所有工作,請對照描述了解上面貼出的handler代碼。
上面完成了Nginx子產品各種元件的開發下面就是将這些組合起來了。一個Nginx子產品被定義為一個ngx_module_t結構,這個結構的字段很多,不過開頭和結尾若幹字段一般可以通過Nginx内置的宏去填充,下面是我們echo子產品的子產品主體定義:
<code>ngx_module_t ngx_http_echo_module = {</code>
<code> </code><code>NGX_MODULE_V1,</code>
<code> </code><code>&ngx_http_echo_module_ctx, </code><code>/* module context */</code>
<code> </code><code>ngx_http_echo_commands, </code><code>/* module directives */</code>
<code> </code><code>NGX_HTTP_MODULE, </code><code>/* module type */</code>
<code> </code><code>NULL, </code><code>/* init master */</code>
<code> </code><code>NULL, </code><code>/* init module */</code>
<code> </code><code>NULL, </code><code>/* init process */</code>
<code> </code><code>NULL, </code><code>/* init thread */</code>
<code> </code><code>NULL, </code><code>/* exit thread */</code>
<code> </code><code>NULL, </code><code>/* exit process */</code>
<code> </code><code>NULL, </code><code>/* exit master */</code>
<code> </code><code>NGX_MODULE_V1_PADDING</code>
開頭和結尾分别用NGX_MODULE_V1和NGX_MODULE_V1_PADDING 填充了若幹字段,就不去深究了。這裡主要需要填入的資訊從上到下以依次為context、指令數組、子產品類型以及若幹特定事件的回調處理函數(不需要可以置為NULL),其中内容還是比較好了解的,注意我們的echo是一個HTTP子產品,是以這裡類型是NGX_HTTP_MODULE,其它可用類型還有NGX_EVENT_MODULE(事件處理子產品)和NGX_MAIL_MODULE(郵件子產品)。
這樣,整個echo子產品就寫好了,下面給出echo子產品的完整代碼:
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<code>/*</code>
<code> </code><code>* Copyright (C) Eric Zhang</code>
<code> </code><code>*/</code>
<code>#include <ngx_config.h></code>
<code>#include <ngx_core.h></code>
<code>#include <ngx_http.h></code>
<code>/* Module config */</code>
<code>char</code> <code>*ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd,</code><code>void</code>
<code>void</code> <code>*ngx_http_echo_create_loc_conf(ngx_conf_t *cf);</code>
<code>char</code> <code>*ngx_http_echo_merge_loc_conf(ngx_conf_t *cf,</code><code>void</code>
<code>*child);</code>
<code>/* Directives */</code>
<code>/* Http context of the module */</code>
<code>/* Module */</code>
<code>/* Handler function */</code>
Nginx不支援動态連結子產品,是以安裝子產品需要将子產品代碼與Nginx源代碼進行重新編譯。安裝子產品的步驟如下:
1、編寫子產品config檔案,這個檔案需要放在和子產品源代碼檔案放在同一目錄下。檔案内容如下:
<code>ngx_addon_name=子產品完整名稱</code>
<code>HTTP_MODULES=</code><code>"$HTTP_MODULES 子產品完整名稱"</code>
<code>NGX_ADDON_SRCS=</code><code>"$NGX_ADDON_SRCS $ngx_addon_dir/源代碼檔案名"</code>
2、進入Nginx源代碼,使用下面指令編譯安裝
<code>--prefix=安裝目錄 --add-module=子產品源代碼檔案目錄</code>
這樣就完成安裝了,例如,我的源代碼檔案放在/home/yefeng/ngxdev/ngx_http_echo下,我的config檔案為:
<code>ngx_addon_name=ngx_http_echo_module</code>
<code>HTTP_MODULES=</code><code>"$HTTP_MODULES ngx_http_echo_module"</code>
<code>NGX_ADDON_SRCS=</code><code>"$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_echo_module.c"</code>
編譯安裝指令為:
<code>--add-module=</code><code>/home/yefeng/ngxdev/ngx_http_echo</code>
<code>sudo</code>
<code>make</code> <code>install</code>
這樣echo子產品就被安裝在我的Nginx上了,下面測試一下,修改配置檔案,增加以下一項配置:
<code>"This is my first nginx module!!!"</code><code>;</code>
然後用curl測試一下:
<code>curl -i http:</code><code>//localhost/echo</code>
結果如下:
<a href="http://www.codinglabs.org/wp-content/uploads/2011/10/image18.png"></a>
可以看到子產品已經正常工作了,也可以在浏覽器中打開網址,就可以看到結果:
<a href="http://www.codinglabs.org/wp-content/uploads/2011/10/image23.png"></a>
2009
[3] Clément Nedelcu, Nginx Http Server. Packt Publishing, 2010