Nginx丟棄http包體怎么處理

nginx丟棄http包體處理實例詳解

http框架丟棄http請求包體和上一篇文章http框架接收包體, 都是由http框架提供的兩個方法,供http各個模塊調用,從而決定對包體做什么處理。是選擇丟棄還是接收,都是由模塊決定的。例如靜態資源模塊,如果接收到來自瀏覽器的get請求,請求某個文件時,則直接返回這個文件內容給瀏覽器就可以了。沒有必要再接收包體數據,get請求實際上也不會有包體。因此靜態資源模塊將調用http框架提供的丟棄包體函數進行丟包處理。

??????? 相比接收包體過程, 丟棄包體操作就簡單很多了,至少不需要把包體存放到http結構中的request_body緩沖區,也不需要考慮包體是否只存放到內存,或者只存放到文件中等問題, 框架接收完包體后就直接丟棄了。丟棄包體由三部分組成:

??????? (1) http模塊首次調用框架提供的ngx_http_discard_request_body函數,做些初始化操作。例如如果一次操作無法丟棄所有包體 ,則需要重新把讀事件注冊到epoll中,這樣再次調度執行時,能夠繼續執行丟包操作。再者,調用實際的丟包函數ngx_http_read_discarded_request_body進行丟棄包體操作。

??????? (2)如果一次操作無法丟棄所有包體,則在事件再次被調度時,繼續接收剩余的包體數據,然后丟棄。

??????? (3)實際的丟包處理,也就是接收包體后,直接丟棄。

Nginx丟棄http包體怎么處理

??????? 從圖中可以看出這三個過程中,丟包流程是一個公共的功能。也就是說不管http模塊調用ngx_http_discard_request_body函數開始進行丟包處理,還是一次調度沒有接收完全部包體時,由ngx_http_discarded_request_body_handler負責丟棄剩余的包體操作, 都會調用公共的丟包函數ngx_http_read_discarded_request_body進行接收包體后直接丟棄操作。

一、丟包初始化流程

??????? ngx_http_discard_request_body是被http模塊調用,用于丟棄包體的函數。對于模塊來講是一個透明的操作。也就是說模塊只需要調用這個接口就可以丟棄http請求包體,而不需要知道http框架是如何實現這個接口的。縱使框架一次調度沒有丟棄完所有包體,下一次調度執行時會再次進行丟包操作,但對模塊來說,他們是不知道的。

//功能:?丟棄http包體的首次回調函數,如果一次性不能全部接收完成并丟棄,則設置? //??讀事件的回調為ngx_http_discarded_request_body_handler? ngx_int_t?ngx_http_discard_request_body(ngx_http_request_t?*r)? {? ? ?//需要丟棄的包體不用考慮超時問題? ?if?(rev->timer_set)? ?{? ??ngx_del_timer(rev);? ?}? ? ?//包體長度小于等于0,則直接返回。表示丟棄包體? ?//如果已經接收過包體了,這時也不需要在接收。通常情況下get請求沒有包體,因此包體長度為0? ?if?(r->headers_in.content_length_n?request_body)? ?{? ??return?ngx_ok;? ?}? ? ?size?=?r->header_in->last?-?r->header_in->pos;? ?//已經預先接收了部分包體? ?if?(size)? ?{? ??//包體未全部接收完成? ??if?(r->headers_in.content_length_n?>?size)? ??{? ???r->header_in->pos?+=?size;? ???r->headers_in.content_length_n?-=?size;? ? ??}? ??else? ??{? ???//包體已經全部接收? ???r->header_in->pos?+=?(size_t)?r->headers_in.content_length_n;? ???r->headers_in.content_length_n?=?0;? ???return?ngx_ok;? ??}? ?}? ? ?//設置后續讀事件的回調? ?r->read_event_handler?=?ngx_http_discarded_request_body_handler;? ? ?//注冊讀事件回調,插入到epoll? ?ngx_handle_read_event(rev,?0));? ?? ?//接收包體內容? ?if?(ngx_http_read_discarded_request_body(r)?==?ngx_ok)? ?{? ??//表示已經接收到完整的包體了,將延遲關閉清0? ??r->lingering_close?=?0;? ? ?}? ?else? ?{? ??//表示需要多次調度才能完成丟棄包體這個操作,于是把引用計數加1,防止這邊在丟棄包體,而其他? ??//事件卻已經讓請求意外銷毀? ??r->count++;? ??//標識為正在丟棄包體? ??r->discard_body?=?1;? ?}? ? ?return?ngx_ok;? }

???????? 在接收http請求頭部時,如果也順便接收了http包體數據,這個時候就沒有必要繼續執行剩余的操作,丟棄包體成功,函數直接返回。如果一次調度沒有丟棄完所有包體,則會設置http請求結構ngx_http_request_s的讀事件read_event_handler為:ngx_http_discarded_request_body_handler, 下一次被調度時由這個函數負責丟棄剩余的包體。因此ngx_http_discard_request_body只會被http模塊首次調用。

??????? 函數也會調用實際的丟包函數ngx_http_read_discarded_request_body開始進行接收包體后直接丟棄處理。

二、丟包處理?

?????? ngx_http_read_discarded_request_body函數負責接收來自客戶端的包體數據,然后再丟棄。因此對于模塊而言,就是丟棄包體操作,但對于框架而言,丟棄包體操作其實就是接收包體操作, 只不過接收后的包體數據沒有交給模塊使用而已。為什么框架要接收包體后再直接丟棄呢? 豈不是多此一舉。其實不然,之所以這樣做是有原因的。假設某個不健壯的客戶端瀏覽器使用阻塞的方法向nginx服務器發送了http包體數據, 如果nginx框架不接收的話,會導致客戶端瀏覽器超時沒有反應,從而導致客戶端瀏覽器關閉這個連接。因此nginx的http框架要先從內核中接收來自客戶端的包體數據, 但這些數據對于模塊而言是沒有用的,因此接收后的這些數據會直接被丟棄。

//功能:?從內核中讀取數據到nginx中,nginx不對收到的數據進行處理。相當于丟棄包體? static?ngx_int_t?ngx_http_read_discarded_request_body(ngx_http_request_t?*r)? {? ?//用于接收包體的臨時緩沖區? ?u_char?buffer[ngx_http_discard_buffer_size];? ? ?for?(?;;?)? ?{? ??//已經全部丟棄成功? ??if?(r->headers_in.content_length_n?==?0)? ??{? ???//設置丟棄后的讀事件回調,再有讀事件時,不做任何處理? ???r->read_event_handler?=?ngx_http_block_reading;? ???return?ngx_ok;? ??}? ??? ??//從內核中接收包體到臨時緩沖區? ??n?=?r->connection->recv(r->connection,?buffer,?size);? ? ??//更新剩余需要接收的包體大小? ??r->headers_in.content_length_n?-=?n;? ?}? }

??????? 函數內部只是使用一個臨時的緩沖區變量存放每次接收來自內核的包體數據。并沒有把這部分數據保存到http請求結構中的request_body緩沖區。因此包體數據沒有交給http模塊,相當于被丟棄了。在所有包體從內核中接收完成時,設置http請求結構ngx_http_request_s的讀事件read_event_handler回調設置為: ngx_http_block_reading, 表示再收到來自客戶端的數據,則不進行任何處理了。因為已經接收完所有的包體數據,也就不需要理會來自客戶端瀏覽器的其它數據。

三、丟棄剩余的包體

??????? ngx_http_discarded_request_body_handler用于在一次調度中沒有丟棄完所有包體,則該函數會表調用,用于丟棄剩余的包體。函數內部也會調用實際的丟棄包體函數,進行接收包體然后丟棄操作。nginx服務器做了一個優化處理,會設置一個總超時時間,如果超過這個時間都還沒有丟棄完全部的包體,則會關閉這個連接。這是一種對服務器保護的措施,避免長時間的丟包操作占用服務器資源。

//功能:?第1次未能全部丟棄包體時,該函數被調用。之后有讀事件時,該函數被調用? void?ngx_http_discarded_request_body_handler(ngx_http_request_t?*r)? {? ?//檢測延遲關閉時間,如果總時長超過了lingering_time,則不再接收任何包體,這是一個總時間。? ?//總超時后,將直接光比連接? ?if?(r->lingering_time)? ?{? ??timer?=?(ngx_msec_t)?(r->lingering_time?-?ngx_time());? ??//已經到達了延遲關閉時間? ??if?(timer?discard_body?=?0;?? ???//延遲關閉開關清0? ???r->lingering_close?=?0;? ???ngx_http_finalize_request(r,?ngx_error);? ???return;? ??}? ? ?}? ? ?//接收包體后丟棄? ?rc?=?ngx_http_read_discarded_request_body(r);? ?//表示包體已經全部丟棄? ?if?(rc?==?ngx_ok)? ?{? ??r->discard_body?=?0;??//包體已經全部接收完? ??r->lingering_close?=?0;??//清空延遲關閉標志? ??ngx_http_finalize_request(r,?ngx_done);? ??return;? ?}? }

??????? ngx_http_discarded_request_body_handler這個函數是怎么被事件對象調用的呢? 在前面的文章已經分析了,ngx_connection_s讀事件的回調設置為ngx_http_request_handler。?? 因此在讀事件發生時,會回調請求結構的讀回調。如果還不是不清楚這個調用過程,可以參考:

static?void?ngx_http_request_handler(ngx_event_t?*ev)? {? ?//如果同時發生讀寫事件,則只有寫事件才會觸發。寫事件優先級更高? ?if?(ev->write)?? ?{? ??r->write_event_handler(r);?//在函數ngx_http_handler設置為ngx_http_core_run_phases? ?}? ?else? ?{? ??r->read_event_handler(r);?//在函數ngx_http_process_request設置為ngx_http_block_reading? ?}? }

? 版權聲明
THE END
喜歡就支持一下吧
點贊6 分享