亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 網站 > Nginx > 正文

詳解Nginx的配置函數對于請求體的讀取

2024-08-30 12:27:51
字體:
來源:轉載
供稿:網友
這篇文章主要介紹了Nginx的配置函數對于請求體的讀取,深入Nginx的內核配置中進行講解,需要的朋友可以參考下
 

nginx核心本身不會主動讀取請求體,這個工作是交給請求處理階段的模塊來做,但是nginx核心提供了ngx_http_read_client_request_body()接口來讀取請求體,另外還提供了一個丟棄請求體的接口-ngx_http_discard_request_body(),在請求執行的各個階段中,任何一個階段的模塊如果對請求體感興趣或者希望丟掉客戶端發過來的請求體,可以分別調用這兩個接口來完成。這兩個接口是nginx核心提供的處理請求體的標準接口,如果希望配置文件中一些請求體相關的指令(比如client_body_in_file_only,client_body_buffer_size等)能夠預期工作,以及能夠正常使用nginx內置的一些和請求體相關的變量(比如$request_body和$request_body_file),一般來說所有模塊都必須調用這些接口來完成相應操作,如果需要自定義接口來處理請求體,也應盡量兼容nginx默認的行為。

1,讀取請求體

請求體的讀取一般發生在nginx的content handler中,一些nginx內置的模塊,比如proxy模塊,fastcgi模塊,uwsgi模塊等,這些模塊的行為必須將客戶端過來的請求體(如果有的話)以相應協議完整的轉發到后端服務進程,所有的這些模塊都是調用了ngx_http_read_client_request_body()接口來完成請求體讀取。值得注意的是這些模塊會把客戶端的請求體完整的讀取后才開始往后端轉發數據。

由于內存的限制,ngx_http_read_client_request_body()接口讀取的請求體會部分或者全部寫入一個臨時文件中,根據請求體的大小以及相關的指令配置,請求體可能完整放置在一塊連續內存中,也可能分別放置在兩塊不同內存中,還可能全部存在一個臨時文件中,最后還可能一部分在內存,剩余部分在臨時文件中。下面先介紹一下和這些不同存儲行為相關的指令:

client_body_buffer_size:設置緩存請求體的buffer大小,默認為系統頁大小的2倍,當請求體的大小超過此大小時,nginx會把請求體寫入到臨時文件中??梢愿鶕I務需求設置合適的大小,盡量避免磁盤io操作;

 

client_body_in_single_buffer:指示是否將請求體完整的存儲在一塊連續的內存中,默認為off,如果此指令被設置為on,則nginx會保證請求體在不大于client_body_buffer_size設置的值時,被存放在一塊連續的內存中,但超過大小時會被整個寫入一個臨時文件;

client_body_in_file_only:設置是否總是將請求體保存在臨時文件中,默認為off,當此指定被設置為on時,即使客戶端顯示指示了請求體長度為0時,nginx還是會為請求創建一個臨時文件。

接著介紹ngx_http_read_client_request_body()接口的實現,它的定義如下:

ngx_int_t ngx_http_read_client_request_body(ngx_http_request_t *r,  ngx_http_client_body_handler_pt post_handler) 

該接口有2個參數,第1個為指向請求結構的指針,第2個為一個函數指針,當請求體讀完時,它會被調用。之前也說到根據nginx現有行為,模塊邏輯會在請求體讀完后執行,這個回調函數一般就是模塊的邏輯處理函數。ngx_http_read_client_request_body()函數首先將參數r對應的主請求的引用加1,這樣做的目的和該接口被調用的上下文有關,一般而言,模塊是在content handler中調用此接口,一個典型的調用如下:

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()中調用了ngx_http_read_client_request_body()函數,其中ngx_http_upstream_init()被作為回調函數傳入進接口中,另外nginx中模塊的content handler調用的上下文如下:

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調用之后,它的返回值作為參數調用了ngx_http_finalize_request()函數,在請求體沒有被接收完全時,ngx_http_read_client_request_body()函數返回值為NGX_AGAIN,此時content handler,比如ngx_http_proxy_handler()會返回NGX_DONE,而NGX_DONE作為參數傳給ngx_http_finalize_request()函數會導致主請求的引用計數減1,所以正好抵消了ngx_http_read_client_request_body()函數開頭對主請求計數的加1。

接下來回到ngx_http_read_client_request_body()函數,它會檢查該請求的請求體是否已經被讀取或者被丟棄了,如果是的話,則直接調用回調函數并返回NGX_OK,這里實際上是為子請求檢查,子請求是nginx中的一個概念,nginx中可以在當前請求中發起另外一個或多個全新的子請求來訪問其他的location,關于子請求的具體介紹會在后面的章節作詳細分析,一般而言子請求不需要自己去讀取請求體。

函數接著調用ngx_http_test_expect()檢查客戶端是否發送了Expect: 100-continue頭,是的話則給客戶端回復"HTTP/1.1 100 Continue",根據http 1.1協議,客戶端可以發送一個Expect頭來向服務器表明期望發送請求體,服務器如果允許客戶端發送請求體,則會回復"HTTP/1.1 100 Continue",客戶端收到時,才會開始發送請求體。

接著繼續為接收請求體做準備工作,分配一個ngx_http_request_body_t結構,并保存在r->request_body,這個結構用來保存請求體讀取過程用到的緩存引用,臨時文件引用,剩余請求體大小等信息,它的定義如下。

<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: 指向當前用于保存請求體的內存緩存;
  • rest: 當前剩余的請求體大?。?/li>
  • post_handler:保存傳給ngx_http_read_client_request_body()函數的回調函數。

 

做好準備工作之后,函數開始檢查請求是否帶有content_length頭,如果沒有該頭或者客戶端發送了一個值為0的content_length頭,表明沒有請求體,這時直接調用回調函數并返回NGX_OK即可。當然如果client_body_in_file_only指令被設置為on,且content_length為0時,該函數在調用回調函數之前,會創建一個空的臨時文件。

進入到函數下半部分,表明客戶端請求確實表明了要發送請求體,該函數會先檢查是否在讀取請求頭時預讀了請求體,這里的檢查是通過判斷保存請求頭的緩存(r->header_in)中是否還有未處理的數據。如果有預讀數據,則分配一個ngx_buf_t結構,并將r->header_in中的預讀數據保存在其中,并且如果r->header_in中還有剩余空間,并且能夠容下剩余未讀取的請求體,這些空間將被繼續使用,而不用分配新的緩存,當然甚至如果請求體已經被整個預讀了,則不需要繼續處理了,此時調用回調函數后返回。

如果沒有預讀數據或者預讀不完整,該函數會分配一塊新的內存(除非r->header_in還有足夠的剩余空間),另外如果request_body_in_single_buf指令被設置為no,則預讀的數據會被拷貝進新開辟的內存塊中,真正讀取請求體的操作是在ngx_http_do_read_client_request_body()函數,該函數循環的讀取請求體并保存在緩存中,如果緩存被寫滿了,其中的數據會被清空并寫回到臨時文件中。當然這里有可能不能一次將數據讀到,該函數會掛載讀事件并設置讀事件handler為ngx_http_read_client_request_body_handler,另外nginx核心對兩次請求體的讀事件之間也做了超時設置,client_body_timeout指令可以設置這個超時時間,默認為60s,如果下次讀事件超時了,nginx會返回408給客戶端。

最終讀完請求體后,ngx_http_do_read_client_request_body()會根據配置,將請求體調整到預期的位置(內存或者文件),所有情況下請求體都可以從r->request_body的bufs鏈表得到,該鏈表最多可能有2個節點,每個節點為一個buffer,但是這個buffer的內容可能是保存在內存中,也可能是保存在磁盤文件中。另外$request_body變量只在當請求體已經被讀取并且是全部保存在內存中,才能取得相應的數據。

2,丟棄請求體

一個模塊想要主動的丟棄客戶端發過的請求體,可以調用nginx核心提供的ngx_http_discard_request_body()接口,主動丟棄的原因可能有很多種,如模塊的業務邏輯壓根不需要請求體 ,客戶端發送了過大的請求體,另外為了兼容http1.1協議的pipeline請求,模塊有義務主動丟棄不需要的請求體。總之為了保持良好的客戶端兼容性,nginx必須主動丟棄無用的請求體。下面開始分析ngx_http_discard_request_body()函數:

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; } 
由于函數不長,這里把它完整的列出來了,函數的開始同樣先判斷了不需要再做處理的情況:子請求不需要處理,已經調用過此函數的也不需要再處理。接著調用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,這個函數每次讀事件來時會被調用,先看一下它的源碼:

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);  } } 
函數一開始就處理了讀事件超時的情況,之前說到在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()來讀取并丟棄數據。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
国产日韩欧美成人| 欧美一区二区大胆人体摄影专业网站| 奇米一区二区三区四区久久| 亚洲国产日韩欧美综合久久| 永久免费精品影视网站| 成人xvideos免费视频| 国产噜噜噜噜噜久久久久久久久| 久久av中文字幕| 亚洲福利视频二区| 国产在线久久久| 亚洲最大中文字幕| 亚洲第一男人天堂| 精品无码久久久久久国产| 欧美精品一区在线播放| 国产精品视频免费观看www| 国产精品一区久久| 在线观看日韩视频| 日韩在线观看视频免费| 国产日韩在线亚洲字幕中文| 国产精品久久久久久久久粉嫩av| 国产一区av在线| 亚洲午夜女主播在线直播| 亚洲欧美国产视频| 亚洲国产美女久久久久| 最新亚洲国产精品| 久久久久久一区二区三区| 欧美人在线视频| 欧美久久精品午夜青青大伊人| 中文字幕日韩在线播放| 91久久久久久久久久久| 久久久精品网站| 久久久久国产精品一区| 免费不卡欧美自拍视频| 97国产精品视频人人做人人爱| 91豆花精品一区| 国产精品一区av| 日韩欧美精品中文字幕| 国产日韩精品在线观看| 欧美精品成人91久久久久久久| 国产精品视频区| 日韩精品免费看| 日本91av在线播放| 国产精品第一区| 成人性生交大片免费看视频直播| 国产乱人伦真实精品视频| 欧美疯狂xxxx大交乱88av| 亚洲国产毛片完整版| 久久国产视频网站| 国产在线a不卡| 97香蕉超级碰碰久久免费软件| 久久久久久中文字幕| 亚洲最大福利视频网| 美女少妇精品视频| 国产精品极品美女在线观看免费| 成人精品视频99在线观看免费| 一区二区三区四区视频| 久久精品久久久久久国产 免费| 日韩在线观看网站| 欧美黑人xxxx| 亚洲精品电影在线观看| 最近的2019中文字幕免费一页| 中国china体内裑精亚洲片| 国产精品视频久久| 一区二区在线视频播放| 黄色精品一区二区| 91精品国产综合久久久久久久久| 日韩一区二区三区xxxx| 日韩精品高清在线| 国产视频久久久久| 欧美人与性动交a欧美精品| 日本精品一区二区三区在线播放视频| 91tv亚洲精品香蕉国产一区7ujn| 亚洲肉体裸体xxxx137| 性欧美长视频免费观看不卡| 国产精品一区二区久久久久| 国产99久久精品一区二区永久免费| 欧美色道久久88综合亚洲精品| 国产精品主播视频| 欧美日韩美女在线| 欧美孕妇孕交黑巨大网站| 91在线视频精品| 亚洲男人天堂手机在线| 亚洲精品美女免费| 亚洲精品短视频| 成人性生交xxxxx网站| 91av在线影院| 91精品久久久久久综合乱菊| 96sao精品视频在线观看| 日韩欧美在线视频| 韩国国内大量揄拍精品视频| 这里只有精品在线播放| 国产成人黄色av| 亚洲香蕉成人av网站在线观看| 97免费中文视频在线观看| 国产成人亚洲综合91| 深夜福利91大全| 疯狂做受xxxx欧美肥白少妇| 亚洲一区二区三区久久| 国产亚洲精品久久久优势| 久久久亚洲国产天美传媒修理工| 91精品国产91久久久久| 欧美成年人视频| 91精品视频在线免费观看| 国产精品va在线| 久久精品99久久香蕉国产色戒| 日韩欧美亚洲一二三区| 黄色成人在线播放| 91国产美女在线观看| 国产精品xxxxx| 97热精品视频官网| 日韩欧美一区二区三区| 91久久精品国产91性色| 久久在线精品视频| 亚洲精品久久久久久久久久久| 黄色91在线观看| 久久精品亚洲精品| 欧美一级淫片aaaaaaa视频| 亚洲国产欧美久久| 亚洲高清不卡av| 精品国产福利在线| 日韩电影在线观看永久视频免费网站| 最近2019年中文视频免费在线观看| 久久综合国产精品台湾中文娱乐网| 国产午夜精品免费一区二区三区| 亚洲国产日韩一区| 欧美成人精品不卡视频在线观看| 成人久久久久爱| 久久中文字幕一区| 日韩中文字幕亚洲| 国产精品久久久久久久久久久久久| 亚洲精品视频网上网址在线观看| 久久久久国产精品免费网站| 精品久久久久久久久国产字幕| 亚洲伊人久久大香线蕉av| 国产欧美中文字幕| 亚洲国产成人久久综合| 色爱精品视频一区| 日韩av免费在线| 久久久久久久久久av| 欧美日本啪啪无遮挡网站| 国产精品av在线| 日韩av电影在线免费播放| 欧美日韩亚洲视频一区| 久久久亚洲欧洲日产国码aⅴ| 亚洲www在线观看| 在线观看久久久久久| 亚洲成av人片在线观看香蕉| 国产亚洲精品久久久久久牛牛| 青草青草久热精品视频在线观看| 久久国产视频网站| 亚洲第一福利视频| 久久黄色av网站| 久久精品成人一区二区三区| 亚洲国产精久久久久久久| 日韩电影免费在线观看中文字幕| 国产美女搞久久| 久久亚洲电影天堂| 亚洲自拍欧美另类| 国产精品视频资源| 欧美在线视频一二三| 久久韩国免费视频| 亚洲国产成人久久综合| 中文字幕精品久久| 欧洲亚洲妇女av|