nginx請求體讀取(二)
2,丟棄請求體
一個模塊想要主動的丟棄客戶端發過的請求體,可以調用nginx核心提供的ngx_http_discard_request_body()接口,主動丟棄的原因可能有很多種,如模塊的業務邏輯壓根不需要請求體 ,客戶端發送了過大的請求體,另外為了兼容http1.1協議的pipeline請求,模塊有義務主動丟棄不需要的請求體。總之為了保持良好的客戶端兼容性,nginx必須主動丟棄無用的請求體。下面開始分析ngx_http_discard_request_body()函數:
[cpp] view plaincopy
- <span style="font-size:18px;">ngx_int_t
- ngx_http_discard_request_body(ngx_http_request_t *r)
- {
- ssize_t size;
- ngx_event_t *rev;
- if (r != r->main || r->discard_body) {
- return NGX_OK;
- }
- if (ngx_http_test_expect(r) != NGX_OK) {
- return NGX_HTTP_INTERNAL_SERVER_ERROR;
- }
- rev = r->connection->read;
- ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body");
- if (rev->timer_set) {
- ngx_del_timer(rev);
- }
- if (r->headers_in.content_length_n <= 0 || r->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;
- if (ngx_handle_read_event(rev, 0) != NGX_OK) {
- return NGX_HTTP_INTERNAL_SERVER_ERROR;
- }
- if (ngx_http_read_discarded_request_body(r) == NGX_OK) {
- r->lingering_close = 0;
- } else {
- r->count++;
- r->discard_body = 1;
- }
- return NGX_OK;
- }
- </span>
由于函數不長,這里把它完整的列出來了,函數的開始同樣先判斷了不需要再做處理的情況:子請求不需要處理,已經調用過此函數的也不需要再處理。接著調用ngx_http_test_expect() 處理http1.1 expect的情況,根據http1.1的expect機制,如果客戶端發送了expect頭,而服務端不希望接收請求體時,必須返回417(Expectation Failed)錯誤。nginx并沒有這樣做,它只是簡單的讓客戶端把請求體發送過來,然后丟棄掉。接下來,函數刪掉了讀事件上的定時器,因為這時本身就不需要請求體,所以也無所謂客戶端發送的快還是慢了,當然后面還會將到,當nginx已經處理完該請求但客戶端還沒有發送完無用的請求體時,nginx會在讀事件上再掛上定時器。
函數同樣還會檢查請求頭中的content-length頭,客戶端如果打算發送請求體,就必須發送content-length頭,同時還會查看其他地方是不是已經讀取了請求體。如果確實有待處理的請求體,函數接著檢查請求頭buffer中預讀的數據,預讀的數據會直接被丟掉,當然如果請求體已經被全部預讀,函數就直接返回了。
接下來,如果還有剩余的請求體未處理,該函數調用ngx_handle_read_event()在事件處理機制中掛載好讀事件,并把讀事件的處理函數設置為ngx_http_discarded_request_body_handler。做好這些準備之后,該函數最后調用ngx_http_read_discarded_request_body()接口讀取客戶端過來的請求體并丟棄。如果客戶端并沒有一次將請求體發過來,函數會返回,剩余的數據等到下一次讀事件過來時,交給ngx_http_discarded_request_body_handler()來處理,這時,請求的discard_body將被設置為1用來標識這種情況。另外請求的引用數(count)也被加1,這樣做的目的是客戶端可能在nginx處理完請求之后仍未完整發送待發送的請求體,增加引用是防止nginx核心在處理完請求后直接釋放了請求的相關資源。
ngx_http_read_discarded_request_body()函數非常簡單,它循環的從鏈接中讀取數據并丟棄,直到讀完接收緩沖區的所有數據,如果請求體已經被讀完了,該函數會設置讀事件的處理函數為ngx_http_block_reading,這個函數僅僅刪除水平觸發的讀事件,防止同一事件不斷被觸發。
再來看一下讀事件的處理函數ngx_http_discarded_request_body_handler,這個函數每次讀事件來時會被調用,先看一下它的源碼:
[cpp] view plaincopy
- <span style="font-size:18px;">void
- ngx_http_discarded_request_body_handler(ngx_http_request_t *r)
- {
- ...
- c = r->connection;
- rev = c->read;
- if (rev->timedout) {
- c->timedout = 1;
- c->error = 1;
- ngx_http_finalize_request(r, NGX_ERROR);
- return;
- }
- if (r->lingering_time) {
- timer = (ngx_msec_t) (r->lingering_time - ngx_time());
- if (timer <= 0) {
- r->discard_body = 0;
- r->lingering_close = 0;
- ngx_http_finalize_request(r, NGX_ERROR);
- return;
- }
- } else {
- timer = 0;
- }
- 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;
- }
- /* rc == NGX_AGAIN */
- if (ngx_handle_read_event(rev, 0) != NGX_OK) {
- c->error = 1;
- ngx_http_finalize_request(r, NGX_ERROR);
- return;
- }
- if (timer) {
- clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
- timer *= 1000;
- if (timer > clcf->lingering_timeout) {
- timer = clcf->lingering_timeout;
- }
- ngx_add_timer(rev, timer);
- }
- }
- </span>
函數一開始就處理了讀事件超時的情況,之前說到在ngx_http_discard_request_body()函數中已經刪除了讀事件的定時器,那么什么時候會設置定時器呢?答案就是在nginx已經處理完該請求,但是又沒有完全將該請求的請求體丟棄的時候(客戶端可能還沒有發送過來),在ngx_http_finalize_connection()函數中,如果檢查到還有未丟棄的請求體時,nginx會添加一個讀事件定時器,它的時長為lingering_timeout指令所指定,默認為5秒,不過這個時間僅僅兩次讀事件之間的超時時間,等待請求體的總時長為lingering_time指令所指定,默認為30秒。這種情況中,該函數如果檢測到超時事件則直接返回并斷開連接。同樣,還需要控制整個丟棄請求體的時長不能超過lingering_time設置的時間,如果超過了最大時長,也會直接返回并斷開連接。
如果讀事件發生在請求處理完之前,則不用處理超時事件,也不用設置定時器,函數只是簡單的調用ngx_http_read_discarded_request_body()來讀取并丟棄數據。
RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成