Nginx research, nginx module development

catalogphp

1. 初探nginx架構
2. handler模塊
3. Nginx編譯、安裝、配置
4. Hello World模塊開發

 

1. 初探nginx架構 html

nginx在啓動後,在unix系統中會以daemon的方式在後臺運行,後臺進程包含一個master進程和多個worker進程。咱們也能夠手動地關掉後臺模式,讓nginx在前臺運行,而且經過配置讓nginx取消master進程,從而可使nginx以單進程方式運行(很顯然,生產環境下咱們確定不會這麼作,因此關閉後臺模式,通常是用來調試用的)。因此,咱們能夠看到,nginx是以多進程的方式來工做的,固然nginx也是支持多線程的方式的,只是咱們主流的方式仍是多進程的方式,也是nginx的默認方式
nginx在啓動後,會有一個master進程和多個worker進程nginx

1. master進程主要用來管理worker進程,包含:
    1) 接收來自外界的信號
    2) 向各worker進程發送信號
    3) 監控worker進程的運行狀態
    4) 當worker進程退出後(異常狀況下),會自動從新啓動新的worker進程
2. woker進程: 基本的網絡事件,則是放在worker進程中來處理了
    1) 多個worker進程之間是對等的,他們同等競爭來自客戶端的請求,各進程互相之間是獨立的
    2) 一個請求,只可能在一個worker進程中處理,一個worker進程,不可能處理其它進程的請求
    3) worker進程的個數是能夠設置的,通常咱們會設置與機器cpu核數一致,這裏面的緣由與nginx的進程模型以及事件處理模型是分不開的 

從上文中咱們能夠看到,master來管理worker進程,因此咱們只須要與master進程通訊就好了。master進程會接收來自外界發來的信號,再根據信號作不一樣的事情。因此咱們要控制nginx,只須要經過kill向master進程發送信號就好了。好比kill -HUP pid,則是告訴nginx,從容地重啓nginx,咱們通常用這個信號來重啓nginx,或從新加載配置,由於是從容地重啓,所以服務是不中斷的git

1. 首先master進程在接到信號後,會先從新加載配置文件
2. 而後再啓動新的worker進程,並向全部老的worker進程發送信號,告訴他們能夠光榮退休了
3. 新的worker在啓動後,就開始接收新的請求(並獲取新的配置文件)
4. 而老的worker在收到來自master的信號後,就再也不接收新的請求,而且在當前進程中的全部未處理完的請求處理完成後,再退出
/*
固然,直接給master進程發送信號,這是比較老的操做方式,nginx在0.8版本以後,引入了一系列命令行參數,來方便咱們管理。好比
    1) ./nginx -s reload: 重啓nginx: 執行命令時,咱們是啓動一個新的nginx進程,而新的nginx進程在解析到reload參數後,就知道咱們的目的是控制nginx來從新加載配置文件了,它會向master進程發送信號,而後接下來的動做,就和咱們直接向master進程發送信號同樣了
    2) ./nginx -s stop: 中止nginx的運行 
*/

worker進程之間是平等的,每一個進程,處理請求的機會也是同樣的。當咱們提供80端口的http服務時,一個鏈接請求過來,每一個進程都有可能處理這個鏈接github

1. 每一個worker進程都是從master進程fork過來
2. 在master進程裏面,先創建好須要listen的socket(listenfd)以後,而後再fork出多個worker進程
3. 全部worker進程的listenfd會在新鏈接到來時變得可讀(子進程和父進程經過fork共享文件句柄,這使得全部worker進程可以擁有同等的機會處理本次請求),爲保證只有一個進程處理該鏈接,全部worker進程在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個進程註冊listenfd讀事件,在讀事件裏調用accept接受該鏈接(connfd)
//全部worker進程都會去爭奪listenfd的讀權限,但只有一個worker能最終得到,並調用accept得到connfd,進行後續的動做,而其餘未爭奪到本次listenfd的worker則繼續等待下一次鏈接並爭奪listenfd
4. 當一個worker進程在accept這個鏈接以後,就開始讀取請求,解析請求,處理請求,產生數據後,再返回給客戶端,最後才斷開鏈接,這樣一個完整的請求就是這樣的了
5. 咱們能夠看到,一個請求,徹底由worker進程來處理,並且只在一個worker進程中處理 

0x1: Nginx Master & Worker架構的優點apache

1. 對於每一個worker進程來講,獨立的進程,不須要加鎖,因此省掉了鎖帶來的開銷
2. 同時在編程以及問題查找時,也會方便不少
3. 採用獨立的進程,可讓互相之間不會影響,一個進程退出後,其它進程還在工做,服務不會中斷,master進程則很快啓動新的worker進程

0x2: Nginx的異步非阻塞請求模型編程

看看一個請求的完整過程。首先,請求過來,要創建鏈接,而後再接收數據,接收數據後,再發送數據。具體到系統底層,就是讀寫事件,而當讀寫事件沒有準備好時,必然不可操做,若是不用非阻塞的方式來調用,那就得阻塞調用了,事件沒有準備好,那就只能等了,等事件準備好了,你再繼續吧。阻塞調用會進入內核等待,cpu就會讓出去給別人用了,對單線程的worker來講,顯然不合適,當網絡事件越多時,你們都在等待呢,cpu空閒下來沒人用,cpu利用率天然上不去了,更別談高併發了。好吧,你說加進程數,這跟apache的線程模型有什麼區別,注意,別增長無謂的上下文切換。因此,在nginx裏面,最忌諱阻塞的系統調用了。不要阻塞,那就非阻塞嘍。非阻塞就是,事件沒有準備好,立刻返回EAGAIN,告訴你,事件還沒準備好呢,你慌什麼,過會再來吧。好吧,你過一會,再來檢查一下事件,直到事件準備好了爲止,在這期間,你就能夠先去作其它事情,而後再來看看事件好了沒。雖然不阻塞了,但你得不時地過來檢查一下事件的狀態,你能夠作更多的事情了,但帶來的開銷也是不小的。因此,纔會有了異步非阻塞的事件處理機制,具體到系統調用就是像select/poll/epoll/kqueue這樣的系統調用。它們提供了一種機制,讓你能夠同時監控多個事件,調用他們是阻塞的,但能夠設置超時時間,在超時時間以內,若是有事件準備好了,就返回。這種機制正好解決了咱們上面的兩個問題,拿epoll爲例(在後面的例子中,咱們多以epoll爲例子,以表明這一類函數),當事件沒準備好時,放到epoll裏面,事件準備好了,咱們就去讀寫,當讀寫返回EAGAIN時,咱們將它再次加入到epoll裏面。這樣,只要有事件準備好了,咱們就去處理它,只有當全部事件都沒準備好時,纔在epoll裏面等着。這樣,咱們就能夠併發處理大量的併發了,固然,這裏的併發請求,是指未處理完的請求,線程只有一個,因此同時能處理的請求固然只有一個了,只是在請求間進行不斷地切換而已,切換也是由於異步事件未準備好,而主動讓出的。這裏的切換是沒有任何代價,你能夠理解爲循環處理多個準備好的事件,事實上就是這樣的。與多線程相比,這種事件處理方式是有很大的優點的,不須要建立線程,每一個請求佔用的內存也不多,沒有上下文切換,事件處理很是的輕量級。併發數再多也不會致使無謂的資源浪費(上下文切換)。更多的併發數,只是會佔用更多的內存而已。 我以前有對鏈接數進行過測試,在24G內存的機器上,處理的併發請求數達到過200萬。如今的網絡服務器基本都採用這種方式,這也是nginx性能高效的主要緣由。
咱們以前說過,推薦設置worker的個數爲cpu的核數,在這裏就很容易理解了,更多的worker數,只會致使進程來競爭cpu資源了,從而帶來沒必要要的上下文切換。並且,nginx爲了更好的利用多核特性,提供了cpu親緣性的綁定選項,咱們能夠將某一個進程綁定在某一個核上,這樣就不會由於進程的切換帶來cache的失效。像這種小的優化在nginx中很是常見,同時也說明了nginx做者的苦心孤詣。好比,nginx在作4個字節的字符串比較時,會將4個字符轉換成一個int型,再做比較,以減小cpu的指令數等等 vim

Relevant Link:數組

http://tengine.taobao.org/book/chapter_02.html#

 

2. handler模塊安全

做爲第三方開發者最可能開發的就是三種類型的模塊

1. handler: Handler模塊就是接受來自客戶端的請求併產生輸出的模塊
配置文件中使用location指令能夠配置content handler模塊,當Nginx系統啓動的時候,每一個handler模塊都有一次機會把本身關聯到對應的location上(若是有多個handler模塊都關聯了同一個location,那麼實際上只有一個handler模塊真正會起做用)
handler模塊處理的結果一般有三種狀況
    1) 處理成功
    2) 處理失敗(處理的時候發生了錯誤)
    3) 拒絕去處理。在拒絕處理的狀況下,這個location的處理就會由默認的handler模塊來進行處理。例如,當請求一個靜態文件的時候,若是關聯到這個location上的一個handler模塊拒絕處理,就會由默認的ngx_http_static_module模塊進行處理,該模塊是一個典型的handler模塊 

2. filter
3. load-balancer

0x1: 模塊的基本數據結構

1. 模塊配置結構

基本上每一個模塊都會提供一些配置指令,以便於用戶能夠經過配置來控制該模塊的行爲。這些配置信息的存儲就須要定義該模塊的配置結構來進行存儲
Nginx的配置信息分紅了幾個做用域(scope,有時也稱做上下文)

1. main
2. server
3. location

每一個模塊提供的配置指令也能夠出如今這幾個做用域裏。那對於這三個做用域的配置信息,每一個模塊就須要定義三個不一樣的數據結構去進行存儲,有一點須要特別注意的就是,在模塊的開發過程當中,咱們最好使用nginx原有的命名習慣。這樣跟原代碼的契合度更高,對於模塊配置信息的定義,命名習慣是

ngx_http_<module name>_(main|srv|loc)_conf_t。這裏有個例子

2. 模塊配置指令

一個模塊的配置指令是定義在一個靜態數組中的,src/core/ngx_conf_file.h

struct ngx_command_s 
{
    //配置指令的名稱
    ngx_str_t             name;

    /*
    該配置的類型,其實更準確一點說,是該配置指令屬性的集合。nginx提供了不少預約義的屬性值(一些宏定義),經過邏輯或運算符可組合在一塊兒,造成對這個配置指令的詳細的說明
    1. NGX_CONF_NOARGS:配置指令不接受任何參數
    2. NGX_CONF_TAKE1:配置指令接受1個參數
    3. NGX_CONF_TAKE2:配置指令接受2個參數
    4. NGX_CONF_TAKE3:配置指令接受3個參數
    5. NGX_CONF_TAKE4:配置指令接受4個參數
    6. NGX_CONF_TAKE5:配置指令接受5個參數
    7. NGX_CONF_TAKE6:配置指令接受6個參數
    8. NGX_CONF_TAKE7:配置指令接受7個參數
    能夠組合多個屬性,好比一個指令便可以不填參數,也能夠接受1個或者2個參數。那麼就是NGX_CONF_NOARGS|NGX_CONF_TAKE1|NGX_CONF_TAKE2
    1. NGX_CONF_TAKE12:配置指令接受1個或者2個參數
    2. NGX_CONF_TAKE13:配置指令接受1個或者3個參數
    3. NGX_CONF_TAKE23:配置指令接受2個或者3個參數
    4. NGX_CONF_TAKE123:配置指令接受1個或者2個或者3參數
    5. NGX_CONF_TAKE1234:配置指令接受1個或者2個或者3個或者4個參數
    6. NGX_CONF_1MORE:配置指令接受至少一個參數
    7. NGX_CONF_2MORE:配置指令接受至少兩個參數
    8. NGX_CONF_MULTI: 配置指令能夠接受多個參數,即個數不定
    
    1. NGX_CONF_BLOCK:配置指令能夠接受的值是一個配置信息塊。也就是一對大括號括起來的內容。裏面能夠再包括不少的配置指令。好比常見的server指令就是這個屬性的
    2. NGX_CONF_FLAG:配置指令能夠接受的值是」on」或者」off」,最終會被轉成bool值
    3. NGX_CONF_ANY:配置指令能夠接受的任意的參數值。一個或者多個,或者」on」或者」off」,或者是配置塊
    值得注意的是,不管如何,nginx的配置指令的參數個數不能夠超過NGX_CONF_MAX_ARGS個。目前這個值被定義爲8,也就是不能超過8個參數值

    下面介紹一組說明配置指令能夠出現的位置的屬性。
    1. NGX_DIRECT_CONF:能夠出如今配置文件中最外層。例如已經提供的配置指令daemon,master_process等
    2. NGX_MAIN_CONF: http、mail、events、error_log等
    3. NGX_ANY_CONF: 該配置指令能夠出如今任意配置級別上
    
    對於咱們編寫的大多數模塊而言,都是在處理http相關的事情,也就是所謂的都是NGX_HTTP_MODULE,對於這樣類型的模塊,其配置可能出現的位置也是分爲直接出如今http裏面,以及其餘位置
    1. NGX_HTTP_MAIN_CONF: 能夠直接出如今http配置指令裏
    2. NGX_HTTP_SRV_CONF: 能夠出如今http裏面的server配置指令裏
    3. NGX_HTTP_LOC_CONF: 能夠出如今http server塊裏面的location配置指令裏
    4. NGX_HTTP_UPS_CONF: 能夠出如今http裏面的upstream配置指令裏
    5. NGX_HTTP_SIF_CONF: 能夠出如今http裏面的server配置指令裏的if語句所在的block中
    6. NGX_HTTP_LMT_CONF: 能夠出如今http裏面的limit_except指令的block中
    7. NGX_HTTP_LIF_CONF: 能夠出如今http server塊裏面的location配置指令裏的if語句所在的block中。
    */
    ngx_uint_t            type;

    /*
    set是一個函數指針,當nginx在解析配置的時候,若是遇到這個配置指令,將會把讀取到的值傳遞給這個函數進行分解處理。由於具體每一個配置指令的值如何處理,只有定義這個配置指令的人是最清楚的 
    char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 
    1. cf: 該參數裏面保存從配置文件讀取到的原始字符串以及相關的一些信息。特別注意的是這個參數的args字段是一個ngx_str_t類型的數組
        1) 該數組的首個元素是這個配置指令自己
        2) 第二個元素是指令的第一個參數
        3) 第三個元素是第二個參數,依次類推
    2. cmd: 這個配置指令對應的ngx_command_t結構
    3. conf: 就是定義的存儲這個配置值的結構體。用戶在處理的時候可使用類型轉換,轉換成本身知道的類型,再進行字段的賦值

    爲了更加方便的實現對配置指令參數的讀取,nginx已經默認提供了對一些標準類型的參數進行讀取的函數,能夠直接賦值給set字段使用。下面來看一下這些已經實現的set類型函數
    1. ngx_conf_set_flag_slot: 讀取NGX_CONF_FLAG類型的參數
    2. ngx_conf_set_str_slot:讀取字符串類型的參數
    3. ngx_conf_set_str_array_slot: 讀取字符串數組類型的參數
    4. ngx_conf_set_keyval_slot: 讀取鍵值對類型的參數
    5. ngx_conf_set_num_slot: 讀取整數類型(有符號整數ngx_int_t)的參數
    6. ngx_conf_set_size_slot:讀取size_t類型的參數,也就是無符號數
    7. ngx_conf_set_off_slot: 讀取off_t類型的參數
    8. ngx_conf_set_msec_slot: 讀取毫秒值類型的參數
    9. ngx_conf_set_sec_slot: 讀取秒值類型的參數
    10. ngx_conf_set_bufs_slot: 讀取的參數值是2個,一個是buf的個數,一個是buf的大小。例如: output_buffers 1 128k;
    11. ngx_conf_set_enum_slot: 讀取枚舉類型的參數,將其轉換成整數ngx_uint_t類型
    12. ngx_conf_set_bitmask_slot: 讀取參數的值,並將這些參數的值以bit位的形式存儲。例如:HttpDavModule模塊的dav_methods指令   
    */
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);

    /*
    該字段被NGX_HTTP_MODULE類型模塊所用(咱們編寫的基本上都是NGX_HTTP_MOUDLE,只有一些nginx核心模塊是非NGX_HTTP_MODULE),該字段指定當前配置項存儲的內存位置。其實是使用哪一個內存池的問題
    由於http模塊對全部http模塊所要保存的配置信息,劃分了main, server和location三個地方進行存儲,每一個地方都有一個內存池用來分配存儲這些信息的內存。這裏可能的值爲 
    1. NGX_HTTP_MAIN_CONF_OFFSET
    2. NGX_HTTP_SRV_CONF_OFFSET
    3. NGX_HTTP_LOC_CONF_OFFSET
    4. 0(NGX_HTTP_MAIN_CONF_OFFSET)
    */
    ngx_uint_t            conf;
    
    /*
    指定該配置項值的精確存放位置,通常指定爲某一個結構體變量的字段偏移。由於對於配置信息的存儲,通常咱們都是定義個結構體來存儲的
    那麼好比咱們定義了一個結構體A,該項配置的值須要存儲到該結構體的b字段。那麼在這裏就能夠填寫爲offsetof(A, b)
    對於有些配置項,它的值不須要保存或者是須要保存到更爲複雜的結構中時,這裏能夠設置爲0 
    */
    ngx_uint_t            offset;

    //該字段存儲一個指針。能夠指向任何一個在讀取配置過程當中須要的數據,以便於進行配置讀取的處理。大多數時候,都不須要,因此簡單地設爲0便可
    void                 *post;
};

//須要注意的是,就是在ngx_http_hello_commands這個數組定義的最後,都要加一個ngx_null_command做爲結尾
#define ngx_null_command  { ngx_null_string, 0, NULL, 0, 0, NULL }

3. 模塊上下文結構

這是一個ngx_http_module_t類型的靜態變量。這個變量其實是提供一組回調函數指針,這些函數有在建立存儲配置信息對象時被調用的函數,也有在建立前和建立後會調用的函數。這些函數都將被nginx在合適的時間進行調用

typedef struct 
{
    //在建立和讀取該模塊的配置信息以前被調用
    ngx_int_t   (*preconfiguration)(ngx_conf_t *cf);

    //在建立和讀取該模塊的配置信息以後被調用
    ngx_int_t   (*postconfiguration)(ngx_conf_t *cf);
    
    //調用該函數建立本模塊位於http block的配置信息存儲結構。該函數成功的時候,返回建立的配置對象。失敗的話,返回NULL
    void       *(*create_main_conf)(ngx_conf_t *cf);
    //調用該函數初始化本模塊位於http block的配置信息存儲結構。該函數成功的時候,返回NGX_CONF_OK。失敗的話,返回NGX_CONF_ERROR或錯誤字符串
    char       *(*init_main_conf)(ngx_conf_t *cf, void *conf);

    //調用該函數建立本模塊位於http server block的配置信息存儲結構,每一個server block會建立一個。該函數成功的時候,返回建立的配置對象。失敗的話,返回NULL
    void       *(*create_srv_conf)(ngx_conf_t *cf);
    //由於有些配置指令既能夠出如今http block,也能夠出如今http server block中。那麼遇到這種狀況,每一個server都會有本身存儲結構來存儲該server的配置,可是在這種狀況下http block中的配置與server block中的配置信息發生衝突的時候,就須要調用此函數進行合併,該函數並不是必須提供,當預計到絕對不會發生須要合併的狀況的時候,就無需提供。固然爲了安全起見仍是建議提供。該函數執行成功的時候,返回NGX_CONF_OK。失敗的話,返回NGX_CONF_ERROR或錯誤字符串
    char       *(*merge_srv_conf)(ngx_conf_t *cf, void *prev, void *conf);
    
    //調用該函數建立本模塊位於location block的配置信息存儲結構。每一個在配置中指明的location建立一個。該函數執行成功,返回建立的配置對象。失敗的話,返回NULL
    void       *(*create_loc_conf)(ngx_conf_t *cf);
    //與merge_srv_conf相似,這個也是進行配置值合併的地方。該函數成功的時候,返回NGX_CONF_OK。失敗的話,返回NGX_CONF_ERROR或錯誤字符串
    char       *(*merge_loc_conf)(ngx_conf_t *cf, void *prev, void *conf);
} ngx_http_module_t;

Nginx裏面的配置信息都是上下一層層的嵌套的,對於具體某個location的話,對於同一個配置,若是當前層次沒有定義,那麼就使用上層的配置,不然使用當前層次的配置(就近原則)
這些配置信息通常默認都應該設爲一個未初始化的值,針對這個需求,Nginx定義了一系列的宏定義來表明各類配置所對應數據類型的未初始化值,以下

#define NGX_CONF_UNSET       -1
#define NGX_CONF_UNSET_UINT  (ngx_uint_t) -1
#define NGX_CONF_UNSET_PTR   (void *) -1
#define NGX_CONF_UNSET_SIZE  (size_t) -1
#define NGX_CONF_UNSET_MSEC  (ngx_msec_t) -1

4. 模塊的定義

對於開發一個模塊來講,咱們都須要定義一個ngx_module_t類型的變量來講明這個模塊自己的信息,這是這個模塊最重要的一個信息,它告訴了nginx這個模塊的一些信息,上面定義的配置信息,還有模塊上下文信息,都是經過這個結構來告訴nginx系統的,也就是加載模塊的上層代碼,都須要經過定義的這個結構,來獲取這些信息

typedef struct ngx_module_s      ngx_module_t;
struct ngx_module_s {
    ngx_uint_t            ctx_index;
    ngx_uint_t            index;
    ngx_uint_t            spare0;
    ngx_uint_t            spare1;
    ngx_uint_t            abi_compatibility;
    ngx_uint_t            major_version;
    ngx_uint_t            minor_version;
    void                 *ctx;
    ngx_command_t        *commands;
    ngx_uint_t            type;
    ngx_int_t           (*init_master)(ngx_log_t *log);
    ngx_int_t           (*init_module)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_process)(ngx_cycle_t *cycle);
    ngx_int_t           (*init_thread)(ngx_cycle_t *cycle);
    void                (*exit_thread)(ngx_cycle_t *cycle);
    void                (*exit_process)(ngx_cycle_t *cycle);
    void                (*exit_master)(ngx_cycle_t *cycle);
    uintptr_t             spare_hook0;
    uintptr_t             spare_hook1;
    uintptr_t             spare_hook2;
    uintptr_t             spare_hook3;
    uintptr_t             spare_hook4;
    uintptr_t             spare_hook5;
    uintptr_t             spare_hook6;
    uintptr_t             spare_hook7;
};

#define NGX_NUMBER_MAJOR  3
#define NGX_NUMBER_MINOR  1
#define NGX_MODULE_V1          0, 0, 0, 0,                              \
    NGX_DSO_ABI_COMPATIBILITY, NGX_NUMBER_MAJOR, NGX_NUMBER_MINOR
#define NGX_MODULE_V1_PADDING  0, 0, 0, 0, 0, 0, 0, 0

Relevant Link:

http://tengine.taobao.org/book/chapter_03.html

 

3. Nginx編譯、安裝、配置

0x1: 編譯安裝

1. wget https://codeload.github.com/nginx/nginx/zip/master
2. cd /usr/local/nginx/nginx-master
3. ./auto/configure --prefix=/usr/local/nginx
make
make install

//啓動nginx
/usr/local/nginx/sbin/nginx

//Nginx默認以Deamon進程啓動
curl -i http://localhost/

//中止Nginx
/usr/local/nginx/sbin/nginx -s stop

0x2: Nginx配置文件

配置文件能夠看作是Nginx的靈魂,Nginx服務在啓動時會讀入配置文件,然後續幾乎一切動做行爲都是按照配置文件中的指令進行的

#user  nobody;
worker_processes  1;

error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

pid        logs/nginx.pid;

events {
    worker_connections  1024;
} 

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
            root   /usr/local/nginx/html;
            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;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }


    # another virtual host using mix of IP-, name-, and port-based configuration
    #
    #server {
    #    listen       8000;
    #    listen       somename:8080;
    #    server_name  somename  alias  another.alias;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}


    # HTTPS server
    #
    #server {
    #    listen       443 ssl;
    #    server_name  localhost;

    #    ssl_certificate      cert.pem;
    #    ssl_certificate_key  cert.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

    #    location / {
    #        root   html;
    #        index  index.html index.htm;
    #    }
    #}

}

每一個層級能夠有本身的指令(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配置的核心

Relevant Link:

http://tengine.taobao.org/book/chapter_03.html

 

4. Hello World模塊開發

0x1: Nginx模塊工做原理

Nginx自己支持多種模塊,如HTTP模塊、EVENT模塊和MAIL模塊(本文只討論HTTP模塊)
Nginx自己作的工做實際不多,當它接到一個HTTP請求時,它僅僅是經過查找配置文件將這次請求映射到一個location block,而此location中所配置的各個指令則會啓動不一樣的模塊去完成工做,所以模塊能夠看作Nginx真正的勞動工做者。一般一個location中的指令會涉及一個handler模塊和多個filter模塊(多個location能夠複用同一個模塊)

1. handler模塊負責處理請求,完成響應內容的生成
2. filter模塊對響應內容進行處理
//所以Nginx模塊開發分爲handler開發和filter開發(暫考慮load-balancer模塊)

咱們接下來學習一個簡單的Nginx模塊開發全過程,咱們開發一個叫echo的handler模塊,這個模塊功能很是簡單,它接收"echo"指令,指令可指定一個字符串參數,模塊會輸出這個字符串做爲HTTP響應。例如,作以下配置

location /echo {
    echo "hello nginx";
}
//nginx根據conf配置文件來指導其自身的行爲

直觀來看,要實現這個功能須要三步

1. 讀入配置文件中echo指令及其參數
2. 進行HTTP包裝(添加HTTP頭等工做)
3. 將結果返回給客戶端 

0x2: 定義模塊配置結構

首先咱們須要一個結構用於存儲從配置文件中讀進來的相關指令參數,即模塊配置信息結構。根據Nginx模塊開發規則,這個結構的命名規則爲ngx_http_[module-name]_[main|srv|loc]_conf_t。其中main、srv和loc分別用於表示同一模塊在三層block中的配置信息。這裏咱們的echo模塊只須要運行在loc層級下,須要存儲一個字符串參數,所以咱們能夠定義以下的模塊配置 

typedef struct {
    ngx_str_t  ed;
} ngx_http_echo_loc_conf_t;

0x3: 定義指令

一個Nginx模塊每每接收一至多個指令,echo模塊接收一個指令「echo」。Nginx模塊使用一個ngx_command_t數組表示模塊所能接收的全部模塊,其中每個元素表示一個條指令。ngx_command_t是ngx_command_s的一個別稱(Nginx習慣於使用"_s"後綴命名結構體,而後typedef一個同名"_t"後綴名稱做爲此結構體的類型名)
下面是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
};
//指令數組的命名規則爲ngx_http_[module-name]_commands,注意數組最後一個元素要是ngx_null_command結束

參數轉化函數(ngx_http_echo)的代碼爲

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);

    //修改了核心模塊配置(也就是這個location的配置),將其handler替換爲咱們編寫的handler:ngx_http_echo_handler。這樣就屏蔽了此location的默認handler,使用ngx_http_echo_handler產生HTTP響應
    
    clcf->handler = ngx_http_echo_handler;
    
    //調用ngx_conf_set_str_slot轉化echo指令的參數
    ngx_conf_set_str_slot(cf,cmd,conf);
    
    return NGX_CONF_OK;
}

0x4: 建立合併配置信息

接下來繼續學習定義模塊Context,這裏首先須要定義一個ngx_http_module_t類型的結構體變量,命名規則爲ngx_http_[module-name]_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便可

1. create_loc_conf用於初始化一個配置結構體,如爲配置結構體分配內存等工做
2. merge_loc_conf用於將其父block的配置信息合併到此結構體中,也就是實現配置的繼承
//這兩個函數會被Nginx自動調用。注意這裏的命名規則:ngx_http_[module-name]_[create|merge]_[main|srv|loc]_conf 

下面是echo模塊這個兩個Hook函數的代碼

static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_echo_loc_conf_t  *conf;
    //ngx_pcalloc用於在Nginx內存池中分配一塊空間,是pcalloc的一個包裝。使用ngx_pcalloc分配的內存空間沒必要手工free,Nginx會自行管理,在適當是否釋放
    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;
    //create_loc_conf新建一個ngx_http_echo_loc_conf_t,分配內存,並初始化其中的數據,而後返回這個結構的指針
    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, "");
    //merge_loc_conf將父block域的配置信息合併到create_loc_conf新建的配置結構體中
    return NGX_CONF_OK;
}

0x5: 編寫Handler

handler能夠說是模塊中真正實現功能業務邏輯的代碼,它主要有如下四項職責

1. 讀入模塊配置
2. 處理功能業務
3. 產生HTTP header
4. 產生HTTP body 

code

/*
* 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 */
//完成了Nginx模塊各類組件的開發下面就是將這些組合起來了。一個Nginx模塊被定義爲一個ngx_module_t結構
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 */
//handler會接收一個ngx_http_request_t指針類型的參數,這個參數指向一個ngx_http_request_t結構體,此結構體存儲了此次HTTP請求的一些信息
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;
    }
    //設置response header
    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;
    //使用ngx_http_send_header就能夠將頭信息輸出
    rc = ngx_http_send_header(r);
    if(rc != NGX_OK)
    {
        return rc;
    }
    /*
    最後一步也是最重要的一步是輸出Response body,Nginx容許handler一次產生一組輸出,能夠產生屢次,Nginx將輸出組織成一個單鏈表結構
    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表示後面還有元素。由於咱們只有一組數據,因此緩衝區鏈表中只有一個節點,若是須要輸入多組數據可將各組數據放入不一樣緩衝區後插入到鏈表
    緩衝數據準備好後,用ngx_http_output_filter就能夠輸出了(會送到filter進行各類過濾處理)
    */
    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;
}

0x6: Nginx模塊的安裝

Nginx不支持動態連接模塊,因此安裝模塊須要將模塊代碼與Nginx源代碼進行從新編譯。安裝模塊的步驟以下

1. cd  /usr/local/nginx/nginx-master/src/http/modules/
2. mkdir ngx_http_echo_module
3. cd ngx_http_echo_module
4. vim /usr/local/nginx/nginx-master/src/http/modules/ngx_http_echo_module/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"
*/

5. cd /usr/local/nginx/nginx-master
6. ./auto/configure --prefix=/usr/local/nginx/ --add-module=/usr/local/nginx/nginx-master/src/http/modules/ngx_http_echo_module/
7. make
8. make install
9. vim /usr/local/nginx/conf/nginx.conf
//編輯增長 
location /echo {
    echo "hello nginx";
}

10. 重啓
/usr/local/nginx/sbin/nginx -s stop
/usr/local/nginx/sbin/nginx  

11. 訪問
http://121.40.254.73/echo

Relevant Link:

http://blog.codinglabs.org/articles/intro-of-nginx-module-development.html
http://blog.csdn.net/poechant/article/details/7627828
http://bg.biedalian.com/2013/08/09/nginx-hello-world.html

 

Copyright (c) 2015 LittleHann All rights reserved

相關文章
相關標籤/搜索