ngx_http_proxy_pass:
...
/* n是對proxy_pass中出現的變量進行計數,看有幾個變量要處理 */
if (n) {
ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
sc.cf = cf;
sc.source = url; // url就是proxy_pass後面配置的字符串,含有變量
/*
* 這裏需要交代的是,nginx這套”運行時處理機“在處理結果的處理上是分長度和內容
* 兩部分的,也就是說,獲得變量實際值對應長度和內容的處理子(也就是處理函數),分別
* 保存在lengths和values中。
*/
sc.lengths = &plcf->proxy_lengths;
sc.values = &plcf->proxy_values;
sc.variables = n;
/* 這兩個值是作爲一次compile的結束標記,在lengths和values的最後添加一個空處理子,即NULL指針。
* 後面會講到:在運行時處理時,即處理lengths和values的時候,碰到NULL,這次處理過程就宣告結束 */
sc.complete_lengths = 1;
sc.complete_values = 1;
if (ngx_http_script_compile(&sc) != NGX_OK) {
return NGX_CONF_ERROR;
}
return NGX_CONF_OK;
}
接下來看ngx_http_script_compile的核心處理部分:
for (i = 0; i < sc->source->len; /* void */ ) {
name.len = 0;
if (sc->source->data[i] == '$') {
// 以'$'結尾,是有錯誤的,因爲這裏處理的都是變量,而不是正則(正則裏面末尾帶$是有意思的)
if (++i == sc->source->len) {
goto invalid_variable;
}
#if (NGX_PCRE)
{
ngx_uint_t n;
/*
* 注意,在這裏所謂的變量有兩種,一種是$後面跟字符串的,一種是跟數字的。
* 這裏判斷是否是數字形式的變量。
*/
if (sc->source->data[i] >= '1' && sc->source->data[i] <= '9') {
n = sc->source->data[i] - '0';
if (sc->captures_mask & (1 << n)) {
sc->dup_capture = 1;
}
/*
* 在sc->captures_mask中將數字對應的位置1,那麼captures_mask的作用是什麼?
* 在後面對sc結構體分析時會提到。
*/
sc->captures_mask |= 1 << n;
if (ngx_http_script_add_capture_code(sc, n) != NGX_OK) {
return NGX_ERROR;
}
i++;
continue;
}
}
#endif
/*
* 這裏是個有意思的地方,舉個例子,假設有個這樣一個配置proxy_pass $host$uritest,
* 我們這裏其實是想用nginx的兩個內置變量,host和uri,但是對於$uritest來說,如果我們
* 不加處理,那麼在函數裏很明顯會將uritest這個整體作爲一個變量,這顯然不是我們想要的。
* 那怎麼辦呢?nginx裏面使用"{}"來把一些變量包裹起來,避免跟其他的字符串混在一起,在此處
* 我們可以這樣用${uri}test,當然變量之後是數字,字母或者下劃線之類的字符纔有必要這樣處理
* 代碼中體現的很明顯。
*/
if (sc->source->data[i] == '{') {
bracket = 1;
if (++i == sc->source->len) {
goto invalid_variable;
}
// name用來保存一個分離出的變量
name.data = &sc->source->data[i];
} else {
bracket = 0;
name.data = &sc->source->data[i];
}
for ( /* void */ ; i < sc->source->len; i++, name.len++) {
ch = sc->source->data[i];
// 在"{}"中的字符串會被分離出來(即break語句),避免跟後面的字符串混在一起
if (ch == '}' && bracket) {
i++;
bracket = 0;
break;
}
/*
* 變量中允許出現的字符,其他字符都不是變量的字符,所以空格是可以區分變量的。
* 這個我們在配置裏經常可以感覺到,而它的原理就是這裏所顯示的了
*/
if ((ch >= 'A' && ch <= 'Z')
|| (ch >= 'a' && ch <= 'z')
|| (ch >= '0' && ch <= '9')
|| ch == '_')
{
continue;
}
break;
}
if (bracket) {
ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0,
"the closing bracket in \"%V\" "
"variable is missing", &name);
return NGX_ERROR;
}
if (name.len == 0) {
goto invalid_variable;
}
// 變量計數
sc->variables++;
// 得到一個變量,做處理
if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) {
return NGX_ERROR;
}
continue;
}
/*
* 程序到這裏意味着一個變量分離出來,或者還沒有碰到變量,一些非變量的字符串,這裏不妨稱爲”常量字符串“
* 這裏涉及到請求參數部分的處理,比較簡單。這個地方一般是在一次分離變量或者常量結束後,後面緊跟'?'的情況
* 相關的處理子在ngx_http_script_add_args_code會設置。
*/
if (sc->source->data[i] == '?' && sc->compile_args) {
sc->args = 1;
sc->compile_args = 0;
if (ngx_http_script_add_args_code(sc) != NGX_OK) {
return NGX_ERROR;
}
i++;
continue;
}
// 這裏name保存一段所謂的”常量字符串“
name.data = &sc->source->data[i];
// 分離該常量字符串
while (i < sc->source->len) {
// 碰到'$'意味着碰到了下一個變量
if (sc->source->data[i] == '$') {
break;
}
/*
* 此處意味着我們在一個常量字符串分離過程中遇到了'?',如果我們不需要對請求參數做特殊處理的話,
* 即sc->compile_args = 0,那麼我們就將其作爲常量字符串的一部分來處理。否則,當前的常量字符串會
* 從'?'處,截斷,分成兩部分。*/
if (sc->source->data[i] == '?') {
sc->args = 1;
if (sc->compile_args) {
break;
}
}
i++;
name.len++;
}
// 一個常量字符串分離完畢,sc->size統計整個字符串(即sc->source)中,常量字符串的總長度
sc->size += name.len;
// 常量字符串的處理子由這個函數來設置
if (ngx_http_script_add_copy_code(sc, &name, (i == sc->source->len))
!= NGX_OK)
{
return NGX_ERROR;
}
}
// 本次compile結束,做一些收尾善後工作。
return ngx_http_script_done(sc);
上面我們分析了一個compile過程的主要工作,很顯然,還有細節沒有討論到。在compile過程中,共需要處理4類:$1這樣的capture變量,普通的變量($uri),args變量,常量(即常量字符串)。其實這些變量的處理過程總體來說並不算多麻煩,而有些細節確實難點。我們這裏總結下,之前的博客有對變量和腳本引擎機制做過探討了,這裏把一下沒有談論到的難點和細節,或者之前不是太清楚的分析再來探討一下,希望你我都能有所收穫。
對於流程,一般gdb跟一下,配合debug日誌,基本上可以理清,難點就在於一些結構中的成員,特別是有些標記位的使用,卻是貫穿整個系統,在理解上有不少難度,這是我們這裏討論的重點,對於這些地方搞懂了,流程就不是什麼大問題了。
首先看ngx_http_script_compile_t結構,這個結構在compile的時候被使用過。
typedef struct {
ngx_conf_t *cf; // 配置信息
ngx_str_t *source; // 需要compile的字符串
/*
* 保存普通變量在變量表中的index,關於什麼是變量表,後面會討論
*/
ngx_array_t **flushes;
ngx_array_t **lengths; // 處理變量長度的處理子數組
ngx_array_t **values; // 處理變量內容的處理子數組
ngx_uint_t variables; // 普通變量的個數,而非其他三種(args變量,$n變量以及常量字符串)
/*
* 下面三個變量放在一起討論,他們都跟pcre的正則處理相關,這三個用到的地方比較少
*/
ngx_uint_t ncaptures; // 當前處理時,出現的$n變量的最大值,如配置的最大爲$3,那麼ncaptures就等於3
/*
* 以位移的形式保存$1,$2...$9等變量,即響應位置上置1來表示,主要的作用是爲dup_capture準備,
* 正是由於這個mask的存在,才比較容易得到是否有重複的$n出現。
*/
ngx_uint_t captures_mask;
/*
* 這個標記位主要在rewrite模塊裏使用,在ngx_http_rewrite中,
* if (sc.variables == 0 && !sc.dup_capture) {
* regex->lengths = NULL;
* }
* 沒有重複的$n,那麼regex->lengths被置爲NULL,這個設置很關鍵,在函數
* ngx_http_script_regex_start_code中就是通過對regex->lengths的判斷,來做不同的處理,
* 因爲在沒有重複的$n的時候,可以通過正則自身的captures機制來獲取$n,一旦出現重複的,
* 那麼pcre正則自身的captures並不能滿足我們的要求,我們需要用自己handler來處理。
*/
unsigned dup_capture:1;
ngx_uint_t size; // 待compile的字符串中,”常量字符串“的總長度
/*
* 對於main這個成員,有許多要挖掘的東西。main一般用來指向一個
* ngx_http_script_regex_code_t的結構,那麼這個main到底起到了什麼作用呢?
* 這裏有對它進行分析。
*/
void *main;
unsigned compile_args:1; // 是否需要處理請求參數
unsigned complete_lengths:1; // 是否設置lengths數組的終止符,即NULL
unsigned complete_values:1; // 是否設置values數組的終止符
unsigned zero:1; // values數組運行時,得到的字符串是否追加'\0'結尾
unsigned conf_prefix:1; // 是否在生成的文件名前,追加路徑前綴
unsigned root_prefix:1; // 同conf_prefix
unsigned args:1; // 待compile的字符串中是否發現了'?'
} ngx_http_script_compile_t;
在函數ngx_http_script_add_var_code中,用到了ngx_http_core_main_conf_t(後面以cmcf代替)中的variables,即所謂的全局變量數組,
由於是cmcf,以爲着在所有的server塊,location塊,包括upstream塊裏面,都是可見的,即一方修改,便會在其他地方呈現出變化的道理。
在各個module中的preconfiguration函數裏,都會將該module預設的一些全局變量,放到cmcf->variables_keys中。另外一個重要的成員就是
cmcf->variables,前面提到cmcf->variables_keys是所有預設的變量(和通過set指令設置的),而cmcf->variables則是配置中實際用到的變量。放到cmcf->variables中的變量實際上是先佔個位置,這些變量的更多信息,來源於cmcf->variables_keys,所以在配置解析結束之後,通過ngx_http_variables_init_vars函數來填充這個變量的各個重要信息。
在r中,也有一個variables成員,它是個數組,而且數組成員的個數跟cmcf->variabels是一樣的,區別在於cmcf->variabels的成員類型是:
struct ngx_http_variable_s {
ngx_str_t name; /* 變量的字符串值 */
ngx_http_set_variable_pt set_handler; /* 使用變量中的值設置request的某個成員的值 */
ngx_http_get_variable_pt get_handler; /* 根據request中成員(如uri,args等)的值來設置,r->variables中對應變量的內容 */
uintptr_t data; /* 在set和get操作中使用,一般是r中某個成員在request結構中的offset */
ngx_uint_t flags; /* 一些在set和get中控制特定動作的標誌,後面會講到 */
ngx_uint_t index; /* 某個變量在r->variabels或者cmcf->variabels中數組中的下標 */
};
而r->variables的成員類型是:
typedef struct {
unsigned len:28; /* 變量值的長度 */
unsigned valid:1; /* 變量是否有效 */
unsigned no_cacheable:1; /* 變量是否是可緩存的,一般來說,某些變量在第一次得到變量值後,後面再次用到時,可以直接使用上
* 次的值,而對於一些所謂的no_cacheable的變量,則需要在每次使用的時候,都要通過get_handler之
* 類操作,再次獲取
*/
unsigned not_found:1; /* 變量沒有找到,一般是指某個變量沒用能夠通過get獲取到其變量值 */
unsigned escape:1; /* 變量值是否需要作轉義處理*/
u_char *data; /* 變量值 */
} ngx_variable_value_t;
這兩個結構的關係很密切,一個所謂變量,一個所謂變量值,所以nginx建立在變量上的這套處理機制,就像一個預定好的方程,類似的運算過程,以不同的變量值來運行,獲得我們想要的不同結果。