詳解Nginx的配置函數(shù)對于請求體的讀取
nginx核心本身不會主動讀取請求體,這個工作是交給請求處理階段的模塊來做,但是nginx核心提供了ngx_http_read_client_request_body()接口來讀取請求體,另外還提供了一個丟棄請求體的接口-ngx_http_discard_request_body(),在請求執(zhí)行的各個階段中,任何一個階段的模塊如果對請求體感興趣或者希望丟掉客戶端發(fā)過來的請求體,可以分別調(diào)用這兩個接口來完成。這兩個接口是nginx核心提供的處理請求體的標(biāo)準(zhǔn)接口,如果希望配置文件中一些請求體相關(guān)的指令(比如client_body_in_file_only,client_body_buffer_size等)能夠預(yù)期工作,以及能夠正常使用nginx內(nèi)置的一些和請求體相關(guān)的變量(比如$request_body和$request_body_file),一般來說所有模塊都必須調(diào)用這些接口來完成相應(yīng)操作,如果需要自定義接口來處理請求體,也應(yīng)盡量兼容nginx默認的行為。
1,讀取請求體
請求體的讀取一般發(fā)生在nginx的content handler中,一些nginx內(nèi)置的模塊,比如proxy模塊,fastcgi模塊,uwsgi模塊等,這些模塊的行為必須將客戶端過來的請求體(如果有的話)以相應(yīng)協(xié)議完整的轉(zhuǎn)發(fā)到后端服務(wù)進程,所有的這些模塊都是調(diào)用了ngx_http_read_client_request_body()接口來完成請求體讀取。值得注意的是這些模塊會把客戶端的請求體完整的讀取后才開始往后端轉(zhuǎn)發(fā)數(shù)據(jù)。
由于內(nèi)存的限制,ngx_http_read_client_request_body()接口讀取的請求體會部分或者全部寫入一個臨時文件中,根據(jù)請求體的大小以及相關(guān)的指令配置,請求體可能完整放置在一塊連續(xù)內(nèi)存中,也可能分別放置在兩塊不同內(nèi)存中,還可能全部存在一個臨時文件中,最后還可能一部分在內(nèi)存,剩余部分在臨時文件中。下面先介紹一下和這些不同存儲行為相關(guān)的指令:
client_body_buffer_size:設(shè)置緩存請求體的buffer大小,默認為系統(tǒng)頁大小的2倍,當(dāng)請求體的大小超過此大小時,nginx會把請求體寫入到臨時文件中??梢愿鶕?jù)業(yè)務(wù)需求設(shè)置合適的大小,盡量避免磁盤io操作;
client_body_in_single_buffer:指示是否將請求體完整的存儲在一塊連續(xù)的內(nèi)存中,默認為off,如果此指令被設(shè)置為on,則nginx會保證請求體在不大于client_body_buffer_size設(shè)置的值時,被存放在一塊連續(xù)的內(nèi)存中,但超過大小時會被整個寫入一個臨時文件;
client_body_in_file_only:設(shè)置是否總是將請求體保存在臨時文件中,默認為off,當(dāng)此指定被設(shè)置為on時,即使客戶端顯示指示了請求體長度為0時,nginx還是會為請求創(chuàng)建一個臨時文件。
接著介紹ngx_http_read_client_request_body()接口的實現(xiàn),它的定義如下:
ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler)
該接口有2個參數(shù),第1個為指向請求結(jié)構(gòu)的指針,第2個為一個函數(shù)指針,當(dāng)請求體讀完時,它會被調(diào)用。之前也說到根據(jù)nginx現(xiàn)有行為,模塊邏輯會在請求體讀完后執(zhí)行,這個回調(diào)函數(shù)一般就是模塊的邏輯處理函數(shù)。ngx_http_read_client_request_body()函數(shù)首先將參數(shù)r對應(yīng)的主請求的引用加1,這樣做的目的和該接口被調(diào)用的上下文有關(guān),一般而言,模塊是在content handler中調(diào)用此接口,一個典型的調(diào)用如下:
static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r) { ... rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init); if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { return rc; } return NGX_DONE; }
上面的代碼是在porxy模塊的content handler,ngx_http_proxy_handler()中調(diào)用了ngx_http_read_client_request_body()函數(shù),其中ngx_http_upstream_init()被作為回調(diào)函數(shù)傳入進接口中,另外nginx中模塊的content handler調(diào)用的上下文如下:
ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) { ... if (r->content_handler) { r->write_event_handler = ngx_http_request_empty_handler; ngx_http_finalize_request(r, r->content_handler(r)); return NGX_OK; } ... }
上面的代碼中,content handler調(diào)用之后,它的返回值作為參數(shù)調(diào)用了ngx_http_finalize_request()函數(shù),在請求體沒有被接收完全時,ngx_http_read_client_request_body()函數(shù)返回值為NGX_AGAIN,此時content handler,比如ngx_http_proxy_handler()會返回NGX_DONE,而NGX_DONE作為參數(shù)傳給ngx_http_finalize_request()函數(shù)會導(dǎo)致主請求的引用計數(shù)減1,所以正好抵消了ngx_http_read_client_request_body()函數(shù)開頭對主請求計數(shù)的加1。
接下來回到ngx_http_read_client_request_body()函數(shù),它會檢查該請求的請求體是否已經(jīng)被讀取或者被丟棄了,如果是的話,則直接調(diào)用回調(diào)函數(shù)并返回NGX_OK,這里實際上是為子請求檢查,子請求是nginx中的一個概念,nginx中可以在當(dāng)前請求中發(fā)起另外一個或多個全新的子請求來訪問其他的location,關(guān)于子請求的具體介紹會在后面的章節(jié)作詳細分析,一般而言子請求不需要自己去讀取請求體。
函數(shù)接著調(diào)用ngx_http_test_expect()檢查客戶端是否發(fā)送了Expect: 100-continue頭,是的話則給客戶端回復(fù)"HTTP/1.1 100 Continue",根據(jù)http 1.1協(xié)議,客戶端可以發(fā)送一個Expect頭來向服務(wù)器表明期望發(fā)送請求體,服務(wù)器如果允許客戶端發(fā)送請求體,則會回復(fù)"HTTP/1.1 100 Continue",客戶端收到時,才會開始發(fā)送請求體。
接著繼續(xù)為接收請求體做準(zhǔn)備工作,分配一個ngx_http_request_body_t結(jié)構(gòu),并保存在r->request_body,這個結(jié)構(gòu)用來保存請求體讀取過程用到的緩存引用,臨時文件引用,剩余請求體大小等信息,它的定義如下。
<span style="font-family:SimSun;font-size:18px;">typedef struct { ngx_temp_file_t *temp_file; ngx_chain_t *bufs; ngx_buf_t *buf; off_t rest; ngx_chain_t *to_write; ngx_http_client_body_handler_pt post_handler; } ngx_http_request_body_t;</span>
- temp_file: 指向儲存請求體的臨時文件的指針;
- bufs: 指向保存請求體的鏈表頭;
- buf: 指向當(dāng)前用于保存請求體的內(nèi)存緩存;
- rest: 當(dāng)前剩余的請求體大?。?/li>
- post_handler:保存?zhèn)鹘ongx_http_read_client_request_body()函數(shù)的回調(diào)函數(shù)。
做好準(zhǔn)備工作之后,函數(shù)開始檢查請求是否帶有content_length頭,如果沒有該頭或者客戶端發(fā)送了一個值為0的content_length頭,表明沒有請求體,這時直接調(diào)用回調(diào)函數(shù)并返回NGX_OK即可。當(dāng)然如果client_body_in_file_only指令被設(shè)置為on,且content_length為0時,該函數(shù)在調(diào)用回調(diào)函數(shù)之前,會創(chuàng)建一個空的臨時文件。
進入到函數(shù)下半部分,表明客戶端請求確實表明了要發(fā)送請求體,該函數(shù)會先檢查是否在讀取請求頭時預(yù)讀了請求體,這里的檢查是通過判斷保存請求頭的緩存(r->header_in)中是否還有未處理的數(shù)據(jù)。如果有預(yù)讀數(shù)據(jù),則分配一個ngx_buf_t結(jié)構(gòu),并將r->header_in中的預(yù)讀數(shù)據(jù)保存在其中,并且如果r->header_in中還有剩余空間,并且能夠容下剩余未讀取的請求體,這些空間將被繼續(xù)使用,而不用分配新的緩存,當(dāng)然甚至如果請求體已經(jīng)被整個預(yù)讀了,則不需要繼續(xù)處理了,此時調(diào)用回調(diào)函數(shù)后返回。
如果沒有預(yù)讀數(shù)據(jù)或者預(yù)讀不完整,該函數(shù)會分配一塊新的內(nèi)存(除非r->header_in還有足夠的剩余空間),另外如果request_body_in_single_buf指令被設(shè)置為no,則預(yù)讀的數(shù)據(jù)會被拷貝進新開辟的內(nèi)存塊中,真正讀取請求體的操作是在ngx_http_do_read_client_request_body()函數(shù),該函數(shù)循環(huán)的讀取請求體并保存在緩存中,如果緩存被寫滿了,其中的數(shù)據(jù)會被清空并寫回到臨時文件中。當(dāng)然這里有可能不能一次將數(shù)據(jù)讀到,該函數(shù)會掛載讀事件并設(shè)置讀事件handler為ngx_http_read_client_request_body_handler,另外nginx核心對兩次請求體的讀事件之間也做了超時設(shè)置,client_body_timeout指令可以設(shè)置這個超時時間,默認為60s,如果下次讀事件超時了,nginx會返回408給客戶端。
最終讀完請求體后,ngx_http_do_read_client_request_body()會根據(jù)配置,將請求體調(diào)整到預(yù)期的位置(內(nèi)存或者文件),所有情況下請求體都可以從r->request_body的bufs鏈表得到,該鏈表最多可能有2個節(jié)點,每個節(jié)點為一個buffer,但是這個buffer的內(nèi)容可能是保存在內(nèi)存中,也可能是保存在磁盤文件中。另外$request_body變量只在當(dāng)請求體已經(jīng)被讀取并且是全部保存在內(nèi)存中,才能取得相應(yīng)的數(shù)據(jù)。
2,丟棄請求體
一個模塊想要主動的丟棄客戶端發(fā)過的請求體,可以調(diào)用nginx核心提供的ngx_http_discard_request_body()接口,主動丟棄的原因可能有很多種,如模塊的業(yè)務(wù)邏輯壓根不需要請求體 ,客戶端發(fā)送了過大的請求體,另外為了兼容http1.1協(xié)議的pipeline請求,模塊有義務(wù)主動丟棄不需要的請求體??傊疄榱吮3至己玫目蛻舳思嫒菪?,nginx必須主動丟棄無用的請求體。下面開始分析ngx_http_discard_request_body()函數(shù):
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; }
函數(shù)同樣還會檢查請求頭中的content-length頭,客戶端如果打算發(fā)送請求體,就必須發(fā)送content-length頭,同時還會查看其他地方是不是已經(jīng)讀取了請求體。如果確實有待處理的請求體,函數(shù)接著檢查請求頭buffer中預(yù)讀的數(shù)據(jù),預(yù)讀的數(shù)據(jù)會直接被丟掉,當(dāng)然如果請求體已經(jīng)被全部預(yù)讀,函數(shù)就直接返回了。
接下來,如果還有剩余的請求體未處理,該函數(shù)調(diào)用ngx_handle_read_event()在事件處理機制中掛載好讀事件,并把讀事件的處理函數(shù)設(shè)置為ngx_http_discarded_request_body_handler。做好這些準(zhǔn)備之后,該函數(shù)最后調(diào)用ngx_http_read_discarded_request_body()接口讀取客戶端過來的請求體并丟棄。如果客戶端并沒有一次將請求體發(fā)過來,函數(shù)會返回,剩余的數(shù)據(jù)等到下一次讀事件過來時,交給ngx_http_discarded_request_body_handler()來處理,這時,請求的discard_body將被設(shè)置為1用來標(biāo)識這種情況。另外請求的引用數(shù)(count)也被加1,這樣做的目的是客戶端可能在nginx處理完請求之后仍未完整發(fā)送待發(fā)送的請求體,增加引用是防止nginx核心在處理完請求后直接釋放了請求的相關(guān)資源。
ngx_http_read_discarded_request_body()函數(shù)非常簡單,它循環(huán)的從鏈接中讀取數(shù)據(jù)并丟棄,直到讀完接收緩沖區(qū)的所有數(shù)據(jù),如果請求體已經(jīng)被讀完了,該函數(shù)會設(shè)置讀事件的處理函數(shù)為ngx_http_block_reading,這個函數(shù)僅僅刪除水平觸發(fā)的讀事件,防止同一事件不斷被觸發(fā)。
再來看一下讀事件的處理函數(shù)ngx_http_discarded_request_body_handler,這個函數(shù)每次讀事件來時會被調(diào)用,先看一下它的源碼:
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); } }
如果讀事件發(fā)生在請求處理完之前,則不用處理超時事件,也不用設(shè)置定時器,函數(shù)只是簡單的調(diào)用ngx_http_read_discarded_request_body()來讀取并丟棄數(shù)據(jù)。
- 修改配置解決Nginx服務(wù)器中常見的上傳與連接錯誤
- 詳解Nginx服務(wù)器中配置全站HTTPS安全連接的方法
- nginx與apache限制ip并發(fā)訪問 限制ip連接的設(shè)置方法
- Nginx配置統(tǒng)計流量帶寬請求及記錄實時請求狀態(tài)的方法
- Nginx服務(wù)器中處理AJAX跨域請求的配置方法講解
- 詳解Nginx的核心配置模塊中對于請求體的接受流程
- Linux服務(wù)器nginx訪問日志里出現(xiàn)大量http 400錯誤的請求分析
- Nginx靜態(tài)文件響應(yīng)POST請求 提示405錯誤的解決方法
- Nginx服務(wù)器中限制連接數(shù)與限制請求的模塊配置教程
相關(guān)文章
詳解Linux環(huán)境下使Nginx服務(wù)器支持中文url的配置流程
這篇文章主要介紹了Linux環(huán)境下使Nginx服務(wù)器支持中文url的配置流程,文中還介紹了一個在Linux下將非UTF-8的文件名轉(zhuǎn)換為UTF-8編碼,的方法,需要的朋友可以參考下2016-04-04分布式架構(gòu)中關(guān)于正向代理反向代理面試提問
這篇文章主要為大家介紹了分布式架構(gòu)中關(guān)于正向代理反向代理的面試提問,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步2022-03-03Nginx?502?bad?gateway錯誤解決的九種方案及原因
一般在訪問某些網(wǎng)站或者我們在做本地測試的時候,服務(wù)器突然返回502?Bad?Gateway?Nginx,這種問題相信大家也遇到不少了,下面這篇文章主要給大家介紹了關(guān)于Nginx?502?bad?gateway錯誤解決的九種方案及原因,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-08-08