歡迎您光臨本站 註冊首頁

Nginx模塊開發入門

←手機掃碼閱讀     火星人 @ 2014-03-03 , reply:0

Nginx模塊開發入門


   Nginx是當前最流行的HTTP Server之一,根據W3techs的統計,目前世界排名前100萬的網站。有6%是用的Nginx。與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所開發。

Nginx在Linux下的安裝與運行:
    使用Nginx的第一步是下載Nginx源碼包,例如1.0.0的下載地址為http://www.nginx.org/download/nginx-1.0.0.tar.gz下載完後用tar命令解壓縮,進入目錄后安裝過程與Linux下通常步驟無異,例如我想講Nginx安裝到/usr/local/nginx下,則執行如下命令:


#./configure --prefix=/usr/local/nginx
#make && make install
#cd ../
安裝完成後可以直接使用下面命令啟動Nginx:
#/usr/local/nginx/sbin/nginx               
Nginx默認以Deamon進程啟動,輸入下列命令:
# curl -I localhost                                 

啟動后如果想停止Nginx可以使用:
#/usr/local/nginx/sbin/nginx -s stop      

Nginx配置文件的結構:
    配置文件可以看做是Nginx的靈魂,Nginx服務在啟動時會讀入配置文件,而後續幾乎一切動作行為都是按照配置文件中的指令進行的,因此如果將Nginx本身看做一個計算機,那麼Nginx的配置文件可以看成是全部的程序指令。
下面是一個Nginx配置文件的實例:
#user nobody;                                                                                      
worker_processes 8;
error_log logs/error.log
pid          logs/nginx.pid
events {
    worker_connecrions 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/wwwroot;
            index index.html index.htm;
           }

#error_page 404    /404.html;
error_page  500 502 503 504 /50x.html;
location = 50x.html {
    root   html;
       }
    }
}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本身支持多種模塊,如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響應。例如,做如下配置: location /echo {                                                
        echo "hello nginx";
}則訪問http://localhost/echo時候會出現hello nginx
    直觀來看,要實現這個功能需要三步:
    1、讀入配置文件中echo指令及其參數;
    2、進行HTTP包裝(添加HTTP頭等工作);
    3、將結果返回給客戶端。

定義模塊配置結構:
     首先我們需要一個結構用於存儲從配置文件中讀進來的相關指令參數,即模塊配置信息結構。根據Nginx模塊開發規則,這個結構的命名規則為ngx_http___conf_t。其中main、srv和loc分別用於表示同一模塊在三層block中的配置信息。這裡我們的echo模塊只需要運行在loc層級下,需要存儲一個字元串參數,因此我們可以定義如下的模塊配置:
typedef struct {
     ngx_str_t  ed;
} ngx_http_echo_loc_conf_t;複製代碼
     其中欄位ed用於存儲echo指令指定的需要輸出的字元串。注意這裡ed的類型,在Nginx模塊開發中使用ngx_str_t類型表示字元串,這個類型定義在core/ngx_string中:
typedef struct {
     size_t      len;
     u_char     *data;
} ngx_str_t;複製代碼
     其中兩個欄位分別表示字元串的長度和數據起始地址。注意在Nginx源代碼中對數據類型進行了別稱定義,如ngx_int_t為intptr_t的別稱,為了保持一致,在開發Nginx模塊時也應該使用這些Nginx源碼定義的類型而不要使用C原生類型。除了ngx_str_t外,其它三個常用的nginx type分別為:typedef intptr_t        ngx_int_t;
typedef uintptr_t       ngx_uint_t;
typedef intptr_t        ngx_flag_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中: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使用掩碼標誌位方式配置指令參數,相關可用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表示此指令不接受參數,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模塊的指令定義: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
};複製代碼

參數轉化函數的代碼為: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;
}複製代碼
    這個函數除了調用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_ctx,這個結構主要用於定義各個Hook函數。下面是echo模塊的context結構: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個Hook注入點,分別會在不同時刻被Nginx調用,由於我們的模塊僅僅用於location域,這裡將不需要的注入點設為NULL即可。其中create_loc_conf用於初始化一個配置結構體,如為配置結構體分配內存等工作;merge_loc_conf用於將其父block的配置信息合併到此結構體中,也就是實現配置的繼承。這兩個函數會被Nginx自動調用。注意這裡的命名規則:ngx_http____conf。

下面是echo模塊這個兩個函數的代碼: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內存池中分配一塊空間,是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中:#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;                                \
         }                                                                  
     }複製代碼
     同時可以看到,core/ngx_conf_file.h還定義了很多merge value的宏用於merge各種數據。它們的行為比較相似:使用prev填充conf,如果prev的數據為空則使用default填充。

編寫Handler:
讀入模塊配置。
處理功能業務。
產生HTTP header。
產生HTTP body。
下面先貼出echo模塊的代碼,然後通過分析代碼的方式介紹如何實現這四步。這一塊的代碼比較複雜:
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會接收一個ngx_http_request_t指針類型的參數,這個參數指向一個ngx_http_request_t結構體,此結構體存儲了這次HTTP請求的一些信息,這個結構定義在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常用信息。這裡需要特別注意的幾個欄位是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和Contentlength等基本內容,ngx_http_headers_out_t定義了所有可以設置的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;複製代碼   設置好頭信息后使用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:struct ngx_chain_s {
     ngx_buf_t    *buf;
     ngx_chain_t  *next;
};複製代碼
    其中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會遍歷鏈表,輸出所有數據。

組合Nginx Module:
    上面完成了Nginx模塊各種組件的開發下面就是將這些組合起來了。一個Nginx模塊被定義為一個ngx_module_t結構,這個結構的欄位很多,不過開頭和結尾若干欄位一般可以通過Nginx內置的宏去填充,下面是我們echo模塊的模塊主體定義:
以上就是handler的所有工作,請對照描述理解上面貼出的handler代碼。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
};複製代碼
    開頭和結尾分別用NGX_MODULE_V1和NGX_MODULE_V1_PADDING 填充了若干欄位,就不去深究了。這裡主要需要填入的信息從上到下以依次為context、指令數組、模塊類型以及若干特定事件的回調處理函數(不需要可以置為NULL),其中內容還是比較好理解的,注意我們的echo是一個HTTP模塊,所以這裡類型是NGX_HTTP_MODULE,其它可用類型還有NGX_EVENT_MODULE(事件處理模塊)和NGX_MAIL_MODULE(郵件模塊)。
這樣,整個echo模塊就寫好了,下面給出echo模塊的完整代碼:#include
#include
#include
/* 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;
}
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不支持動態鏈接模塊,所以安裝模塊需要將模塊代碼與Nginx源代碼進行重新編譯。安裝模塊的步驟如下:
1、編寫模塊config文件,這個文件需要放在和模塊源代碼文件放在同一目錄下。文件內容如下:ngx_addon_name=模塊完整名稱
HTTP_MODULES="$HTTP_MODULES 模塊完整名稱"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/源代碼文件名"複製代碼

2、進入Nginx源代碼,使用下面命令編譯安裝(--add-module=)

     本文只是簡要介紹了Nginx模塊的開發過程,由於篇幅的原因,不能面面俱到。因為目前Nginx的學習資料很少,如果讀者希望更深入學習Nginx的原理及模塊開發,那麼閱讀源代碼是最好的辦法。在Nginx源代碼的core/下放有Nginx的核心代碼,對理解Nginx的內部工作機制非常有幫助,http/目錄下有Nginx HTTP相關的實現,http/module下放有大量內置http模塊,可供讀者學習模塊的開發,另外在http://wiki.nginx.org/3rdPartyModules上有大量優秀的第三方模塊,也是非常好的學習資料。

[火星人 ] Nginx模塊開發入門已經有964次圍觀

http://coctec.com/docs/service/show-post-2272.html