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

詳解Nginx的核心配置模塊中對(duì)于請(qǐng)求體的接受流程

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

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


首先介紹一下rfc2616中定義的http請(qǐng)求基本格式:

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

 

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

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

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

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

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

一般來(lái)說(shuō)根據(jù)請(qǐng)求方法(Method)的不同,請(qǐng)求URI的格式會(huì)有所不同,通常只需寫出path和query部分。

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

HTTP/<major>.<minor> 


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


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


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

static ngx_int_t 
ngx_event_process_init(ngx_cycle_t *cycle) 
{ 
  ... 
 
 
  /* 初始化用來(lái)管理所有定時(shí)器的紅黑樹 */ 
  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 */ 
  /* 為每個(gè)監(jiān)聽套接字分配一個(gè)連接結(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)識(shí)此讀事件為新請(qǐng)求連接事件 */ 
    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)一個(gè)工作進(jìn)程在某個(gè)時(shí)刻將監(jiān)聽事件掛載上事件處理模型之后,nginx就可以正式的接收并處理客戶端過(guò)來(lái)的請(qǐng)求了。這時(shí)如果有一個(gè)用戶在瀏覽器的地址欄內(nèi)輸入一個(gè)域名,并且域名解析服務(wù)器將該域名解析到一臺(tái)由nginx監(jiān)聽的服務(wù)器上,nginx的事件處理模型接收到這個(gè)讀事件之后,會(huì)速度交由之前注冊(cè)好的事件處理函數(shù)ngx_event_accept來(lái)處理。


在ngx_event_accept函數(shù)中,nginx調(diào)用accept函數(shù),從已連接隊(duì)列得到一個(gè)連接以及對(duì)應(yīng)的套接字,接著分配一個(gè)連接結(jié)構(gòu)(ngx_connection_t),并將新得到的套接字保存在該連接結(jié)構(gòu)中,這里還會(huì)做一些基本的連接初始化工作:
首先給該連接分配一個(gè)內(nèi)存池,初始大小默認(rèn)為256字節(jié),可通過(guò)connection_pool_size指令設(shè)置;
分配日志結(jié)構(gòu),并保存在其中,以便后續(xù)的日志系統(tǒng)使用;
初始化連接相應(yīng)的io收發(fā)函數(shù),具體的io收發(fā)函數(shù)和使用的事件模型及操作系統(tǒng)相關(guān);
分配一個(gè)套接口地址(sockaddr),并將accept得到的對(duì)端地址拷貝在其中,保存在sockaddr字段;
將本地套接口地址保存在local_sockaddr字段,因?yàn)檫@個(gè)值是從監(jiān)聽結(jié)構(gòu)ngx_listening_t中可得,而監(jiān)聽結(jié)構(gòu)中保存的只是配置文件中設(shè)置的監(jiān)聽地址,但是配置的監(jiān)聽地址可能是通配符*,即監(jiān)聽在所有的地址上,所以連接中保存的這個(gè)值最終可能還會(huì)變動(dòng),會(huì)被確定為真正的接收地址;
將連接的寫事件設(shè)置為已就緒,即設(shè)置ready為1,nginx默認(rèn)連接第一次為可寫;
如果監(jiān)聽套接字設(shè)置了TCP_DEFER_ACCEPT屬性,則表示該連接上已經(jīng)有數(shù)據(jù)包過(guò)來(lái),于是設(shè)置讀事件為就緒;
將sockaddr字段保存的對(duì)端地址格式化為可讀字符串,并保存在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,這個(gè)事件處理函數(shù)不會(huì)做任何操作,實(shí)際上nginx默認(rèn)連接第一次可寫,不會(huì)掛載寫事件,如果有數(shù)據(jù)需要發(fā)送,nginx會(huì)直接寫到這個(gè)連接,只有在發(fā)生一次寫不完的情況下,才會(huì)掛載寫事件到事件模型上,并設(shè)置真正的寫事件處理函數(shù),這里后面的章節(jié)還會(huì)做詳細(xì)介紹;讀事件的處理函數(shù)設(shè)置為ngx_http_init_request,此時(shí)如果該連接上已經(jīng)有數(shù)據(jù)過(guò)來(lái)(設(shè)置了deferred accept),則會(huì)直接調(diào)用ngx_http_init_request函數(shù)來(lái)處理該請(qǐng)求,反之則設(shè)置一個(gè)定時(shí)器并在事件處理模型上掛載一個(gè)讀事件,等待數(shù)據(jù)到來(lái)或者超時(shí)。當(dāng)然這里不管是已經(jīng)有數(shù)據(jù)到來(lái),或者需要等待數(shù)據(jù)到來(lái),又或者等待超時(shí),最終都會(huì)進(jìn)入讀事件的處理函數(shù)-ngx_http_init_request。
 

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

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


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


由于可能多次進(jìn)入ngx_http_process_request_line函數(shù),ngx_http_read_request_header函數(shù)首先檢查請(qǐng)求的header_in指向的緩沖區(qū)內(nèi)是否有數(shù)據(jù),有的話直接返回;否則從連接讀取數(shù)據(jù)并保存在請(qǐng)求的header_in指向的緩存區(qū),而且只要緩沖區(qū)有空間的話,會(huì)一次盡可能多的讀數(shù)據(jù),讀到多少返回多少;如果客戶端暫時(shí)沒(méi)有發(fā)任何數(shù)據(jù)過(guò)來(lái),并返回NGX_AGAIN,返回之前會(huì)做2件事情:1,設(shè)置一個(gè)定時(shí)器,時(shí)長(zhǎng)默認(rèn)為60s,可以通過(guò)指令client_header_timeout設(shè)置,如果定時(shí)事件到達(dá)之前沒(méi)有任何可讀事件,nginx將會(huì)關(guān)閉此請(qǐng)求;2,調(diào)用ngx_handle_read_event函數(shù)處理一下讀事件-如果該連接尚未在事件處理模型上掛載讀事件,則將其掛載上;如果客戶端提前關(guān)閉了連接或者讀取數(shù)據(jù)發(fā)生了其他錯(cuò)誤,則給客戶端返回一個(gè)400錯(cuò)誤(當(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ù)來(lái)解析,這個(gè)函數(shù)根據(jù)http協(xié)議規(guī)范中對(duì)請(qǐng)求行的定義實(shí)現(xiàn)了一個(gè)有限狀態(tài)機(jī),經(jīng)過(guò)這個(gè)狀態(tài)機(jī),nginx會(huì)記錄請(qǐng)求行中的請(qǐng)求方法(Method),請(qǐng)求uri以及http協(xié)議版本在緩沖區(qū)中的起始位置,在解析過(guò)程中還會(huì)記錄一些其他有用的信息,以便后面的處理過(guò)程中使用。如果解析請(qǐng)求行的過(guò)程中沒(méi)有產(chǎn)生任何問(wèn)題,該函數(shù)會(huì)返回NGX_OK;如果請(qǐng)求行不滿足協(xié)議規(guī)范,該函數(shù)會(huì)立即終止解析過(guò)程,并返回相應(yīng)錯(cuò)誤號(hào);如果緩沖區(qū)數(shù)據(jù)不夠,該函數(shù)返回NGX_AGAIN。在整個(gè)解析http請(qǐng)求的狀態(tài)機(jī)中始終遵循著兩條重要的原則:減少內(nèi)存拷貝和回溯。內(nèi)存拷貝是一個(gè)相對(duì)比較昂貴的操作,大量的內(nèi)存拷貝會(huì)帶來(lái)較低的運(yùn)行時(shí)效率。nginx在需要做內(nèi)存拷貝的地方盡量只拷貝內(nèi)存的起始和結(jié)束地址而不是內(nèi)存本身,這樣做的話僅僅只需要兩個(gè)賦值操作而已,大大降低了開銷,當(dāng)然這樣帶來(lái)的影響是后續(xù)的操作不能修改內(nèi)存本身,如果修改的話,會(huì)影響到所有引用到該內(nèi)存區(qū)間的地方,所以必須很小心的管理,必要的時(shí)候需要拷貝一份。這里不得不提到nginx中最能體現(xiàn)這一思想的數(shù)據(jù)結(jié)構(gòu),ngx_buf_t,它用來(lái)表示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ù)返回了錯(cuò)誤,則直接給客戶端返回400錯(cuò)誤;
如果返回NGX_AGAIN,則需要判斷一下是否是由于緩沖區(qū)空間不夠,還是已讀數(shù)據(jù)不夠。如果是緩沖區(qū)大小不夠了,nginx會(huì)調(diào)用ngx_http_alloc_large_header_buffer函數(shù)來(lái)分配另一塊大緩沖區(qū),如果大緩沖區(qū)還不夠裝下整個(gè)請(qǐng)求行,nginx則會(huì)返回414錯(cuò)誤給客戶端,否則分配了更大的緩沖區(qū)并拷貝之前的數(shù)據(jù)之后,繼續(xù)調(diào)用ngx_http_read_request_header函數(shù)讀取數(shù)據(jù)來(lái)進(jìn)入請(qǐng)求行自動(dòng)機(jī)處理,直到請(qǐng)求行解析結(jié)束;
如果返回了NGX_OK,則表示請(qǐng)求行被正確的解析出來(lái)了,這時(shí)先記錄好請(qǐng)求行的起始地址以及長(zhǎng)度,并將請(qǐng)求uri的path和參數(shù)部分保存在請(qǐng)求結(jié)構(gòu)的uri字段,請(qǐng)求方法起始位置和長(zhǎng)度保存在method_name字段,http版本起始位置和長(zhǎng)度記錄在http_protocol字段。還要從uri中解析出參數(shù)以及請(qǐng)求資源的拓展名,分別保存在args和exten字段。

丟棄請(qǐng)求體

一個(gè)模塊想要主動(dòng)的丟棄客戶端發(fā)過(guò)的請(qǐng)求體,可以調(diào)用nginx核心提供的ngx_http_discard_request_body()接口,主動(dòng)丟棄的原因可能有很多種,如模塊的業(yè)務(wù)邏輯壓根不需要請(qǐng)求體 ,客戶端發(fā)送了過(guò)大的請(qǐng)求體,另外為了兼容http1.1協(xié)議的pipeline請(qǐng)求,模塊有義務(wù)主動(dòng)丟棄不需要的請(qǐng)求體??傊疄榱吮3至己玫目蛻舳思嫒菪裕琻ginx必須主動(dòng)丟棄無(wú)用的請(qǐng)求體。下面開始分析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ù)不長(zhǎng),這里把它完整的列出來(lái)了,函數(shù)的開始同樣先判斷了不需要再做處理的情況:子請(qǐng)求不需要處理,已經(jīng)調(diào)用過(guò)此函數(shù)的也不需要再處理。接著調(diào)用ngx_http_test_expect() 處理http1.1 expect的情況,根據(jù)http1.1的expect機(jī)制,如果客戶端發(fā)送了expect頭,而服務(wù)端不希望接收請(qǐng)求體時(shí),必須返回417(Expectation Failed)錯(cuò)誤。nginx并沒(méi)有這樣做,它只是簡(jiǎn)單的讓客戶端把請(qǐng)求體發(fā)送過(guò)來(lái),然后丟棄掉。接下來(lái),函數(shù)刪掉了讀事件上的定時(shí)器,因?yàn)檫@時(shí)本身就不需要請(qǐng)求體,所以也無(wú)所謂客戶端發(fā)送的快還是慢了,當(dāng)然后面還會(huì)將到,當(dāng)nginx已經(jīng)處理完該請(qǐng)求但客戶端還沒(méi)有發(fā)送完無(wú)用的請(qǐng)求體時(shí),nginx會(huì)在讀事件上再掛上定時(shí)器。
函數(shù)同樣還會(huì)檢查請(qǐng)求頭中的content-length頭,客戶端如果打算發(fā)送請(qǐng)求體,就必須發(fā)送content-length頭,同時(shí)還會(huì)查看其他地方是不是已經(jīng)讀取了請(qǐng)求體。如果確實(shí)有待處理的請(qǐng)求體,函數(shù)接著檢查請(qǐng)求頭buffer中預(yù)讀的數(shù)據(jù),預(yù)讀的數(shù)據(jù)會(huì)直接被丟掉,當(dāng)然如果請(qǐng)求體已經(jīng)被全部預(yù)讀,函數(shù)就直接返回了。

接下來(lái),如果還有剩余的請(qǐng)求體未處理,該函數(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()接口讀取客戶端過(guò)來(lái)的請(qǐng)求體并丟棄。如果客戶端并沒(méi)有一次將請(qǐng)求體發(fā)過(guò)來(lái),函數(shù)會(huì)返回,剩余的數(shù)據(jù)等到下一次讀事件過(guò)來(lái)時(shí),交給ngx_http_discarded_request_body_handler()來(lái)處理,這時(shí),請(qǐng)求的discard_body將被設(shè)置為1用來(lái)標(biāo)識(shí)這種情況。另外請(qǐng)求的引用數(shù)(count)也被加1,這樣做的目的是客戶端可能在nginx處理完請(qǐng)求之后仍未完整發(fā)送待發(fā)送的請(qǐng)求體,增加引用是防止nginx核心在處理完請(qǐng)求后直接釋放了請(qǐng)求的相關(guān)資源。

ngx_http_read_discarded_request_body()函數(shù)非常簡(jiǎn)單,它循環(huán)的從鏈接中讀取數(shù)據(jù)并丟棄,直到讀完接收緩沖區(qū)的所有數(shù)據(jù),如果請(qǐng)求體已經(jīng)被讀完了,該函數(shù)會(huì)設(shè)置讀事件的處理函數(shù)為ngx_http_block_reading,這個(gè)函數(shù)僅僅刪除水平觸發(fā)的讀事件,防止同一事件不斷被觸發(fā)。
再來(lái)看一下讀事件的處理函數(shù)ngx_http_discarded_request_body_handler,這個(gè)函數(shù)每次讀事件來(lái)時(shí)會(huì)被調(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ù)一開始就處理了讀事件超時(shí)的情況,之前說(shuō)到在ngx_http_discard_request_body()函數(shù)中已經(jīng)刪除了讀事件的定時(shí)器,那么什么時(shí)候會(huì)設(shè)置定時(shí)器呢?答案就是在nginx已經(jīng)處理完該請(qǐng)求,但是又沒(méi)有完全將該請(qǐng)求的請(qǐng)求體丟棄的時(shí)候(客戶端可能還沒(méi)有發(fā)送過(guò)來(lái)),在ngx_http_finalize_connection()函數(shù)中,如果檢查到還有未丟棄的請(qǐng)求體時(shí),nginx會(huì)添加一個(gè)讀事件定時(shí)器,它的時(shí)長(zhǎng)為lingering_timeout指令所指定,默認(rèn)為5秒,不過(guò)這個(gè)時(shí)間僅僅兩次讀事件之間的超時(shí)時(shí)間,等待請(qǐng)求體的總時(shí)長(zhǎng)為lingering_time指令所指定,默認(rèn)為30秒。這種情況中,該函數(shù)如果檢測(cè)到超時(shí)事件則直接返回并斷開連接。同樣,還需要控制整個(gè)丟棄請(qǐng)求體的時(shí)長(zhǎng)不能超過(guò)lingering_time設(shè)置的時(shí)間,如果超過(guò)了最大時(shí)長(zhǎng),也會(huì)直接返回并斷開連接。
如果讀事件發(fā)生在請(qǐng)求處理完之前,則不用處理超時(shí)事件,也不用設(shè)置定時(shí)器,函數(shù)只是簡(jiǎn)單的調(diào)用ngx_http_read_discarded_request_body()來(lái)讀取并丟棄數(shù)據(jù)。

相關(guān)文章

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

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

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

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

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

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

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

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

    最近一臺(tái)服務(wù)器要配置多個(gè)前端項(xiàng)目,當(dāng)然前后端分離就需要nginx來(lái)配置了,下面這篇文章主要給大家介紹了關(guān)于nginx部署多個(gè)前端項(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)穿透多個(gè)Windows Web站點(diǎn)端口的步驟詳解

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

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