前文鏈接:《深入理解Nginx》閱讀與實踐(一):Nginx安裝配置與HelloWorld
HelloWorld的完成意味着已經踏入了nginx的大門,雖然很振奮人心,但在編寫中仍有很多疑惑的存在:nginx.conf的配置項中各個參數是如何讀入程序中的?ngx_command_t如何完成配置項的讀入工作?名稱相同的配置項的沖突如何解決?HelloWorld中的ngx_http_module_t何以稱為模塊的上下文?同時我在讀第4章"配置項的使用"時又有成見:不就是各種瑣碎的參數設置嘛,有什么好讀的?(這個成見來自於UNP中某一章節套接字選項)不過經過仔細閱讀並實踐這部分內容之后,我發現完全八竿子打不着要點。其實這里的配置項的使用,是指將我們在nginx.conf中設置的配置項的參數傳遞到nginx程序的過程。同時,經過這一章,你將學到如何將一個“根據用戶請求的URI返回HelloWorld的模塊”變成一個“可以完成解析各種nginx配置項的模塊”。
一、存放配置項的值的數據結構
先來看看文中介紹的保存配置參數的數據結構ngx_http_mytest_conf_t:
typedef struct { ngx_str_t my_str; ngx_int_t my_num; ngx_flag_t my_flag; size_t my_size; ngx_array_t* my_str_array; ngx_array_t* my_keyval; off_t my_off; ngx_msec_t my_msec; time_t my_sec; ngx_bufs_t my_bufs; ngx_uint_t my_enum_seq; ngx_uint_t my_bitmask; ngx_uint_t my_access; ngx_path_t* my_path; } ngx_http_mytest_conf_t;
與原書提到的問題(為什么不用全局變量來存儲)不同,我的問題是,既然想要統一用一個結構體存放各種各樣的配置項如整數、標記(非0即1)、字符串、時間等等,為什么不用一個聯合從而節省空間?帶着疑問繼續往下讀才會發現:這里的數據結構是為了便於后面說明預設配置項解析方法而定義的,它能處理的配置項是下面這種形式:
name [ngx_str_t] [ngx_int_t] ... [ngx_path_t*] [[ngx_str_t][ngx_int_t]]
這種配置項的名稱后面可以跟着14種參數以及在后文定義的myconfig的2個參數,這16個參數可以全部出現,也可以只出現其中之一。也即,它的功能並不是僅僅處理配置項名+單一參數的配置項,而是所有可能形式的配置項。這樣復雜在所難免,如果實際使用的配置項是我們所能控制的有限幾種,沒有必要設計這么復雜,正如HelloWorld中mytest配置項后根本就沒有值,只是設立了個遇到該配置項就啟動mytest模塊的函數而已,那時甚至沒有必要設計這么一個保存配置項的值的結構。
二、對配置項的值的操作:讀取與合並
回顧HelloWorld中的代表模塊上下文的ngx_http_module_t結構,之前雖然提到它是模塊的上下文,使得模塊具有自己的特性,但當時把它的8個回調方法都設置成了NULL,也看不出什么名堂。這時《深入理解Nginx》對它做了個回顧並說明,我是這樣理解的:所謂“上文”,就是做preconfiguration的工作、處理nginx.conf的配置項(將所有配置項讀入、建立數據結構保存、各級同名配置項合並);“下文”就是在處理完配置項后調用函數postconfiguration進行一些后續工作。
static ngx_http_module_t ngx_http_mytest_module_ctx = { NULL, /* preconfiguration */ ngx_http_mytest_post_conf, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ ngx_http_mytest_create_loc_conf, /* create location configuration */ ngx_http_mytest_merge_loc_conf /* merge location configuration */ };
為了便於說明,這里只實現了create_loc_conf和merge_loc_conf,前者在nginx.conf中的每一個http{...}、server{...}、location{...}都會調用來為ngx_http_mytest_conf_t賦初值;后者用來合並create_loc_conf生成的所有ngx_http_mytest_conf_t的配置項。
除此以外,為了打印出讀取的配置項的值,需要實現ngx_http_mytest_post_conf,在處理完配置項后調用。這樣,模塊的上下文便寫好了。
關於create_loc_conf、create_srv_conf、create_main_conf的調用時機:
create_loc_conf是遇到每一個http{...}、server{...}、location{...}時;
create_srv_conf是遇到每一個server{...}、location{...}時;
create_main_conf只在遇到http{...}時。
這個細節可以參考原書。
關於merge_srv_conf的調用作用:合並main級別和server級別的配置項,請參考10.2.4。
這時就可以通過設定ngx_command_s來設定配置項的解析方式了,簡單概括就是配置項的名稱、它所能出現的位置、對值處理的回調方法也即解析方法、值存放於之前定義的數據結構的偏移量。這一系列解析方式構成了一個數組,用來解析所有可能出現的配置項的形式。這里原書說的比較詳細,就不再重復。對應14種配置項的值的類型,書上講了14種解析方式和針對自定義配置項的處理方法(對應的回調函數是ngx_conf_set_myconfig)。
這里不得不提一下ngx_conf_t類型。原書中多次出現在函數原型形參中但並未提及,我查了一些資料,它的定義如下:
struct ngx_conf_s { char *name; ngx_array_t *args; ngx_cycle_t *cycle; ngx_pool_t *pool; ngx_pool_t *temp_pool; ngx_conf_file_t *conf_file; ngx_log_t *log; void *ctx; ngx_uint_t module_type; ngx_uint_t cmd_type; ngx_conf_handler_pt handler; char *handler_conf; };
在http://kenby.iteye.com/blog/1169675中提到,“解析配置文件的時候, 用結構體ngx_conf_s來暫時存放指令的參數”。同時查閱nginx源碼,排除掉作為函數形參時的出現,出現在下面幾處,作用和這個描述很類似:
src\core\Ngx_cycle.c
src\event\Ngx_event.c
src\http\Ngx_http.c
src\http\Ngx_http_script.h
src\mail\Ngx_mail.c
那么就暫時不去管它,當作一個中間數據的結構體就行了。
原書以test_str配置項為例,編寫了用於合並test_str類型的ngx_http_mytest_loc_conf()函數,並可以看出當父子配置塊有相同配置項的處理方法:以父配置塊為准或子配置塊為准均可,可以自由選擇,或者進行運算生成。原書后文是更詳細的配置塊合並時的數據結構存儲模型和合並邏輯的概括,值得仔細揣摩。
三、error日志和請求上下文
這一部分沒有什么好寫的,原書已經很詳細了。不過error日志的輸出會在后面用到。
四、把HelloWorld變成配置項輸出
按着第4章的線索,對HelloWorld做出相應修改就能完成這個轉變了。其實整體邏輯組成和第3章圖示差不多,只是具體實現方法不大一樣:
- 定義結構體ngx_http_mytest_conf_t;
- 原先的ngx_module_t類型ngx_http_mytest_module以及config文件保持不變;
- 將原先的所有成員為空的模塊上下文ngx_http_module_t進行修改,添加回調函數create_loc_conf()、merge_loc_conf()、ngx_http_mytest_post_conf()並實現(ngx_http_mytest_post_conf()可以放在最后來進行);
- 為所有值類型編寫解析方式ngx_command_t ngx_http_mytest_commands[]替代原先的,在此之前需要實現解析自定義類型的ngx_conf_set_myconfig()方法;
- 原先用於處理HTTP報文並返回請求的ngx_http_mytest_handler已無用,刪去;
- 編寫ngx_http_mytest_post_conf()取的不是這個模塊的信息而是已傳遞給ngx_http_module和ngx_http_core_module的數據因此需要在源碼中extern進二者。ngx_http_mytest_post_conf()的具體實現有些超出本章內容,不做詳解。
- 將nginx.conf修改,在各個配置塊中增加test_str,重新編譯運行源碼,就可以在屏幕上看到相應的輸出了,如下圖所示:

完整的源碼可以在《深入理解Nginx》作者的頁面上進行下載:http://nginx.weebly.com/31034203632830430721.html
