ngx_http_script_run 的作用是從配置文件中獲取配置項的值。
這樣說可能太抽象,我們來舉個例子。
比如:
現在需要從 nginx.conf 中取這樣一個配置項:
tt_signpass “mypass$remote_addr”;
注意,這個配置項的值包含 2 個部分 mypass(靜態字符 / 常量) + $remote_addr(動態參數)。
那 ngx_http_script_run 是怎麼完成這 2 個部分的解析的呢?
先來跟蹤下這個函數:
u_char *
ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value,
void *code_lengths, size_t len, void *code_values)
{
ngx_uint_t i;
ngx_http_script_code_pt code;
ngx_http_script_len_code_pt lcode;
ngx_http_script_engine_t e;
ngx_http_core_main_conf_t *cmcf;
// 獲取 core 模塊的配置項
cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
for (i = 0; i < cmcf->variables.nelts; i++) {
if (r->variables[i].no_cacheable) {
r->variables[i].valid = 0;
r->variables[i].not_found = 0;
}
}
ngx_memzero(&e, sizeof(ngx_http_script_engine_t));
e.ip = code_lengths;
e.request = r;
e.flushed = 1;
// 找到已經設置好的函數,執行並獲得字符長度結果
while (*(uintptr_t *) e.ip) {
lcode = *(ngx_http_script_len_code_pt *) e.ip;
len += lcode(&e);
}
value->len = len;
value->data = ngx_pnalloc(r->pool, len);
if (value->data == NULL) {
return NULL;
}
e.ip = code_values;
e.pos = value->data;
// 找到已經設置好的函數,調用獲取返回值
while (*(uintptr_t *) e.ip) {
code = *(ngx_http_script_code_pt *) e.ip;
code((ngx_http_script_engine_t *) &e);
}
return e.pos;
}
看到這裏,有個疑問:回調函數哪裏來的? 來,直接看
-> ngx_http_script_compile, 它的作用是根據配置項的字符,分離常量和變量,同時準備獲取實際內容的環境和條件。
可能有人會有疑問:
1,爲什麼不馬上獲取?
2,獲取出來的內容不是直接就是字符了麼,還分什麼實際內容。
這都是針對配置項的值可能包含了 $remote_addr 這種動態參數,並且這種動態參數對於每次請求來說,都可能是不一樣的值。所以有這個需求,在需要的地方及時獲取。ngx_http_script_compile 和 ngx_http_script_run 一起, 這 2 個函數的實現我們可以看成是 LazyLoad, 或者是延遲調用這樣的概念。
我們繼續看 ngx_http_script_compile:
ngx_int_t
ngx_http_script_compile(ngx_http_script_compile_t *sc)
{
u_char ch;
ngx_str_t name;
ngx_uint_t i, bracket;
// 初始化存放變量的數組
if (ngx_http_script_init_arrays(sc) != NGX_OK) {
return NGX_ERROR;
}
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) // Perl的正則庫支持,處理類似 $1 的變量模型
...
#endif
// 處理 ${var}www 變量和字符相連的模型,以 { 開始
if (sc->source->data[i] == '{') {
bracket = 1;
if (++i == sc->source->len) {
goto invalid_variable;
}
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];
// 處理 ${var}www 變量和字符相連的模型,以 } 結束
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;
}
// 根據 sc->compile_args 確定 '?' 是帶參數的形式解析,
// 還是認定它本身是常量字符,這裏是針對 ${var}?*** 這種模型來解析
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.data = &sc->source->data[i];
// 在解析常量的時候遇到了 '$' 和 '?'
while (i < sc->source->len) {
if (sc->source->data[i] == '$') {
break;
}
// 此處處理類似 ABC?abc 的模型
if (sc->source->data[i] == '?') {
sc->args = 1;
if (sc->compile_args) {
break;
}
}
i++;
name.len++;
}
sc->size += name.len;
// 對常量的長度和值的獲取,準備回調等環境
if (ngx_http_script_add_copy_code(sc, &name, (i == sc->source->len))
!= NGX_OK)
{
return NGX_ERROR;
}
}
return ngx_http_script_done(sc);
...
}
ngx_http_script_compile 函數是對配置項值解析的流程和框架的預加載,在實際環境中只需要調用 ngx_http_script_run 就可以獲取到配置項實際代表的內容了。
將複雜的邏輯提前,簡化實際運行的處理邏輯,這符合一個高性能服務器的要求。
看到這裏,可能有了一個粗略的瞭解,但是在 ngx_http_script_run 調用的回調到底是哪個呢?
再來看 ngx_http_script_compile 中調用的 2 個函數:
ngx_http_script_add_var_code
ngx_http_script_add_copy_code
來分別跟蹤下:
static ngx_int_t
ngx_http_script_add_var_code(ngx_http_script_compile_t *sc, ngx_str_t *name)
{
ngx_int_t index, *p;
ngx_http_script_var_code_t *code;
index = ngx_http_get_variable_index(sc->cf, name);
if (index == NGX_ERROR) {
return NGX_ERROR;
}
if (sc->flushes) {
p = ngx_array_push(*sc->flushes);
if (p == NULL) {
return NGX_ERROR;
}
*p = index;
}
//爲將要解析的變量,申請空間,並添加到鏈尾
code = ngx_http_script_add_code(*sc->lengths,
sizeof(ngx_http_script_var_code_t), NULL);
if (code == NULL) {
return NGX_ERROR;
}
//添加獲取變量長度的回調函數
code->code = (ngx_http_script_code_pt) ngx_http_script_copy_var_len_code;
code->index = (uintptr_t) index;
code = ngx_http_script_add_code(*sc->values,
sizeof(ngx_http_script_var_code_t),
&sc->main);
if (code == NULL) {
return NGX_ERROR;
}
// 添加獲取參數的內容的回調
code->code = ngx_http_script_copy_var_code;
code->index = (uintptr_t) index;
return NGX_OK;
}
這個函數先爲要解析的變量申請了空間,並附加在全局鏈表尾部,然後爲這個變量長度的計算定義了回調函數 ngx_http_script_copy_var_len_code, 以及爲這個變量內容的獲取定義了回調函數 ngx_http_script_copy_var_code, 那麼其實在ngx_http_script_run 函數中就是通過調用這 2 個回調來獲取變量的值的。
對於常量的獲取其實也是相同的道理。來看下常量的處理函數:
static ngx_int_t
ngx_http_script_add_copy_code(ngx_http_script_compile_t *sc, ngx_str_t *value,
ngx_uint_t last)
{
u_char *p;
size_t size, len, zero;
ngx_http_script_copy_code_t *code;
zero = (sc->zero && last);
len = value->len + zero;
//申請內存,並添加至鏈尾
code = ngx_http_script_add_code(*sc->lengths,
sizeof(ngx_http_script_copy_code_t), NULL);
if (code == NULL) {
return NGX_ERROR;
}
//爲常量長度的獲取,添加回調函數
code->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code;
code->len = len;
size = (sizeof(ngx_http_script_copy_code_t) + len + sizeof(uintptr_t) - 1)
& ~(sizeof(uintptr_t) - 1);
code = ngx_http_script_add_code(*sc->values, size, &sc->main);
if (code == NULL) {
return NGX_ERROR;
}
//爲常量內容的獲取,添加回調函數
code->code = ngx_http_script_copy_code;
code->len = len;
p = ngx_cpymem((u_char *) code + sizeof(ngx_http_script_copy_code_t),
value->data, value->len);
if (zero) {
*p = '\0';
sc->zero = 0;
}
return NGX_OK;
}
OK,相同的結構,偶們不在重複了。
接下來我們來看看獲取變量內容的回調函數的實現 ngx_http_script_copy_var_code 中都幹了些什麼。
void
ngx_http_script_copy_var_code(ngx_http_script_engine_t *e)
{
u_char *p;
ngx_http_variable_value_t *value;
ngx_http_script_var_code_t *code;
code = (ngx_http_script_var_code_t *) e->ip;
e->ip += sizeof(ngx_http_script_var_code_t);
if (!e->skip) {
// e->flushed 標示是否去緩存還是重頭獲取值
if (e->flushed) {
value = ngx_http_get_indexed_variable(e->request, code->index);
} else {
value = ngx_http_get_flushed_variable(e->request, code->index);
}
if (value && !value->not_found) {
p = e->pos;
//嗯,終於找到獲取值的位置了。
e->pos = ngx_copy(p, value->data, value->len);
}
}
}
對於以上代碼,有興趣的可以關注 ngx_http_get_indexed_variable 和 ngx_http_get_flushed_variable 這 2 個函數,不同點在於是否需要獲取事先設置好的 handler 去立即更新數據。 另外 nginx 提供了一些了功能性函數。比如 array, string 的操作,用 C 寫出了面向對象的概念,值得思考。 其中的一些功能函數,比如 ngx_array_push_n , 在數組擴充上是新結構大小的 2 倍(和 Vector 一致,這一點 C/C++ 和 Java都是一樣的, Java 中 ArrayList 是 (×3/2 + 1)),由此可見,編程的思想基本是一致的。
扯遠了,回來總結下,當我們要取一個配置項的值:
tt_signpass “mypass$remote_addr”;
對於常量和變量部分,其實是分開去獲取,然後拼接在一起的。在 ngx_http_script_compile 的實現中,先將常量和變量拆分,然後分別爲他們設置好回調處理函數(長度 + 內容)。比如變量內容的獲取就爲它設置好處理函數 ngx_http_script_copy_var_code。實際的運行環境中,在 ngx_http_script_run 中調用事先在 ngx_http_script_compile 中設置好的回調函數,來獲取該變量具體的值。