日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Nginx 過濾模塊的分析

 

過濾模塊的分析

相關結構體

ngx_chain_t 結構非常簡單,是一個單向鏈表:

 typedef struct ngx_chain_s ngx_chain_t;
 struct ngx_chain_s {
 ngx_buf_t *buf;
 ngx_chain_t *next;
 };

在過濾模塊中,所有輸出的內容都是通過一條單向鏈表所組成。這種單向鏈表的設計,正好應和了 Nginx 流式的輸出模式。每次 Nginx 都是讀到一部分的內容,就放到鏈表,然后輸出出去。這種設計的好處是簡單,非阻塞,但是相應的問題就是跨鏈表的內容操作非常麻煩,如果需要跨鏈表,很多時候都只能緩存鏈表的內容。

單鏈表負載的就是 ngx_buf_t,這個結構體使用非常廣泛,先讓我們看下該結構體的代碼:

 struct ngx_buf_s {
 u_char *pos; /* 當前buffer真實內容的起始位置 */
 u_char *last; /* 當前buffer真實內容的結束位置 */
 off_t file_pos; /* 在文件中真實內容的起始位置 */
 off_t file_last; /* 在文件中真實內容的結束位置 */
 u_char *start; /* buffer內存的開始分配的位置 */
 u_char *end; /* buffer內存的結束分配的位置 */
 ngx_buf_tag_t tag; /* buffer屬于哪個模塊的標志 */
 ngx_file_t *file; /* buffer所引用的文件 */
 /* 用來引用替換過后的buffer,以便當所有buffer輸出以后,
 * 這個影子buffer可以被釋放。
 */
 ngx_buf_t *shadow; 
 /* the buf's content could be changed */
 unsigned temporary:1;
 /*
 * the buf's content is in a memory cache or in a read only memory
 * and must not be changed
 */
 unsigned memory:1;
 /* the buf's content is mmap()ed and must not be changed */
 unsigned mmap:1;
 unsigned recycled:1; /* 內存可以被輸出并回收 */
 unsigned in_file:1; /* buffer的內容在文件中 */
 /* 馬上全部輸出buffer的內容, gzip模塊里面用得比較多 */
 unsigned flush:1;
 /* 基本上是一段輸出鏈的最后一個buffer帶的標志,標示可以輸出,
 * 有些零長度的buffer也可以置該標志
 */
 unsigned sync:1;
 /* 所有請求里面最后一塊buffer,包含子請求 */
 unsigned last_buf:1;
 /* 當前請求輸出鏈的最后一塊buffer */
 unsigned last_in_chain:1;
 /* shadow鏈里面的最后buffer,可以釋放buffer了 */
 unsigned last_shadow:1;
 /* 是否是暫存文件 */
 unsigned temp_file:1;
 /* 統計用,表示使用次數 */
 /* STUB */ int num;
 };

一般 buffer 結構體可以表示一塊內存,內存的起始和結束地址分別用 start 和 end 表示,pos 和 last 表示實際的內容。如果內容已經處理過了,pos 的位置就可以往后移動。如果讀取到新的內容,last 的位置就會往后移動。所以 buffer 可以在多次調用過程中使用。如果 last 等于 end,就說明這塊內存已經用完了。如果 pos 等于 last,說明內存已經處理完了。下面是一個簡單的示意圖,說明 buffer 中指針的用法:

Nginx 過濾模塊的分析

 

響應頭過濾函數

響應頭過濾函數主要的用處就是處理 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,分別表示處理成功,失敗和未完成。

你可以把 HTTP 響應頭的存儲方式想象成一個 hash 表,在 Nginx 內部可以很方便地查找和修改各個響應頭部,ngx_http_header_filter_module 過濾模塊把所有的 HTTP 頭組合成一個完整的 buffer,最終 ngx_http_write_filter_module 過濾模塊把 buffer 輸出。

按照前一節過濾模塊的順序,依次講解如下:

Nginx 過濾模塊的分析

 

響應體過濾函數

響應體過濾函數是過濾響應主體的函數。ngx_http_top_body_filter 這個函數每個請求可能會被執行多次,它的入口函數是 ngx_http_output_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 可以被一般的靜態處理模塊調用,也有可能是在 upstream 模塊里面被調用,對于整個請求的處理階段來說,他們處于的用處都是一樣的,就是把響應內容過濾,然后發給客戶端。

具體模塊的響應體過濾函數的格式類似這樣:

 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一般不會分配過大的內存,處理的原則是收到一定的數據,就發送出去。一個簡單的例子,可以看看Nginx的chunked_filter模塊,在沒有 content-length 的情況下,chunk 模塊可以流式(stream)的加上長度,方便瀏覽器接收和顯示內容。

在響應體過濾模塊中,尤其要注意的是 buf 的標志位,完整描述可以在“相關結構體”這個節中看到。如果 buf 中包含 last 標志,說明是最后一塊 buf,可以直接輸出并結束請求了。如果有 flush 標志,說明這塊 buf 需要馬上輸出,不能緩存。如果整塊 buffer 經過處理完以后,沒有數據了,你可以把 buffer 的 sync 標志置上,表示只是同步的用處。

當所有的過濾模塊都處理完畢時,在最后的 write_fitler 模塊中,Nginx 會將 in 輸出鏈拷貝到 r->out 輸出鏈的末尾,然后調用 sendfile 或者 writev 接口輸出。由于 Nginx 是非阻塞的 socket 接口,寫操作并不一定會成功,可能會有部分數據還殘存在 r->out。在下次的調用中,Nginx 會繼續嘗試發送,直至成功。

發出子請求

Nginx 過濾模塊一大特色就是可以發出子請求,也就是在過濾響應內容的時候,你可以發送新的請求,Nginx 會根據你調用的先后順序,將多個回復的內容拼接成正常的響應主體。一個簡單的例子可以參考 addition 模塊。

Nginx 是如何保證父請求和子請求的順序呢?當 Nginx 發出子請求時,就會調用 ngx_http_subrequest 函數,將子請求插入父請求的 r->postponed 鏈表中。子請求會在主請求執行完畢時獲得依次調用。子請求同樣會有一個請求所有的生存期和處理過程,也會進入過濾模塊流程。

關鍵點是在 postpone_filter 模塊中,它會拼接主請求和子請求的響應內容。r->postponed 按次序保存有父請求和子請求,它是一個鏈表,如果前面一個請求未完成,那后一個請求內容就不會輸出。當前一個請求完成時并輸出時,后一個請求才可輸出,當所有的子請求都完成時,所有的響應內容也就輸出完畢了。

一些優化措施

Nginx 過濾模塊涉及到的結構體,主要就是 chain 和 buf,非常簡單。在日常的過濾模塊中,這兩類結構使用非常頻繁,Nginx采用類似 freelist 重復利用的原則,將使用完畢的 chain 或者 buf 結構體,放置到一個固定的空閑鏈表里,以待下次使用。

比如,在通用內存池結構體中,pool->chain 變量里面就保存著釋放的 chain。而一般的 buf 結構體,沒有模塊間公用的空閑鏈表池,都是保存在各模塊的緩存空閑鏈表池里面。對于 buf 結構體,還有一種 busy 鏈表,表示該鏈表中的 buf 都處于輸出狀態,如果 buf 輸出完畢,這些 buf 就可以釋放并重復利用了。

功能函數名chain 分配ngx_alloc_chain_linkchain 釋放ngx_free_chainbuf 分配ngx_chain_get_free_bufbuf 釋放ngx_chain_update_chains

過濾內容的緩存

由于 Nginx 設計流式的輸出結構,當我們需要對響應內容作全文過濾的時候,必須緩存部分的 buf 內容。該類過濾模塊往往比較復雜,比如 sub,ssi,gzip 等模塊。這類模塊的設計非常靈活,我簡單講一下設計原則:

  1. 輸入鏈 in 需要拷貝操作,經過緩存的過濾模塊,輸入輸出鏈往往已經完全不一樣了,所以需要拷貝,通過 ngx_chain_add_copy 函數完成。
  2. 一般有自己的 free 和 busy 緩存鏈表池,可以提高 buf 分配效率。
  3. 如果需要分配大塊內容,一般分配固定大小的內存卡,并設置 recycled 標志,表示可以重復利用。
  4. 原有的輸入 buf 被替換緩存時,必須將其 buf->pos 設為 buf->last,表明原有的 buf 已經被輸出完畢。或者在新建立的 buf,將 buf->shadow 指向舊的 buf,以便輸出完畢時及時釋放舊的 buf。

分享到:
標簽:Nginx
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定