欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Nginx的核心配置模塊中對于請求體的接受流程

 更新時間:2015年12月30日 14:48:39   作者:fengmo_q  
這篇文章主要介紹了詳解Nginx的核心配置模塊中對于請求體的接受流程,包括其丟棄請求的過程,需要的朋友可以參考下

本篇文章主要會介紹nginx中請求的接收流程,包括請求頭的解析和請求體的讀取流程。


首先介紹一下rfc2616中定義的http請求基本格式:

Request = Request-Line  
   *(( general-header      
      | request-header      
      | entity-header ) CRLF)  
     CRLF 
     [ message-body ]  </span> 

 

第一行是請求行(request line),用來說明請求方法,要訪問的資源以及所使用的HTTP版本:
Request-Line   = Method SP Request-URI SP HTTP-Version CRLF</span> 

請求方法(Method)的定義如下,其中最常用的是GET,POST方法:

Method = "OPTIONS"  
| "GET"  
| "HEAD"  
| "POST"  
| "PUT"  
| "DELETE"  
| "TRACE"  
| "CONNECT"  
| extension-method  
extension-method = token

要訪問的資源由統(tǒng)一資源地位符URI(Uniform Resource Identifier)確定,它的一個比較通用的組成格式(rfc2396)如下:

<scheme>://<authority><path>?<query> 

一般來說根據(jù)請求方法(Method)的不同,請求URI的格式會有所不同,通常只需寫出path和query部分。

http版本(version)定義如下,現(xiàn)在用的一般為1.0和1.1版本:

HTTP/<major>.<minor> 


請求行的下一行則是請求頭,rfc2616中定義了3種不同類型的請求頭,分別為general-header,request-header和entity-header,每種類型rfc中都定義了一些通用的頭,其中entity-header類型可以包含自定義的頭。


現(xiàn)在開始介紹nginx中請求頭的解析,nginx的請求處理流程中,會涉及到2個非常重要的數(shù)據(jù)結(jié)構(gòu),ngx_connection_t和ngx_http_request_t,分別用來表示連接和請求,這2個數(shù)據(jù)結(jié)構(gòu)在本書的前篇中已經(jīng)做了比較詳細(xì)的介紹,沒有印象的讀者可以翻回去復(fù)習(xí)一下,整個請求處理流程從頭到尾,對應(yīng)著這2個數(shù)據(jù)結(jié)構(gòu)的分配,初始化,使用,重用和銷毀。


nginx在初始化階段,具體是在init process階段的ngx_event_process_init函數(shù)中會為每一個監(jiān)聽套接字分配一個連接結(jié)構(gòu)(ngx_connection_t),并將該連接結(jié)構(gòu)的讀事件成員(read)的事件處理函數(shù)設(shè)置為ngx_event_accept,并且如果沒有使用accept互斥鎖的話,在這個函數(shù)中會將該讀事件掛載到nginx的事件處理模型上(poll或者epoll等),反之則會等到init process階段結(jié)束,在工作進(jìn)程的事件處理循環(huán)中,某個進(jìn)程搶到了accept鎖才能掛載該讀事件。

static ngx_int_t 
ngx_event_process_init(ngx_cycle_t *cycle) 
{ 
  ... 
 
 
  /* 初始化用來管理所有定時器的紅黑樹 */ 
  if (ngx_event_timer_init(cycle->log) == NGX_ERROR) { 
    return NGX_ERROR; 
  } 
  /* 初始化事件模型 */ 
  for (m = 0; ngx_modules[m]; m++) { 
    if (ngx_modules[m]->type != NGX_EVENT_MODULE) { 
      continue; 
    } 
 
 
    if (ngx_modules[m]->ctx_index != ecf->use) { 
      continue; 
    } 
 
 
    module = ngx_modules[m]->ctx; 
 
 
    if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) { 
      /* fatal */ 
      exit(2); 
    } 
 
 
    break; 
  } 
 
 
  ... 
 
 
  /* for each listening socket */ 
  /* 為每個監(jiān)聽套接字分配一個連接結(jié)構(gòu) */ 
  ls = cycle->listening.elts; 
  for (i = 0; i < cycle->listening.nelts; i++) { 
 
 
    c = ngx_get_connection(ls[i].fd, cycle->log); 
 
 
    if (c == NULL) { 
      return NGX_ERROR; 
    } 
 
 
    c->log = &ls[i].log; 
 
 
    c->listening = &ls[i]; 
    ls[i].connection = c; 
 
 
    rev = c->read; 
 
 
    rev->log = c->log; 
    /* 標(biāo)識此讀事件為新請求連接事件 */ 
    rev->accept = 1; 
 
 
    ... 
 
 
#if (NGX_WIN32) 
 
 
    /* windows環(huán)境下不做分析,但原理類似 */ 
 
 
#else 
    /* 將讀事件結(jié)構(gòu)的處理函數(shù)設(shè)置為ngx_event_accept */ 
    rev->handler = ngx_event_accept; 
    /* 如果使用accept鎖的話,要在后面搶到鎖才能將監(jiān)聽句柄掛載上事件處理模型上 */ 
    if (ngx_use_accept_mutex) { 
      continue; 
    } 
    /* 否則,將該監(jiān)聽句柄直接掛載上事件處理模型 */ 
    if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { 
      if (ngx_add_conn(c) == NGX_ERROR) { 
        return NGX_ERROR; 
      } 
 
 
    } else { 
      if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { 
        return NGX_ERROR; 
      } 
    } 
 
 
#endif 
 
 
  } 
 
 
  return NGX_OK; 
}

當(dāng)一個工作進(jìn)程在某個時刻將監(jiān)聽事件掛載上事件處理模型之后,nginx就可以正式的接收并處理客戶端過來的請求了。這時如果有一個用戶在瀏覽器的地址欄內(nèi)輸入一個域名,并且域名解析服務(wù)器將該域名解析到一臺由nginx監(jiān)聽的服務(wù)器上,nginx的事件處理模型接收到這個讀事件之后,會速度交由之前注冊好的事件處理函數(shù)ngx_event_accept來處理。


在ngx_event_accept函數(shù)中,nginx調(diào)用accept函數(shù),從已連接隊列得到一個連接以及對應(yīng)的套接字,接著分配一個連接結(jié)構(gòu)(ngx_connection_t),并將新得到的套接字保存在該連接結(jié)構(gòu)中,這里還會做一些基本的連接初始化工作:
首先給該連接分配一個內(nèi)存池,初始大小默認(rèn)為256字節(jié),可通過connection_pool_size指令設(shè)置;
分配日志結(jié)構(gòu),并保存在其中,以便后續(xù)的日志系統(tǒng)使用;
初始化連接相應(yīng)的io收發(fā)函數(shù),具體的io收發(fā)函數(shù)和使用的事件模型及操作系統(tǒng)相關(guān);
分配一個套接口地址(sockaddr),并將accept得到的對端地址拷貝在其中,保存在sockaddr字段;
將本地套接口地址保存在local_sockaddr字段,因?yàn)檫@個值是從監(jiān)聽結(jié)構(gòu)ngx_listening_t中可得,而監(jiān)聽結(jié)構(gòu)中保存的只是配置文件中設(shè)置的監(jiān)聽地址,但是配置的監(jiān)聽地址可能是通配符*,即監(jiān)聽在所有的地址上,所以連接中保存的這個值最終可能還會變動,會被確定為真正的接收地址;
將連接的寫事件設(shè)置為已就緒,即設(shè)置ready為1,nginx默認(rèn)連接第一次為可寫;
如果監(jiān)聽套接字設(shè)置了TCP_DEFER_ACCEPT屬性,則表示該連接上已經(jīng)有數(shù)據(jù)包過來,于是設(shè)置讀事件為就緒;
將sockaddr字段保存的對端地址格式化為可讀字符串,并保存在addr_text字段;
最后調(diào)用ngx_http_init_connection函數(shù)初始化該連接結(jié)構(gòu)的其他部分。


ngx_http_init_connection函數(shù)最重要的工作是初始化讀寫事件的處理函數(shù):將該連接結(jié)構(gòu)的寫事件的處理函數(shù)設(shè)置為ngx_http_empty_handler,這個事件處理函數(shù)不會做任何操作,實(shí)際上nginx默認(rèn)連接第一次可寫,不會掛載寫事件,如果有數(shù)據(jù)需要發(fā)送,nginx會直接寫到這個連接,只有在發(fā)生一次寫不完的情況下,才會掛載寫事件到事件模型上,并設(shè)置真正的寫事件處理函數(shù),這里后面的章節(jié)還會做詳細(xì)介紹;讀事件的處理函數(shù)設(shè)置為ngx_http_init_request,此時如果該連接上已經(jīng)有數(shù)據(jù)過來(設(shè)置了deferred accept),則會直接調(diào)用ngx_http_init_request函數(shù)來處理該請求,反之則設(shè)置一個定時器并在事件處理模型上掛載一個讀事件,等待數(shù)據(jù)到來或者超時。當(dāng)然這里不管是已經(jīng)有數(shù)據(jù)到來,或者需要等待數(shù)據(jù)到來,又或者等待超時,最終都會進(jìn)入讀事件的處理函數(shù)-ngx_http_init_request。
 

ngx_http_init_request函數(shù)主要工作即是初始化請求,由于它是一個事件處理函數(shù),它只有唯一一個ngx_event_t *類型的參數(shù),ngx_event_t 結(jié)構(gòu)在nginx中表示一個事件,事件處理的上下文類似于一個中斷處理的上下文,為了在這個上下文得到相關(guān)的信息,nginx中一般會將連接結(jié)構(gòu)的引用保存在事件結(jié)構(gòu)的data字段,請求結(jié)構(gòu)的引用則保存在連接結(jié)構(gòu)的data字段,這樣在事件處理函數(shù)中可以方便的得到對應(yīng)的連接結(jié)構(gòu)和請求結(jié)構(gòu)。進(jìn)入函數(shù)內(nèi)部看一下,首先判斷該事件是否是超時事件,如果是的話直接關(guān)閉連接并返回;反之則是指之前accept的連接上有請求過來需要處理,ngx_http_init_request函數(shù)首先在連接的內(nèi)存池中為該請求分配一個ngx_http_request_t結(jié)構(gòu),這個結(jié)構(gòu)將用來保存該請求所有的信息。分配完之后,這個結(jié)構(gòu)的引用會被包存在連接的hc成員的request字段,以便于在長連接或pipelined請求中復(fù)用該請求結(jié)構(gòu)。在這個函數(shù)中,nginx根據(jù)該請求的接收端口和地址找到一個默認(rèn)虛擬服務(wù)器配置(listen指令的default_server屬性用來標(biāo)識一個默認(rèn)虛擬服務(wù)器,否則監(jiān)聽在相同端口和地址的多個虛擬服務(wù)器,其中第一個定義的則為默認(rèn)),因?yàn)樵趎ginx配置文件中可以設(shè)置多個監(jiān)聽在不同端口和地址的虛擬服務(wù)器(每個server塊對應(yīng)一個虛擬服務(wù)器),另外還根據(jù)域名(server_name指令可以配置該虛擬服務(wù)器對應(yīng)的域名)來區(qū)分監(jiān)聽在相同端口和地址的虛擬服務(wù)器,每個虛擬服務(wù)器可以擁有不同的配置內(nèi)容,而這些配置內(nèi)容決定了nginx在接收到一個請求之后如何處理該請求。找到之后,相應(yīng)的配置被保存在該請求對應(yīng)的ngx_http_request_t結(jié)構(gòu)中。注意這里根據(jù)端口和地址找到的默認(rèn)配置只是臨時使用一下,最終nginx會根據(jù)域名找到真正的虛擬服務(wù)器配置,隨后的初始化工作還包括:

將連接的讀事件的處理函數(shù)設(shè)置為ngx_http_process_request_line函數(shù),這個函數(shù)用來解析請求行,將請求的read_event_handler設(shè)置為ngx_http_block_reading函數(shù),這個函數(shù)實(shí)際上什么都不做(當(dāng)然在事件模型設(shè)置為水平觸發(fā)時,唯一做的事情就是將事件從事件模型監(jiān)聽列表中刪除,防止該事件一直被觸發(fā)),后面會說到這里為什么會將read_event_handler設(shè)置為此函數(shù);
為這個請求分配一個緩沖區(qū)用來保存它的請求頭,地址保存在header_in字段,默認(rèn)大小為1024個字節(jié),可以使用client_header_buffer_size指令修改,這里需要注意一下,nginx用來保存請求頭的緩沖區(qū)是在該請求所在連接的內(nèi)存池中分配,而且會將地址保存一份在連接的buffer字段中,這樣做的目的也是為了給該連接的下一次請求重用這個緩沖區(qū),另外如果客戶端發(fā)過來的請求頭大于1024個字節(jié),nginx會重新分配更大的緩存區(qū),默認(rèn)用于大請求的頭的緩沖區(qū)最大為8K,最多4個,這2個值可以用large_client_header_buffers指令設(shè)置,后面還會說到請求行和一個請求頭都不能超過一個最大緩沖區(qū)的大??;
同樣的nginx會為這個請求分配一個內(nèi)存池,后續(xù)所有與該請求相關(guān)的內(nèi)存分配一般都會使用該內(nèi)存池,默認(rèn)大小為4096個字節(jié),可以使用request_pool_size指令修改;
為這個請求分配響應(yīng)頭鏈表,初始大小為20;
創(chuàng)建所有模塊的上下文ctx指針數(shù)組,變量數(shù)據(jù);
將該請求的main字段設(shè)置為它本身,表示這是一個主請求,nginx中對應(yīng)的還有子請求概念,后面的章節(jié)會做詳細(xì)的介紹;
將該請求的count字段設(shè)置為1,count字段表示請求的引用計數(shù);
將當(dāng)前時間保持在start_sec和start_msec字段,這個時間是該請求的起始時刻,將被用來計算一個請求的處理時間(request time),nginx使用的這個起始點(diǎn)和apache略有差別,nginx中請求的起始點(diǎn)是接收到客戶端的第一個數(shù)據(jù)包開始,而apache則是接收到客戶端的整個request line后開始算起;
初始化請求的其他字段,比如將uri_changes設(shè)置為11,表示最多可以將該請求的uri改寫10次,subrequests被設(shè)置為201,表示一個請求最多可以發(fā)起200個子請求;
做完所有這些初始化工作之后,ngx_http_init_request函數(shù)會調(diào)用讀事件的處理函數(shù)來真正的解析客戶端發(fā)過來的數(shù)據(jù),也就是會進(jìn)入ngx_http_process_request_line函數(shù)中處理。


ngx_http_process_request_line函數(shù)的主要作用即是解析請求行,同樣由于涉及到網(wǎng)絡(luò)IO操作,即使是很短的一行請求行可能也不能被一次讀完,所以在之前的ngx_http_init_request函數(shù)中,ngx_http_process_request_line函數(shù)被設(shè)置為讀事件的處理函數(shù),它也只擁有一個唯一的ngx_event_t *類型參數(shù),并且在函數(shù)的開頭,同樣需要判斷是否是超時事件,如果是的話,則關(guān)閉這個請求和連接;否則開始正常的解析流程。先調(diào)用ngx_http_read_request_header函數(shù)讀取數(shù)據(jù)。


由于可能多次進(jìn)入ngx_http_process_request_line函數(shù),ngx_http_read_request_header函數(shù)首先檢查請求的header_in指向的緩沖區(qū)內(nèi)是否有數(shù)據(jù),有的話直接返回;否則從連接讀取數(shù)據(jù)并保存在請求的header_in指向的緩存區(qū),而且只要緩沖區(qū)有空間的話,會一次盡可能多的讀數(shù)據(jù),讀到多少返回多少;如果客戶端暫時沒有發(fā)任何數(shù)據(jù)過來,并返回NGX_AGAIN,返回之前會做2件事情:1,設(shè)置一個定時器,時長默認(rèn)為60s,可以通過指令client_header_timeout設(shè)置,如果定時事件到達(dá)之前沒有任何可讀事件,nginx將會關(guān)閉此請求;2,調(diào)用ngx_handle_read_event函數(shù)處理一下讀事件-如果該連接尚未在事件處理模型上掛載讀事件,則將其掛載上;如果客戶端提前關(guān)閉了連接或者讀取數(shù)據(jù)發(fā)生了其他錯誤,則給客戶端返回一個400錯誤(當(dāng)然這里并不保證客戶端能夠接收到響應(yīng)數(shù)據(jù),因?yàn)榭蛻舳丝赡芏家呀?jīng)關(guān)閉了連接),最后函數(shù)返回NGX_ERROR;


如果ngx_http_read_request_header函數(shù)正常的讀取到了數(shù)據(jù),ngx_http_process_request_line函數(shù)將調(diào)用ngx_http_parse_request_line函數(shù)來解析,這個函數(shù)根據(jù)http協(xié)議規(guī)范中對請求行的定義實(shí)現(xiàn)了一個有限狀態(tài)機(jī),經(jīng)過這個狀態(tài)機(jī),nginx會記錄請求行中的請求方法(Method),請求uri以及http協(xié)議版本在緩沖區(qū)中的起始位置,在解析過程中還會記錄一些其他有用的信息,以便后面的處理過程中使用。如果解析請求行的過程中沒有產(chǎn)生任何問題,該函數(shù)會返回NGX_OK;如果請求行不滿足協(xié)議規(guī)范,該函數(shù)會立即終止解析過程,并返回相應(yīng)錯誤號;如果緩沖區(qū)數(shù)據(jù)不夠,該函數(shù)返回NGX_AGAIN。在整個解析http請求的狀態(tài)機(jī)中始終遵循著兩條重要的原則:減少內(nèi)存拷貝和回溯。內(nèi)存拷貝是一個相對比較昂貴的操作,大量的內(nèi)存拷貝會帶來較低的運(yùn)行時效率。nginx在需要做內(nèi)存拷貝的地方盡量只拷貝內(nèi)存的起始和結(jié)束地址而不是內(nèi)存本身,這樣做的話僅僅只需要兩個賦值操作而已,大大降低了開銷,當(dāng)然這樣帶來的影響是后續(xù)的操作不能修改內(nèi)存本身,如果修改的話,會影響到所有引用到該內(nèi)存區(qū)間的地方,所以必須很小心的管理,必要的時候需要拷貝一份。這里不得不提到nginx中最能體現(xiàn)這一思想的數(shù)據(jù)結(jié)構(gòu),ngx_buf_t,它用來表示nginx中的緩存,在很多情況下,只需要將一塊內(nèi)存的起始地址和結(jié)束地址分別保存在它的pos和last成員中,再將它的memory標(biāo)志置1,即可表示一塊不能修改的內(nèi)存區(qū)間,在另外的需要一塊能夠修改的緩存的情形中,則必須分配一塊所需大小的內(nèi)存并保存其起始地址,再將ngx_bug_t的temprary標(biāo)志置1,表示這是一塊能夠被修改的內(nèi)存區(qū)域。


再回到ngx_http_process_request_line函數(shù)中,如果ngx_http_parse_request_line函數(shù)返回了錯誤,則直接給客戶端返回400錯誤;
如果返回NGX_AGAIN,則需要判斷一下是否是由于緩沖區(qū)空間不夠,還是已讀數(shù)據(jù)不夠。如果是緩沖區(qū)大小不夠了,nginx會調(diào)用ngx_http_alloc_large_header_buffer函數(shù)來分配另一塊大緩沖區(qū),如果大緩沖區(qū)還不夠裝下整個請求行,nginx則會返回414錯誤給客戶端,否則分配了更大的緩沖區(qū)并拷貝之前的數(shù)據(jù)之后,繼續(xù)調(diào)用ngx_http_read_request_header函數(shù)讀取數(shù)據(jù)來進(jìn)入請求行自動機(jī)處理,直到請求行解析結(jié)束;
如果返回了NGX_OK,則表示請求行被正確的解析出來了,這時先記錄好請求行的起始地址以及長度,并將請求uri的path和參數(shù)部分保存在請求結(jié)構(gòu)的uri字段,請求方法起始位置和長度保存在method_name字段,http版本起始位置和長度記錄在http_protocol字段。還要從uri中解析出參數(shù)以及請求資源的拓展名,分別保存在args和exten字段。

丟棄請求體

一個模塊想要主動的丟棄客戶端發(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ù)不長,這里把它完整的列出來了,函數(shù)的開始同樣先判斷了不需要再做處理的情況:子請求不需要處理,已經(jīng)調(diào)用過此函數(shù)的也不需要再處理。接著調(diào)用ngx_http_test_expect() 處理http1.1 expect的情況,根據(jù)http1.1的expect機(jī)制,如果客戶端發(fā)送了expect頭,而服務(wù)端不希望接收請求體時,必須返回417(Expectation Failed)錯誤。nginx并沒有這樣做,它只是簡單的讓客戶端把請求體發(fā)送過來,然后丟棄掉。接下來,函數(shù)刪掉了讀事件上的定時器,因?yàn)檫@時本身就不需要請求體,所以也無所謂客戶端發(fā)送的快還是慢了,當(dāng)然后面還會將到,當(dāng)nginx已經(jīng)處理完該請求但客戶端還沒有發(fā)送完無用的請求體時,nginx會在讀事件上再掛上定時器。
函數(shù)同樣還會檢查請求頭中的content-length頭,客戶端如果打算發(fā)送請求體,就必須發(fā)送content-length頭,同時還會查看其他地方是不是已經(jīng)讀取了請求體。如果確實(shí)有待處理的請求體,函數(shù)接著檢查請求頭buffer中預(yù)讀的數(shù)據(jù),預(yù)讀的數(shù)據(jù)會直接被丟掉,當(dāng)然如果請求體已經(jīng)被全部預(yù)讀,函數(shù)就直接返回了。

接下來,如果還有剩余的請求體未處理,該函數(shù)調(diào)用ngx_handle_read_event()在事件處理機(jī)制中掛載好讀事件,并把讀事件的處理函數(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); 
  } 
} 

函數(shù)一開始就處理了讀事件超時的情況,之前說到在ngx_http_discard_request_body()函數(shù)中已經(jīng)刪除了讀事件的定時器,那么什么時候會設(shè)置定時器呢?答案就是在nginx已經(jīng)處理完該請求,但是又沒有完全將該請求的請求體丟棄的時候(客戶端可能還沒有發(fā)送過來),在ngx_http_finalize_connection()函數(shù)中,如果檢查到還有未丟棄的請求體時,nginx會添加一個讀事件定時器,它的時長為lingering_timeout指令所指定,默認(rèn)為5秒,不過這個時間僅僅兩次讀事件之間的超時時間,等待請求體的總時長為lingering_time指令所指定,默認(rèn)為30秒。這種情況中,該函數(shù)如果檢測到超時事件則直接返回并斷開連接。同樣,還需要控制整個丟棄請求體的時長不能超過lingering_time設(shè)置的時間,如果超過了最大時長,也會直接返回并斷開連接。
如果讀事件發(fā)生在請求處理完之前,則不用處理超時事件,也不用設(shè)置定時器,函數(shù)只是簡單的調(diào)用ngx_http_read_discarded_request_body()來讀取并丟棄數(shù)據(jù)。

相關(guān)文章

  • Nginx下無法使用中文URL的解決方法

    Nginx下無法使用中文URL的解決方法

    最近發(fā)現(xiàn)有很多的url打不開,后來仔細(xì)觀察后發(fā)現(xiàn)是中文url的問題,所以這篇文章主要給大家介紹了Nginx下無法使用中文URL的解決方法,文中提供了兩種方法供大家參考學(xué)習(xí),需要的朋友們下面來一起看看吧。
    2017-05-05
  • nginx配置PC站手機(jī)站分離實(shí)現(xiàn)重定向

    nginx配置PC站手機(jī)站分離實(shí)現(xiàn)重定向

    這篇文章主要介紹了nginx配置PC站手機(jī)站分離實(shí)現(xiàn)重定向,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • nginx配置IP白名單的詳細(xì)步驟

    nginx配置IP白名單的詳細(xì)步驟

    在日常運(yùn)維工作中會碰到這樣的需求,設(shè)置網(wǎng)站訪問只對某些ip開放,其他ip的客戶端都不能訪問,下面這篇文章主要給大家介紹了關(guān)于nginx配置IP白名單的詳細(xì)步驟,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2022-12-12
  • Nginx if語句加正則表達(dá)式實(shí)現(xiàn)字符串截斷

    Nginx if語句加正則表達(dá)式實(shí)現(xiàn)字符串截斷

    這篇文章主要介紹了Nginx if語句加正則表達(dá)式實(shí)現(xiàn)字符串截斷功能,特殊場合下可能會需要這個功能,NGINX的奇淫技巧之一,需要的朋友可以參考下
    2015-02-02
  • ELK收集Nginx日志的項(xiàng)目實(shí)戰(zhàn)

    ELK收集Nginx日志的項(xiàng)目實(shí)戰(zhàn)

    本文主要介紹了ELK收集Nginx日志的項(xiàng)目實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • nginx修改默認(rèn)運(yùn)行80端口的方法

    nginx修改默認(rèn)運(yùn)行80端口的方法

    這篇文章主要給大家介紹了關(guān)于nginx是如何修改默認(rèn)運(yùn)行80端口的方法,文中介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-04-04
  • 詳解Nginx服務(wù)器中map模塊的配置與使用

    詳解Nginx服務(wù)器中map模塊的配置與使用

    這篇文章主要介紹了Nginx服務(wù)器中map模塊的配置與使用,文中同時給出了ngx_http_map_module模塊的map命令用于制作服務(wù)器限速白名單的示例,需要的朋友可以參考下
    2016-01-01
  • nginx部署多個前端項(xiàng)目詳細(xì)步驟

    nginx部署多個前端項(xiàng)目詳細(xì)步驟

    最近一臺服務(wù)器要配置多個前端項(xiàng)目,當(dāng)然前后端分離就需要nginx來配置了,下面這篇文章主要給大家介紹了關(guān)于nginx部署多個前端項(xiàng)目的詳細(xì)步驟,需要的朋友可以參考下
    2023-10-10
  • Nginx實(shí)現(xiàn)if多重判斷配置方法示例

    Nginx實(shí)現(xiàn)if多重判斷配置方法示例

    這篇文章主要介紹了Nginx實(shí)現(xiàn)if多重判斷配置方法示例,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2015-05-05
  • Nginx+cpolar實(shí)現(xiàn)內(nèi)網(wǎng)穿透多個Windows Web站點(diǎn)端口的步驟詳解

    Nginx+cpolar實(shí)現(xiàn)內(nèi)網(wǎng)穿透多個Windows Web站點(diǎn)端口的步驟詳解

    這篇文章主要給大家介紹了Nginx+cpolar實(shí)現(xiàn)內(nèi)網(wǎng)穿透多個Windows Web站點(diǎn)端口的詳細(xì)步驟,文章通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-10-10

最新評論