nginx的腳本引擎(一)


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指令:這個略顯麻煩,但是道理是一樣的,休息,明天接着寫。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM