nginx源码分析之变量
nginx中的變量在nginx中的使用非常的多,正因?yàn)樽兞康拇嬖?#xff0c;使得nginx在配置上變得非常靈活。
我們知道,在nginx的配置文件中,配合變量,我們可以動(dòng)態(tài)的得到我們想要的值。最常見的使用是,我們?cè)趯慳ccess_log的格式時(shí),需要用到多很多變量。 而這些變量是如何工作的呢?我們可以輸出哪些變量?我們又怎么才能輸出自己想要的內(nèi)容呢?當(dāng)然,我們可能還想知道,如何在我們的模塊里面去使用變量,如何添加變量,獲取變量的值,以及設(shè)置變量的內(nèi)容?如何使用,以及需要注意些什么?
問題一大堆,那接下來,就讓我們一起去一探nginx源碼的秘密。
我要講的內(nèi)容
1. 變量的分類
站在使用者的角度來看,我們?cè)谂渲梦募锌梢钥吹?#xff1a;
從這里,也解決我們的問題,在配置access_log時(shí),我們可以配置哪些變量,是否是用戶添加的變量,是否是內(nèi)建變量在ngx_http_core_variables中有,其次,是否是規(guī)則變量,另外,如果想輸出自己的內(nèi)容,那只能寫模塊自己添加一個(gè)變量了,或者h(yuǎn)ack nginx在ngx_http_core_variables中添加一個(gè)變量。
more
從nginx內(nèi)部實(shí)現(xiàn)上來看,變量可分為:
我們?cè)谀K里面可以通過ngx_http_add_variable來添加一個(gè)變量,在后面的介紹中我們可以看到。而我們添加的變量,最好不要是以這些規(guī)則開頭的變量,否則就有可能會(huì)覆蓋掉這些規(guī)則的變量。
從變量獲取者來看,可以分為索引變量與未索引的變量。
2. 相關(guān)結(jié)構(gòu)
接下來,我們就要開始進(jìn)入源碼的世界了,先看看幾個(gè)關(guān)鍵結(jié)構(gòu):
// ngx_variable_value_t即變量的結(jié)果,變量的值 typedef struct {unsigned len:28; unsigned valid:1; // 當(dāng)前變量是否合法unsigned no_cacheable:1; // 當(dāng)前變量是否可以緩存,緩存過的變量將只會(huì)調(diào)用一次get_handler函數(shù)unsigned not_found:1;// 變量是否找到unsigned escape:1;u_char *data; // 變量的數(shù)據(jù) } ngx_variable_value_t; // 變量本身的信息 struct ngx_http_variable_s {ngx_str_t name; // 變量的名稱ngx_http_set_variable_pt set_handler; // 變量的設(shè)置函數(shù)ngx_http_get_variable_pt get_handler; // 變量的get函數(shù)uintptr_t data; // 傳給get與set_handler的值ngx_uint_t flags; // 變量的標(biāo)志ngx_uint_t index; // 如果有索引,則是變量的索引號(hào) }; // 在ngx_http_core_module的配置文件中保存了所使用的變量信息 typedef struct {ngx_hash_t variables_hash; // 變量的hash表ngx_array_t variables; // 索引變量的數(shù)組ngx_hash_keys_arrays_t *variables_keys; // 變量的hash數(shù)組 } ngx_http_core_main_conf_t; // 變量在每個(gè)請(qǐng)求中的值是不一樣的,也就是說變量是請(qǐng)求相關(guān)的 // 所以在ngx_http_request_s中有一個(gè)變量數(shù)組,主要用于緩存當(dāng)前請(qǐng)求的變量結(jié)果 // 從而可以避免一個(gè)變量的多次計(jì)數(shù),計(jì)算過一次的變量就不用再計(jì)算了 // 但里面保存的一定是索引變量的值,是否緩存,也要由變量的特性來決定 struct ngx_http_request_s {ngx_http_variable_value_t *variables; }3. 模塊中操作變量的函數(shù)
那么,在模塊中,我們要如何使用一個(gè)變量呢?在前面講分類的時(shí)候,我們也提到過了,這里再總結(jié)并細(xì)說一下: 首先,如果要添加一個(gè)變量,我們需要調(diào)用ngx_http_add_variable函數(shù)來添加一個(gè)變量。添加時(shí)需要指明變量的名稱就行了。
// name: 即變量的名字 // flags: 如果同一個(gè)變量要多次添加,則flags應(yīng)該設(shè)置NGX_HTTP_VAR_CHANGEABLE // 否則,多次添加將會(huì)提示重復(fù) // 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);然后,要獲取變量,如果要高效一點(diǎn),我們可以先將該變量放到索引數(shù)組里面,通過ngx_http_get_variable_index來添加一個(gè)變量的索引:
// name: 即nginx支持的任意變量名 // 返回該變量的索引 ngx_int_t ngx_http_get_variable_index(ngx_conf_t *cf, ngx_str_t *name);不過,要注意的是,添加的變量必須是nginx支持的已存在的變量。即如果是hash過的變量,則一定是通過ngx_http_add_variable添加的變量,否則,一定是規(guī)則變量,如”http_host”。當(dāng)然,在解析配置文件的時(shí)候,變量不一定是要先通過ngx_http_add_variable然后才能獲取索引,這個(gè)是不需要有順序保證的。nginx會(huì)將在最后配置文件解析完成后,去驗(yàn)證這些索引變量的合法性,在ngx_http_variables_init_vars函數(shù)中可以看到,我們?cè)诤竺婢唧w再分析。 所以,可以看到,獲取索引的操作,一定是要在解析配置文件的過程是進(jìn)行的, 一旦配置文件解析完成后,索引變量不能再添加。在獲取索引號(hào)后,我們需要保存該索引號(hào),以便在后面通過索引號(hào)來獲取變量。
那么,索引變量的獲取,可以通過ngx_http_get_indexed_variable與ngx_http_get_flushed_variable來獲取,兩個(gè)函數(shù)間的區(qū)別,我們后面再介紹:
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函數(shù)來獲取了。
// key 由ngx_hash_strlow來計(jì)算 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來計(jì)算的,所以變量名是沒有大小寫區(qū)分的。
最后,通過獲取變量的函數(shù),我們可以看到,變量是與請(qǐng)求相關(guān)的,也就是獲取的變量都是與當(dāng)前請(qǐng)求相關(guān)的。
4. 變量的實(shí)現(xiàn)源碼及流程
那接下來,我們就來看看nginx在源碼中的實(shí)現(xiàn)吧!
初始化:
首先,在數(shù)據(jù)結(jié)構(gòu)中,我們知道ngx_http_core_main_conf_t中保存了變量相關(guān)的一些信息,我們添加的變量key放在cmcf->variables_keys中,而cmcf->variables保存變量的索引結(jié)構(gòu),cmcf->variables_hash則保存著變量hash過的結(jié)構(gòu)。
ngx_http_add_variable添加變量的時(shí)候,會(huì)先放到cmcf->variables_keys中,然后在解析完后,再生成hash結(jié)構(gòu)體。
那么,ngx_http_core_module的preconfiguration階段,調(diào)用ngx_http_variables_add_core_vars初始化變量的數(shù)據(jù)結(jié)構(gòu),然后再添加ngx_http_core_variables結(jié)構(gòu)中的變量。所以可以看出,nginx中內(nèi)建的變量是在這個(gè)數(shù)組里面的。 然后在解析其它模塊的配置文件時(shí),會(huì)通過ngx_http_add_variable函數(shù)來添加變量:
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;// 如果已添加,并且是不可變的變量,則提示變量的重復(fù)添加// 其它NGX_HTTP_VAR_CHANGEABLE就是為了讓變量的重復(fù)添加時(shí)不出錯(cuò),都指向同一變量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;}// 添加這個(gè)變量v = ngx_palloc(cf->pool, sizeof(ngx_http_variable_t));v->name.len = name->len;// 注意,變量名不區(qū)分大小寫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; }在添加完變量后,我們需要設(shè)置變量的get_handler與set_handler。get_handler是當(dāng)我們?cè)讷@取變量的時(shí)候調(diào)用的函數(shù),在該函數(shù)中,我們需要設(shè)置變量的值。而在set_handler則是用于主動(dòng)設(shè)置變量的值。get_handler與set_handler的區(qū)別是:get_handler是在變量使用時(shí)獲取值,而set_handler則是變量會(huì)主動(dòng)先設(shè)置好,在使用的時(shí)候就不用再算了。目前,set指令,設(shè)置一個(gè)變量的值是用的set_handler。 在需要獲取變量的模塊中,可以通過ngx_http_get_variable_index來得到變量的索引,這個(gè)函數(shù)工作很簡(jiǎn)單,就是在ngx_http_core_main_conf_t的variables中添加一個(gè)變量,并返回該變量在數(shù)組中的索引號(hào)。源碼就不展示了。然后,在解析配置文件之后,在ngx_http_block中通過ngx_http_variables_init_vars函數(shù)來初始化變量,在ngx_http_variables_init_vars中,會(huì)做兩個(gè)事情,檢查索引變量,以及初始化變量的hash表。首先,對(duì)索引數(shù)組中的每一個(gè)元素,會(huì)先檢查是否在ngx_http_core_main_conf_t的variables_keys中出現(xiàn),即是否是添加過的,然后再檢查是否是有特定規(guī)則的變量,如”http_host”,如果都不是,則說明該變量是不存在的,該索引會(huì)對(duì)應(yīng)于一個(gè)不存在的變量,所以就會(huì)提示錯(cuò)誤,程序無法啟動(dòng)。然后,如果變量有設(shè)置NGX_HTTP_VAR_NOHASH,則會(huì)跳過該變量,不進(jìn)行hash,再對(duì)hash過的變量建立hash表。
在請(qǐng)求中:?當(dāng)一個(gè)請(qǐng)求過來時(shí),在ngx_http_init_request函數(shù)中,即請(qǐng)求初始化的時(shí)候,會(huì)建立一個(gè)與ngx_http_core_main_conf_t中的變量索引數(shù)組variables大小一樣的數(shù)組。r->variables有兩個(gè)作用,一是為了緩存變量的值,二是可以在創(chuàng)建子請(qǐng)求時(shí),父請(qǐng)求給子請(qǐng)求傳遞一些信息。注意,變量的值是與當(dāng)前請(qǐng)求相關(guān)的,所以每個(gè)請(qǐng)求里面會(huì)不一樣。 然后在模塊里面ngx_http_get_indexed_variable和ngx_http_get_flushed_variable,這兩個(gè)函數(shù)的代碼還是要小講一下:
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);// 變量已經(jīng)獲取過了,就不再計(jì)算變量的值,直接返回if (r->variables[index].not_found || r->variables[index].valid) {return &r->variables[index];}// 如果變量是初次獲取,則調(diào)用變量的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];}// 變量獲取失敗,設(shè)置為不合法,以及未找到// 注意我們?cè)谡{(diào)用完本函數(shù)后,需要檢查函數(shù)的返回值以及這兩個(gè)屬性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) {// 變量已經(jīng)獲取過了,而且是合法的并且可緩存的,則直接返回if (!v->no_cacheable) {return v;}// 否則,清除標(biāo)志,并再次獲取變量的值v->valid = 0;v->not_found = 0;}return ngx_http_get_indexed_variable(r, index); }注意:ngx_http_get_flushed_variable會(huì)考慮到變量的cache標(biāo)志,如果變量是可緩存的,則只有在變量是合法的時(shí)才返回變量的值,否則重新獲取變量的值。而ngx_http_get_indexed_variable則不管變量是否可緩存,只要獲取過一次了,不管是否成功,則都不會(huì)再獲取了。最后,如果是未索引的變量,我們可以通過ngx_http_get_variable函數(shù)來得到變量的值。ngx_http_get_variable做的工作:
至此,變量的整個(gè)流程差不多就完了,另外還有一個(gè)要注意的是,在創(chuàng)建子請(qǐng)求時(shí)候的變量。在ngx_http_subrequest函數(shù)中,我們可以看到,子請(qǐng)求的variables是直接指向父請(qǐng)求的variables數(shù)組的,所以子請(qǐng)求與父請(qǐng)求是共享variables數(shù)組的,這樣父子請(qǐng)求就可以傳遞變量的值。但正因?yàn)槿绱?#xff0c;我們?cè)谑褂酶缸诱?qǐng)求的時(shí)候會(huì)產(chǎn)生一些問題,如果一個(gè)父請(qǐng)求創(chuàng)建多個(gè)子請(qǐng)求,他們之間獲取同一個(gè)變量時(shí),會(huì)有很明顯的干擾,因?yàn)槊總€(gè)請(qǐng)求的環(huán)境是不一樣的,這樣獲取的值也是不一樣的。
好吧,變量也簡(jiǎn)單的介紹了一下。
總結(jié)
以上是生活随笔為你收集整理的nginx源码分析之变量的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nginx处理http(http变量篇)
- 下一篇: Nginx基本数据结构之ngx_hash