nginx的腳本的語法和shell是很像的,我大致看了一下覺得挺有意思的,就想寫寫記錄一下。我沒看過shell腳本的引擎,不知道nginx腳本引擎和shell腳本引擎像不像,但是我覺得nginx的腳本引擎有點像C和匯編。
ngx_http_script_engine_t這個結構就代表了一段腳本,ip指向的是編譯好的腳本,sp指向的是一塊內存用來存儲腳本運行的時候產生的一些中間值。ip/sp從名字看就已經很像匯編了instruction pointer/stack pointer指令寄存器和棧寄存器呀,當然是我瞎猜的,有時間的話可以查一下官方文檔。代碼段里的各個指令長度不一定相同。
再來說說編譯過程,編譯過程是在nginx_http_script_engine_t建立之前執行的,我先畫出了整個圖是為了更好理解。舉個set指令編譯的的例子,比如你在腳本里有這樣的代碼set $foo helloworld,腳本編譯的步驟如下:
第一步:首先在cmcf->variables_keys和cmcf->variables里增加一個變量foo,這個變量是可寫的。我之前寫的nginx的變量系統里只說了變量的讀取方法,差別不大。
第二步:把ngx_http_script_value_code_t指令放到代碼段里(code字段是一個回調函數,賦值成ngx_http_script_value_code),把ngx_http_script_var_code_t指令放到代碼段里(code字段是一個回調函數,賦值成ngx_http_script_set_var_code)。
第三步:http請求來的時候會在rewrite階段按順序執行ip指向的這一段代碼,也就是執行ngx_http_script_value_code和ngx_http_script_set_var_code函數。
我們看一下這兩個函數做了什么
void
ngx_http_script_value_code(ngx_http_script_engine_t *e)
{ngx_http_script_value_code_t *code;code = (ngx_http_script_value_code_t *) e->ip;e->ip += sizeof(ngx_http_script_value_code_t);e->sp->len = code->text_len;e->sp->data = (u_char *) code->text_data;ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script value: \"%v\"", e->sp);e->sp++;
}
void
ngx_http_script_set_var_code(ngx_http_script_engine_t *e)
{ngx_http_request_t *r;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);r = e->request;e->sp--;r->variables[code->index].len = e->sp->len;r->variables[code->index].valid = 1;r->variables[code->index].no_cacheable = 0;r->variables[code->index].not_found = 0;r->variables[code->index].data = e->sp->data;
}
第一條指令把“helloworld”這個字符串放到了sp里,第二條指令把值從sp里取出來存到了變量系統的foo變量里,任務完成,看起來很簡單。
set指令還可以這樣用set $foo $x$y,這就是所謂的變量插值,過程和上面這個類似,只不過第一條指令是先從變量系統里取出$x和$y的值,再放入sp里。
其他指令和set指令的執行過程類似,把我看到的也寫一下吧
if指令:同樣舉個最簡單的例子if( $host = "www.foo.com" ),編譯的時候依次把ngx_http_script_var_code/ngx_http_script_value_code_t/ngx_http_script_equal_code/ngx_http_script_if_code四條指令放到代碼段里。腳本運行的時候這幾條指令的工作分別是ngx_http_script_var_code把變量host的值取出來放到sp里。ngx_http_script_value_code_t把字符串“www.foo.com”放到sp里。ngx_http_script_equal_code比較sp里存的兩個值是否相等并把兩個值清除掉,相等就在sp里寫入“1”,不相等就寫入“0”(比較完以后這兩個值就沒用了,清除掉這兩個值并且寫入結果很像C里函數調用的過程)。ngx_http_script_if_code檢查sp里的值是不是“0”,不是“0”說明條件為真繼續執行之后的腳本,是“0”說明條件為假就會跳過這一段代碼執行ngx_http_script_if_code_t結構里next偏移之后的代碼。所有的代碼都是在一個代碼段里,不會因為有if把代碼做嵌套,只不過會用next跳來跳去。
有一點需要注意如果if在location里if體里可以做一些location的配置,比如root之類的。當NGX_HTTP_REWRITE_PHASE階段執行腳本的時候會把新的loc_conf賦值給r->loc_conf,這個一定要注意是NGX_HTTP_REWRITE_PHASE階段而不是NGX_HTTP_FIND_CONFIG_PHASE階段,設置loc_conf一般情況是在NGX_HTTP_FIND_CONFIG_PHASE階段,但是這次不是。
void
ngx_http_script_if_code(ngx_http_script_engine_t *e)
{ngx_http_script_if_code_t *code;code = (ngx_http_script_if_code_t *) e->ip;ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script if");e->sp--;if (e->sp->len && (e->sp->len != 1 || e->sp->data[0] != '0')) {if (code->loc_conf) {e->request->loc_conf = code->loc_conf;ngx_http_update_location_config(e->request);}e->ip += sizeof(ngx_http_script_if_code_t);return;}ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,"http script if: false");e->ip += code->next;
}
return指令:這個是比較簡單的,腳本執行到這個指令就直接返回了,參數了可以帶數據,例如return 200 helloworld,此外還可以重定向return 302 http://www.nginx.org。
break指令:粗暴的結束目前的腳本,但是有一點要注意,如果break指令在location里面,他并不會影響location其他字段的設置,因為他們在不同的階段執行。比如說設置如下的配置文件
location / {root html;break;index index.html;}
?這一點都不會影響你的index指令,他們不在同一階段,index是在NGX_HTTP_FIND_CONFIG_PHASE階段,break是在NGX_HTTP_REWRITE_PHASE階段,就像if指令里說的那樣。
rewrite指令:這個略顯麻煩,但是道理是一樣的,休息,明天接著寫。