日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

nginx源码分析之变量

發布時間:2024/2/28 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 nginx源码分析之变量 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

nginx中的變量在nginx中的使用非常的多,正因為變量的存在,使得nginx在配置上變得非常靈活。

我們知道,在nginx的配置文件中,配合變量,我們可以動態的得到我們想要的值。最常見的使用是,我們在寫access_log的格式時,需要用到多很多變量。 而這些變量是如何工作的呢?我們可以輸出哪些變量?我們又怎么才能輸出自己想要的內容呢?當然,我們可能還想知道,如何在我們的模塊里面去使用變量,如何添加變量,獲取變量的值,以及設置變量的內容?如何使用,以及需要注意些什么?

問題一大堆,那接下來,就讓我們一起去一探nginx源碼的秘密。

我要講的內容

  • 變量的分類
  • 相關結構
  • 模塊中操作變量的函數
  • 變量的實現源碼及流程
  • 1. 變量的分類

    站在使用者的角度來看,我們在配置文件中可以看到:

  • set添加的變量(變量名由用戶設定)
  • nginx功能模塊中添加的變量,如geo模塊(變量名由用戶設定)
  • nginx內建的變量(變量名已由nginx設定好,可以看ngx_http_core_variables結構)
  • 有一定規則的變量,如”http_host”等(有相同前綴,表示某一類變量),我們就稱為規則變量吧
  • 從這里,也解決我們的問題,在配置access_log時,我們可以配置哪些變量,是否是用戶添加的變量,是否是內建變量在ngx_http_core_variables中有,其次,是否是規則變量,另外,如果想輸出自己的內容,那只能寫模塊自己添加一個變量了,或者hack nginx在ngx_http_core_variables中添加一個變量。

    more

    從nginx內部實現上來看,變量可分為:

  • hash過的變量
  • 未hash過的變量,變量有設置NGX_HTTP_VAR_NOHASH
  • 未hash過的變量,但有一定規則的變量,如以這些串開頭的:”http_”,”sent_http_”,”upstream_http_”,”cookie_”,”arg_”
  • 我們在模塊里面可以通過ngx_http_add_variable來添加一個變量,在后面的介紹中我們可以看到。而我們添加的變量,最好不要是以這些規則開頭的變量,否則就有可能會覆蓋掉這些規則的變量。

    從變量獲取者來看,可以分為索引變量與未索引的變量。

  • 索引變量,我們通過ngx_http_get_variable_index來獲得一個索引變量的索引號。然后可以通過ngx_http_get_indexed_variable與ngx_http_get_flushed_variable來獲取索引過變量的值。如果要索引某個變量,則只能在配置文件初始化的時候來設置。ngx_http_get_variable_index不會添加一個真正的變量,在配置文件初始化結束時,會檢查該變量的合法性。索引過的變量,將會有緩存等特性(緩存在r->variables中)。
  • 未索引過的變量,則只能通過ngx_http_get_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做的工作:

  • 變量是hash過的,而且變量有索引過,則調用ngx_http_get_flushed_variable來得到變量值。
  • 變量hash過,未索引過,則調用變量的get_handler來獲取變量,注意,此時每次調用變量,都將會調用get_handler來計算變量的值,然后返回該值。注意因為只有索引過的變量的值才會緩存到ngx_http_request_t的variables中去,所以變量的添加方要注意,如果當前變量是可緩存的,要將該變量建立索引,即調用完ngx_http_add_variable后,再調用ngx_http_get_variable_index來將該變量建立索引。
  • 特定規則的變量,”http_”開頭的會調用ngx_http_variable_unknown_header_out函數,”upstream_http_”開頭的會調用ngx_http_upstream_header_variable函數,”cookie_”開頭的會調用ngx_http_variable_cookie函數,”arg_”開頭的會調用ngx_http_variable_argument函數。
  • 變量未找到,設置變量
  • 至此,變量的整個流程差不多就完了,另外還有一個要注意的是,在創建子請求時候的變量。在ngx_http_subrequest函數中,我們可以看到,子請求的variables是直接指向父請求的variables數組的,所以子請求與父請求是共享variables數組的,這樣父子請求就可以傳遞變量的值。但正因為如此,我們在使用父子請求的時候會產生一些問題,如果一個父請求創建多個子請求,他們之間獲取同一個變量時,會有很明顯的干擾,因為每個請求的環境是不一樣的,這樣獲取的值也是不一樣的。

    好吧,變量也簡單的介紹了一下。

    總結

    以上是生活随笔為你收集整理的nginx源码分析之变量的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。