Nginx深入詳解之過濾模組
一、模組簡介
過濾(filter)模組是過濾響應頭和內容的模組,可以對回覆的頭和內容進行處理。它的處理時間在獲取回覆內容之後,向用戶傳送響應之前。它的處理過程分為兩個階段,過濾HTTP回覆和頭部和主體,在這兩個階段可以分別對頭部和主體進行修改。下面函式就是分別對頭部和主體進行過濾的函式,所有模組的響應內容要返回給客戶端,都必須呼叫這兩個介面:
ngx_http_top_header_filter(r);
ngx_http_top_body_filter(r, in);
二、執行順序
過濾模組的呼叫是有順序的,這在編譯的時候就決定了,控制編譯的指令碼位於auto/modules中,當編譯完Nginx之後,可以在objs目錄下看到一個ngx_modules.c的檔案,開啟這個檔案能看到如下資料結構:
ngx_module_t *ngx_modules[] = { ... &ngx_http_write_filter_module, &ngx_http_header_filter_module, &ngx_http_chunked_filter_module, &ngx_http_range_header_filter_module, &ngx_http_gzip_filter_module, &ngx_http_postpone_filter_module, &ngx_http_ssi_filter_module, &ngx_http_charset_filter_module, &ngx_http_userid_filter_module, &ngx_http_headers_filter_module, &ngx_http_copy_filter_module, &ngx_http_range_body_filter_module, &ngx_http_not_modified_filter_module, NULL };
從write_filter到not_modified_filter,模組的執行順序是反向的,即最早執行的是not_modified_filter,然後各個模組依次執行。所有第三方模組只能加入到copy_filter和headers_filter模組之間執行。每個filter模組的處理函式賦值給全域性變數ngx_http_top_header_filter,而前一個filter模組的處理函式賦值給區域性變數ngx_http_next_header_filter,響應頭和響應體過濾函式的執行順序如下圖:
三、模組編譯
Nginx可以很方便地加入第三方過濾模組。在過濾模組的目錄里加入config檔案,內容如下:
typedef struct ngx_chain_s ngx_chain_t;
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
一般buffer結構體可以表示一種記憶體,記憶體的起始和結束地址分別用start和end表示,pos和last表示實際的內容。如果內容已經處理過了,pos的位置就可以往後移動。如果讀取到新的內容,last的位置就會往後移動。所有buffer可以在多次呼叫過程中使用。如果last等於end,就說明這塊記憶體已經用完了。如果pos等於last,說明記憶體已經處理完了,下面是一個簡單的示意圖,說明buffer中指標的用法:
五、過濾函式
1、響應頭過濾函式
響應頭過濾函式的主要用處是處理HTTP響應的頭,可以根據實際情況對於響應頭進行修改或者新增刪除。響應頭過濾函式先於響應體過濾函式,而且只調用一次,所以一般是做過濾模組的初始化工作,響應頭過濾函式的入口如下:
ngx_int_t ngx_http_send_header(ngx_http_request_t *r)
{
...
return ngx_http_top_header_filter(r);
}
該函式在向客戶端傳送回覆的時候呼叫,返回值一般為NGX_OK、NGX_ERROR和NGX_AGAIN,分別表示處理成功、失敗和未完成。
ngx_http_header_filter_module過濾模組把所有的HTTP頭組合成一個完成的buffer,最終由ngx_http_write_filter_module過濾模組把所有的HTTP頭組合成一個完成的buffer,最終由ngx_http_write_filter_module過濾模組把buffer輸出。
2、響應體過濾函式
響應體過濾模組是過濾響應主題的函式。對於每個請求,函式ngx_http_top_body_filter可能會被執行多次,它的入口函式如下:
ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
ngx_int_t rc;
ngx_connection_t *c;
c = r->connection;
rc = ngx_http_top_body_filter(r, in);
if (rc == NGX_ERROR) {
/* NGX_ERROR may be returned by any filter */
c->error = 1;
}
return rc;
}
對於整個請求的處理階段來說,ngx_http_output_filter的作用就是把響應記憶體過濾,然後發給客戶端,具體模組的響應體過濾函式的格式會類似如下:
static int ngx_http_example_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
...
return ngx_http_next_body_filter(r, in);
}
該函式的返回值一般是NGX_OK、NGX_ERROR和NGX_AGAIN,分別表示處理成功、失敗和未完成。
響應的主體內容就存於單鏈表in,連結串列一般不會太長,有時in引數可能為NULL。in中存有buf結構體中,對於靜態檔案,這個buf大小預設是32K;對於反向代理的應用,這個buf可能是4K或者8K。為了保持記憶體的低消耗,Nginx一般不會分配過大的記憶體,處理的原則是收到一定的資料,就傳送出去。
在響應體過濾模組中,尤其要注意的是buf的標誌位,具體可以參看圖2.如果buf中包含last標誌,說明是最後一塊buf,可以直接輸出並結束請求了。如果有flush標誌,說明這塊buf需要馬上輸出,不能快取。如果整塊buffer經過處理完以後,沒有資料了,你可以把buffer的sync標誌置上,表示只是同步的用處。當所有的過濾模組都處理完畢時,在最後的write_filter模組中,Nginx會將in輸出鏈拷貝到r->out輸出鏈的末尾,然後呼叫sendfile或者writev介面輸出。由於Nginx是非阻塞的socket介面,寫操作並不一定會成功,可能會有部分資料還殘存在r->out。在下次的呼叫中,Nginx會繼續嘗試傳送,直至成功。
六、子請求
Nginx過濾模組一大特色就是可以發出子請求,即在過濾響應內容的時候,你可以傳送新的請求,Nginx會根據你呼叫的先後順序,將多個回覆的內容拼接成正常的響應主體。一個簡單的例子可以參考addtion模組。當Nginx發出子請求時,就會呼叫ngx_http_subrequest函式,將子請求插入福請求的r->postponed連結串列中。子請求會在主請求執行完畢時獲得依次呼叫。子請求同樣會有一個請求所有的生存期和處理過程,也會進入過濾模組流程。
關鍵點是在postpone_filter模組中,它會拼接主請求和子請求的響應內容。r->postponed按次序儲存有父請求和子請求,它是一個連結串列,如果前面一個請求未完成,那麼後一個請求內容就不會輸出。當前一個請求完成時並輸出時,後一個請求才可輸出,當所有的子請求都完成時,所有的響應內容也就輸出完畢了。