nginx源码分析之变量
nginx中的變量在nginx中的使用非常的多,正因為變量的存在,使得nginx在配置上變得非常靈活。
我們知道,在nginx的配置文件中,配合變量,我們可以動態的得到我們想要的值。最常見的使用是,我們在寫access_log的格式時,需要用到多很多變量。 而這些變量是如何工作的呢?我們可以輸出哪些變量?我們又怎么才能輸出自己想要的內容呢?當然,我們可能還想知道,如何在我們的模塊里面去使用變量,如何添加變量,獲取變量的值,以及設置變量的內容?如何使用,以及需要注意些什么?
問題一大堆,那接下來,就讓我們一起去一探nginx源碼的秘密。
我要講的內容
1. 變量的分類
站在使用者的角度來看,我們在配置文件中可以看到:
從這里,也解決我們的問題,在配置access_log時,我們可以配置哪些變量,是否是用戶添加的變量,是否是內建變量在ngx_http_core_variables中有,其次,是否是規則變量,另外,如果想輸出自己的內容,那只能寫模塊自己添加一個變量了,或者hack nginx在ngx_http_core_variables中添加一個變量。
more
從nginx內部實現上來看,變量可分為:
我們在模塊里面可以通過ngx_http_add_variable來添加一個變量,在后面的介紹中我們可以看到。而我們添加的變量,最好不要是以這些規則開頭的變量,否則就有可能會覆蓋掉這些規則的變量。
從變量獲取者來看,可以分為索引變量與未索引的變量。
2. 相關結構
接下來,我們就要開始進入源碼的世界了,先看看幾個關鍵結構:
// ngx_variable_value_t即變量的結果,變量的值 typedef struct {unsigned len:28; unsigned valid:1; // 當前變量是否合法unsigned no_cacheable:1; // 當前變量是否可以緩存,緩存過的變量將只會調用一次get_handler函數unsigned not_found:1;// 變量是否找到unsigned escape:1;u_char *data; // 變量的數據 } ngx_variable_value_t; // 變量本身的信息 struct ngx_http_variable_s {ngx_str_t name; // 變量的名稱ngx_http_set_variable_pt set_handler; // 變量的設置函數ngx_http_get_variable_pt get_handler; // 變量的get函數uintptr_t data; // 傳給get與set_handler的值ngx_uint_t flags; // 變量的標志ngx_uint_t index; // 如果有索引,則是變量的索引號 }; // 在ngx_http_core_module的配置文件中保存了所使用的變量信息 typedef struct {ngx_hash_t variables_hash; // 變量的hash表ngx_array_t variables; // 索引變量的數組ngx_hash_keys_arrays_t *variables_keys; // 變量的hash數組 } ngx_http_core_main_conf_t; // 變量在每個請求中的值是不一樣的,也就是說變量是請求相關的 // 所以在ngx_http_request_s中有一個變量數組,主要用于緩存當前請求的變量結果 // 從而可以避免一個變量的多次計數,計算過一次的變量就不用再計算了 // 但里面保存的一定是索引變量的值,是否緩存,也要由變量的特性來決定 struct ngx_http_request_s {ngx_http_variable_value_t *variables; }3. 模塊中操作變量的函數
那么,在模塊中,我們要如何使用一個變量呢?在前面講分類的時候,我們也提到過了,這里再總結并細說一下: 首先,如果要添加一個變量,我們需要調用ngx_http_add_variable函數來添加一個變量。添加時需要指明變量的名稱就行了。
// name: 即變量的名字 // flags: 如果同一個變量要多次添加,則flags應該設置NGX_HTTP_VAR_CHANGEABLE // 否則,多次添加將會提示重復 // flags表示可以是:NGX_HTTP_VAR_CHANGEABLE // NGX_HTTP_VAR_NOCACHEABLE // NGX_HTTP_VAR_INDEXED // NGX_HTTP_VAR_NOHASH ngx_http_variable_t *ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags);然后,要獲取變量,如果要高效一點,我們可以先將該變量放到索引數組里面,通過ngx_http_get_variable_index來添加一個變量的索引:
// name: 即nginx支持的任意變量名 // 返回該變量的索引 ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);不過,要注意的是,添加的變量必須是nginx支持的已存在的變量。即如果是hash過的變量,則一定是通過ngx_http_add_variable添加的變量,否則,一定是規則變量,如”http_host”。當然,在解析配置文件的時候,變量不一定是要先通過ngx_http_add_variable然后才能獲取索引,這個是不需要有順序保證的。nginx會將在最后配置文件解析完成后,去驗證這些索引變量的合法性,在ngx_http_variables_init_vars函數中可以看到,我們在后面具體再分析。 所以,可以看到,獲取索引的操作,一定是要在解析配置文件的過程是進行的, 一旦配置文件解析完成后,索引變量不能再添加。在獲取索引號后,我們需要保存該索引號,以便在后面通過索引號來獲取變量。
那么,索引變量的獲取,可以通過ngx_http_get_indexed_variable與ngx_http_get_flushed_variable來獲取,兩個函數間的區別,我們后面再介紹:
ngx_http_variable_value_t *ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index); ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index);而如果沒有索引過的變量,則只能通過ngx_http_get_variable函數來獲取了。
// key 由ngx_hash_strlow來計算 ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key);可以看到,key是通過ngx_hash_strlow來計算的,所以變量名是沒有大小寫區分的。
最后,通過獲取變量的函數,我們可以看到,變量是與請求相關的,也就是獲取的變量都是與當前請求相關的。
4. 變量的實現源碼及流程
那接下來,我們就來看看nginx在源碼中的實現吧!
初始化:
首先,在數據結構中,我們知道ngx_http_core_main_conf_t中保存了變量相關的一些信息,我們添加的變量key放在cmcf->variables_keys中,而cmcf->variables保存變量的索引結構,cmcf->variables_hash則保存著變量hash過的結構。
ngx_http_add_variable添加變量的時候,會先放到cmcf->variables_keys中,然后在解析完后,再生成hash結構體。
那么,ngx_http_core_module的preconfiguration階段,調用ngx_http_variables_add_core_vars初始化變量的數據結構,然后再添加ngx_http_core_variables結構中的變量。所以可以看出,nginx中內建的變量是在這個數組里面的。 然后在解析其它模塊的配置文件時,會通過ngx_http_add_variable函數來添加變量:
ngx_http_variable_t * ngx_http_add_variable(ngx_conf_t *cf, ngx_str_t *name, ngx_uint_t flags) {// 先檢查變量是否在已添加key = cmcf->variables_keys->keys.elts;for (i = 0; i < cmcf->variables_keys->keys.nelts; i++) {if (name->len != key[i].key.len|| ngx_strncasecmp(name->data, key[i].key.data, name->len) != 0){continue;}v = key[i].value;// 如果已添加,并且是不可變的變量,則提示變量的重復添加// 其它NGX_HTTP_VAR_CHANGEABLE就是為了讓變量的重復添加時不出錯,都指向同一變量if (!(v->flags & NGX_HTTP_VAR_CHANGEABLE)) {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"the duplicate \"%V\" variable", name);return NULL;}// 如果變量已添加,并且有NGX_HTTP_VAR_CHANGEABLE表志,則直接返回return v;}// 添加這個變量v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));v->name.len = name->len;// 注意,變量名不區分大小寫ngx_strlow(v->name.data, name->data, name->len);rc = ngx_hash_add_key(cmcf->variables_keys, &v->name, v, 0);if (rc == NGX_ERROR) {return NULL;}return v; }在添加完變量后,我們需要設置變量的get_handler與set_handler。get_handler是當我們在獲取變量的時候調用的函數,在該函數中,我們需要設置變量的值。而在set_handler則是用于主動設置變量的值。get_handler與set_handler的區別是:get_handler是在變量使用時獲取值,而set_handler則是變量會主動先設置好,在使用的時候就不用再算了。目前,set指令,設置一個變量的值是用的set_handler。 在需要獲取變量的模塊中,可以通過ngx_http_get_variable_index來得到變量的索引,這個函數工作很簡單,就是在ngx_http_core_main_conf_t的variables中添加一個變量,并返回該變量在數組中的索引號。源碼就不展示了。然后,在解析配置文件之后,在ngx_http_block中通過ngx_http_variables_init_vars函數來初始化變量,在ngx_http_variables_init_vars中,會做兩個事情,檢查索引變量,以及初始化變量的hash表。首先,對索引數組中的每一個元素,會先檢查是否在ngx_http_core_main_conf_t的variables_keys中出現,即是否是添加過的,然后再檢查是否是有特定規則的變量,如”http_host”,如果都不是,則說明該變量是不存在的,該索引會對應于一個不存在的變量,所以就會提示錯誤,程序無法啟動。然后,如果變量有設置NGX_HTTP_VAR_NOHASH,則會跳過該變量,不進行hash,再對hash過的變量建立hash表。
在請求中:?當一個請求過來時,在ngx_http_init_request函數中,即請求初始化的時候,會建立一個與ngx_http_core_main_conf_t中的變量索引數組variables大小一樣的數組。r->variables有兩個作用,一是為了緩存變量的值,二是可以在創建子請求時,父請求給子請求傳遞一些信息。注意,變量的值是與當前請求相關的,所以每個請求里面會不一樣。 然后在模塊里面ngx_http_get_indexed_variable和ngx_http_get_flushed_variable,這兩個函數的代碼還是要小講一下:
ngx_http_variable_value_t * ngx_http_get_indexed_variable(ngx_http_request_t *r, ngx_uint_t index) {ngx_http_variable_t *v;ngx_http_core_main_conf_t *cmcf;cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);// 變量已經獲取過了,就不再計算變量的值,直接返回if (r->variables[index].not_found || r->variables[index].valid) {return &r->variables[index];}// 如果變量是初次獲取,則調用變量的get_handler來得到變量值,并緩存到r->variables中去v = cmcf->variables.elts;if (v[index].get_handler(r, &r->variables[index], v[index].data)== NGX_OK){if (v[index].flags & NGX_HTTP_VAR_NOCACHEABLE) {r->variables[index].no_cacheable = 1;}return &r->variables[index];}// 變量獲取失敗,設置為不合法,以及未找到// 注意我們在調用完本函數后,需要檢查函數的返回值以及這兩個屬性r->variables[index].valid = 0;r->variables[index].not_found = 1;return NULL; } ngx_http_variable_value_t * ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_uint_t index) {ngx_http_variable_value_t *v;v = &r->variables[index];if (v->valid) {// 變量已經獲取過了,而且是合法的并且可緩存的,則直接返回if (!v->no_cacheable) {return v;}// 否則,清除標志,并再次獲取變量的值v->valid = 0;v->not_found = 0;}return ngx_http_get_indexed_variable(r, index); }注意:ngx_http_get_flushed_variable會考慮到變量的cache標志,如果變量是可緩存的,則只有在變量是合法的時才返回變量的值,否則重新獲取變量的值。而ngx_http_get_indexed_variable則不管變量是否可緩存,只要獲取過一次了,不管是否成功,則都不會再獲取了。最后,如果是未索引的變量,我們可以通過ngx_http_get_variable函數來得到變量的值。ngx_http_get_variable做的工作:
至此,變量的整個流程差不多就完了,另外還有一個要注意的是,在創建子請求時候的變量。在ngx_http_subrequest函數中,我們可以看到,子請求的variables是直接指向父請求的variables數組的,所以子請求與父請求是共享variables數組的,這樣父子請求就可以傳遞變量的值。但正因為如此,我們在使用父子請求的時候會產生一些問題,如果一個父請求創建多個子請求,他們之間獲取同一個變量時,會有很明顯的干擾,因為每個請求的環境是不一樣的,這樣獲取的值也是不一樣的。
好吧,變量也簡單的介紹了一下。
總結
以上是生活随笔為你收集整理的nginx源码分析之变量的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nginx处理http(http变量篇)
- 下一篇: nginx学习十 ngx_cycle_t