目錄
- 1. 概述
- 2. 動態變量的分類
- 2.1 按照變量名的確定性來分類
- 2.2 按照變量聲明的來源分類
- 2.3 按照是否可以變更分類
- 2.4 按照是否可以緩存分類
- 2.5 按照變量的索引方式分類
- 3. 變量的使用
- 3.1 聲明一個變量
- 3.1.1 支撐變量聲明的nginx關鍵結構體
- 3.1.2 在配置文件中聲明
- 3.1.3 在http的核心模塊中聲明
- 3.1.4 在模塊中聲明
- 3.1.5 是否還有第四種聲明變量的方式:
- 3.2 索引一個變量
- 3.3 獲取變量值
1. 概述
?? nginx提供了非常強大的動態變量的機制,通過動態變量,我們可以控制nginx的行為(如限流、acl驗證、if判斷、url rewrite等),可以輸出nginx的請求信息和運行狀態(如nginx的log中使用的動態變量。
?? nginx中的動態變量都是以$開頭后面緊跟變量名的方式來使用的,可以在nginx配置文件中使用,也可以在openresty的lua腳本中使用,更可以在自研nginx模塊中使用其他模塊聲明的變量。
?? nginx本身聲明了非常多的內置變量,可以供我們使用,當然,nginx的內核框架也不妨礙我們定義自己的動態變量,開放給其他模塊(譬如日志模塊)使用。
??本文將從源碼層面來學習和了解nginx動態變量的使用方法和運行機制,以便對nginx的動態變量機制有一個比較深入的理解。在繼續下文之前,本文對分析的范圍故且做一個限定,本文只討論nginx http模塊下的動態變量機制,對于stream模塊,mail模塊等不在本文討論范圍之內。
2. 動態變量的分類
2.1 按照變量名的確定性來分類
?? 按照聲明變量的時候變量名是否確定來分類,可以分為:
- 普通變量 : 這種變量變量名提前可知,如:$remote_addr $body_bytes_sent等。
- 前綴匹配型變量 :這種變量變量名提前不可知,聲明的時候只能確定變量名的前綴,如:$http_host, $http_referer等。總共包括http_、 sent_http_ 、 upstream_http_、 cookie_或者
arg_共5種類型的前綴變量。
2.2 按照變量聲明的來源分類
?? 按照聲明變量的來源來分類,可以分為:
- 核心變量:由ngx_http_core_module和ngx_http_upstream_module定義的變量,如:$connect_host、 $connect_port、$upstream_addr等。
- 模塊變量:由擴展模塊聲明的內置變量,如:ngx_http_slice_module聲明的$slice_range,ngx_http_proxy_module聲明的$proxy_host等,還包括其他第三方模塊聲明的變量。
- 配置文件變量:由nginx配置文件通過set指令聲明的變量。
2.3 按照是否可以變更分類
?? 按照變量是否可以被非聲明它的模塊變更來分類,可以分為:
- 可變更變量:不能由其他模塊修改,如 $remote_addr $body_bytes_sent等。
- 不可變更變量:可以由其他模塊修改,如通過set指令聲明的變量。
nginx本身提供的內置變量決定部分是不可變更的。
2.4 按照是否可以緩存分類
?? 按照聲明的變量是可以緩存,可以分為:
- 不可緩存變量
- 可以緩存變量
??這里再解釋一下,而變量值緩存的地方就是在ngx_http_request_t中,也就是說每個變量值從nginx的框架上來說是屬于某個ngx_http_request_t實例的,這個可以通過動態變量的獲取函數的定義得到明證,如下:
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_variable_value_t *
ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key);
??以上三個版本的動態變量獲取函數都是需要將ngx_http_request_t的指針作為上下文參數傳入的。當變量被聲明為可以緩存,那么在某個http request在求值完成后將緩存到ngx_http_request_t,下次在該請求的某個處理環節還需要這個變量的值的時候就直接從緩存中獲取了而不需要再進行求值操作,從而可以優化nginx的處理性能。
2.5 按照變量的索引方式分類
?? 按照變量的索引方式分類, 可以分為:
- hash 變量: 通過哈希表用名字進行查找的變量類型
- 不可hash 變量:不把這個變盤 has h 到散列表中
?? 因為nginx非常注重性能,對內存的使用也極其“摳門”的應用。如果某個模塊設計了 一個可選變量提供給其他模塊使用,并且要求如果有其他模塊使用該變量
就必須通過數組索引的方式再使用(即不能調用 ngx_http_get_variable方法來獲取變
量值),這樣,這個變量就不用浪費散列表的存儲空間了。
3. 變量的使用
?? 和強類型編程語言一樣,nginx對變量的使用是分為兩個階段的,即變量聲明階段和變量讀寫階段。
?? 變量的聲明階段定義了變量的一些屬性,包括名字、是否可緩存標記、是否可修改標記、是否可哈希標記,以及遍歷讀寫的回調函數等信息。
?? 變量的讀寫階段才會真正給變量分配對應的存儲空間用來存儲變量值存儲。
??區別于正常的編程語言,nginx變量只能支持字符串類型。
??為了加速變量的讀寫操作,nginx也會將需要用到的變量放到數組里面,通過數組的下標可以直接對變量的值進行讀寫操作,避免每次都需要通過hash表進行名字查找。
3.1 聲明一個變量
3.1.1 支撐變量聲明的nginx關鍵結構體
??在詳細闡述如何進行變量的聲明前,有必要對支撐nginx變量機制中的相關結構體進行說明。先來看一下ngx_http_variable_s結構體的定義,它表示了一個nginx動態變量的聲明:
struct ngx_http_variable_s {/* 聲明的變量名稱,不包含第一個$字符 */ngx_str_t name; /* must be first to build the hash *//* 設置變量值的回調函數 */ngx_http_set_variable_pt set_handler;/* 獲取變量值的回調函數 */ngx_http_get_variable_pt get_handler;/* 變量聲明模塊自定義的上下文信息 */uintptr_t data;/* 變量的屬性 包括: NGX_HTTP_VAR_CHANGEABLE NGX_HTTP_VAR_NOCACHEABLE NGX_HTTP_VAR_INDEXED NGX_HTTP_VAR_NOHASH NGX_HTTP_VAR_WEAK NGX_HTTP_VAR_PREFIX 等標記*/ngx_uint_t flags;/* 如果變量被索引到數組中了,它在數組中的序號 */ngx_uint_t index;
};
??在ngx_http_core_main_conf_t也有對變量聲明相關的類型定義,源碼如下:
typedef struct {ngx_array_t servers; /* ngx_http_core_srv_conf_t */ngx_http_phase_engine_t phase_engine;ngx_hash_t headers_in_hash;ngx_hash_t variables_hash;ngx_array_t variables; /* ngx_http_variable_t */ngx_array_t prefix_variables; /* ngx_http_variable_t */ngx_uint_t ncaptures;ngx_uint_t server_names_hash_max_size;ngx_uint_t server_names_hash_bucket_size;ngx_uint_t variables_hash_max_size;ngx_uint_t variables_hash_bucket_size;ngx_hash_keys_arrays_t *variables_keys;ngx_array_t *ports;ngx_http_phase_t phases[NGX_HTTP_LOG_PHASE + 1];
} ngx_http_core_main_conf_t;
??在ngx_http_core_main_conf_t的定義中,首先需要關注的是variables_keys,它會把nginx初始化期間聲明的所有變量都注冊在里面,在配置文件解析完成并且完成postconfiguration后,就會調用ngx_http_variables_init_vars,將nginx運行過程中將來會使用到的動態變量索引到variables數組中,同時將可哈希的變量添加到variables_hash哈希表中。
3.1.2 在配置文件中聲明
?? 在配置文件的http塊及其各子塊中,可以通過set指令來聲明并為一個動態變量設置值,這個是大家日常用得最多的自定義變量的方式。我們可以看看ngx_http_rewrite_module的ngx_http_rewrite_set函數的實現,它負責解析set指令。函數源碼如下:
static char *
ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{ngx_http_rewrite_loc_conf_t *lcf = conf;ngx_int_t index;ngx_str_t *value;ngx_http_variable_t *v;ngx_http_script_var_code_t *vcode;ngx_http_script_var_handler_code_t *vhcode;value = cf->args->elts;/* 判斷傳入的第一個參數的第一個字符是否為$,如果不是說明不是有效的變量名,則報錯 */if (value[1].data[0] != '$') {ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,"invalid variable name \"%V\"", &value[1]);return NGX_CONF_ERROR;}value[1].len--;value[1].data++;/* 如果變量已經存在,并且變量屬性聲明為可以緩存,則返回原來已經聲明的變量,否則則創建一個新的變量。*/v = ngx_http_add_variable(cf, &value[1],NGX_HTTP_VAR_CHANGEABLE|NGX_HTTP_VAR_WEAK);if (v == NULL) {return NGX_CONF_ERROR;}/* 獲取上面添加的變量的數組索引下標 */index = ngx_http_get_variable_index(cf, &value[1]);if (index == NGX_ERROR) {return NGX_CONF_ERROR;}/* 如果沒有設置對應的get_handler,那么對它設置默認值ngx_http_rewrite_var */if (v->get_handler == NULL) {v->get_handler = ngx_http_rewrite_var;v->data = index;}if (ngx_http_rewrite_value(cf, lcf, &value[2]) != NGX_CONF_OK) {return NGX_CONF_ERROR;}/* 如果v->set_handler不為空,說明是其他模塊聲明的變量并且可以允許修改,向動態script引擎中添加對應的動態腳本執行指令代碼,該代碼用于在rewrite階段對上面的變量進行賦值。*/if (v->set_handler) {vhcode = ngx_http_script_start_code(cf->pool, &lcf->codes,sizeof(ngx_http_script_var_handler_code_t));if (vhcode == NULL) {return NGX_CONF_ERROR;}vhcode->code = ngx_http_script_var_set_handler_code;vhcode->handler = v->set_handler;vhcode->data = v->data;return NGX_CONF_OK;}/* 如果v->set_handler為空,意味著是set指令聲明的變量,向動態script引擎中添加對應的動態腳本執行指令代碼,該代碼用于在rewrite階段對上面的變量進行賦值。*/vcode = ngx_http_script_start_code(cf->pool, &lcf->codes,sizeof(ngx_http_script_var_code_t));if (vcode == NULL) {return NGX_CONF_ERROR;}vcode->code = ngx_http_script_set_var_code;vcode->index = (uintptr_t) index;return NGX_CONF_OK;
}
3.1.3 在http的核心模塊中聲明
?? nginx的http內核默認內置了大量的動態變量,在ngx_http_core_module的preconfiguration階段,會聲明這些內置動態變量,對應調用的函數是ngx_http_core_preconfiguration,而ngx_http_core_preconfiguration又轉而調用ngx_http_variables_add_core_vars來聲明動態變量,聲明的動態變量將都放在cmcf->variables_keys中,實現代碼如下:
ngx_int_t
ngx_http_variables_add_core_vars(ngx_conf_t *cf)
{ngx_http_variable_t *cv, *v;ngx_http_core_main_conf_t *cmcf;cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);/* 初始化variables_keys,variables_keys用來存放所有聲明的動態變量列表 */cmcf->variables_keys = ngx_pcalloc(cf->temp_pool,sizeof(ngx_hash_keys_arrays_t));if (cmcf->variables_keys == NULL) {return NGX_ERROR;}cmcf->variables_keys->pool = cf->pool;cmcf->variables_keys->temp_pool = cf->pool;if (ngx_hash_keys_array_init(cmcf->variables_keys, NGX_HASH_SMALL)!= NGX_OK){return NGX_ERROR;}if (ngx_array_init(&cmcf->prefix_variables, cf->pool, 8,sizeof(ngx_http_variable_t))!= NGX_OK){return NGX_ERROR;}/*ngx_http_core_variables定義了內置動態變量的列表, 通過遍歷該列表,向nginx變量管理框架添加所有這些變量。*/for (cv = ngx_http_core_variables; cv->name.len; cv++) {v = ngx_http_add_variable(cf, &cv->name, cv->flags);if (v == NULL) {return NGX_ERROR;}*v = *cv;}return NGX_OK;
}
?? 在添加完變量后, 這個函數和ngx_http_rewrite_set有一點不一樣的地方,ngx_http_rewrite_set是有ngx_http_get_variable_index調用的,而ngx_http_variables_add_core_vars卻沒有,為什么?因為ngx_http_variables_add_core_vars只是聲明變量,至于這個變量在運行過程中用或是不用還不能確定,要等到其他模塊或者nginx配置文件有地方引用這個變量的時候才能知道,但是此時因為還沒有解析配置文件是不知道的;而rewrite模塊中的ngx_http_rewrite_set函數對應的set指令,它不光是聲明一個變量,同時也是是對變量進行賦值操作,所以在這里對變量進行了索引操作,變量的索引的具體解析放在3.2節中。
3.1.4 在模塊中聲明
?? 在nginx的擴展模塊或者自研模塊中進行聲明其實和http核心模塊差不多,我們以ngx_http_slice_module模塊為例,關于ngx_http_slice_module模塊的解析可以查看[[nginx slice模塊的源碼分析]],其中slice_range動態變量聲明的源碼如下:
static ngx_int_t
ngx_http_slice_add_variables(ngx_conf_t *cf)
{ngx_http_variable_t *var;var = ngx_http_add_variable(cf, &ngx_http_slice_range_name, 0);if (var == NULL) {return NGX_ERROR;}var->get_handler = ngx_http_slice_range_variable;return NGX_OK;
}
?? 這里聲明了一個名字為slice_range的只讀變量,當然如果需要聲明一個可讀寫變量,只要在調用ngx_http_add_variable的時候傳遞NGX_HTTP_VAR_CHANGEABLE即可,并且給ngx_http_variable_t設置set_handler回調函數,代碼舉例如下:
static ngx_int_t
ngx_http_writable_add_variable(ngx_conf_t *cf)
{ngx_http_variable_t *v;ngx_str_t name = ngx_string( "writable_variable" );v = ngx_http_add_variable( cf, &name, NGX_HTTP_VAR_NOCACHEABLE|NGX_HTTP_VAR_CHANGEABLE );if( v == NULL ){return NGX_ERROR;}v->get_handler = ngx_http_writable_get_variable;v->set_handler = ngx_http_writable_set_variable;v->data = 0;return NGX_OK;
}
3.1.5 是否還有第四種聲明變量的方式:
??上面把nginx添加動態變量聲明的三種方式進行了詳細的說明,那么是不是還有第四中方式呢?我想到的是好像可以通過openresty lua腳本來聲明。那openresty是不是可以在openresty中用lua代碼來聲明變量呢?
??正確的答案是不可以的,openresty只能對已經通過上述三種方式聲明過的變量進行讀寫訪問,不能自己聲明變量,說白了openresty lua模塊自己也是一個nginx中的特殊的模塊,因為動態變量的聲明只能是在nginx配置文件解析前,而openresty lua模塊的執行至少是在配置文件解析之后,而且一般是在http request發生的時候,所以openresty是無法聲明nginx動態變量的。
??至于是不是還有其他方式,大家有知道的,也歡迎大家提供一下。
3.2 索引一個變量
3.3 獲取變量值
深入理解nginx的動態變量機制(完整版)