国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 網(wǎng)站 > Nginx > 正文

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

2024-08-30 12:27:51
字體:
供稿:網(wǎng)友
這篇文章主要介紹了詳解Nginx的核心配置模塊中對(duì)于請(qǐng)求體的接受流程,包括其丟棄請(qǐng)求的過程,需要的朋友可以參考下
 

本篇文章主要會(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),用來說明請(qǐng)求方法,要訪問的資源以及所使用的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

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

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

一般來說根據(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,分別用來表示連接和請(qǐng)求,這2個(gè)數(shù)據(jù)結(jié)構(gòu)在本書的前篇中已經(jīng)做了比較詳細(xì)的介紹,沒有印象的讀者可以翻回去復(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,并且如果沒有使用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) {   ...     /* 初始化用來管理所有定時(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就可以正式的接收并處理客戶端過來的請(qǐng)求了。這時(shí)如果有一個(gè)用戶在瀏覽器的地址欄內(nèi)輸入一個(gè)域名,并且域名解析服務(wù)器將該域名解析到一臺(tái)由nginx監(jiān)聽的服務(wù)器上,nginx的事件處理模型接收到這個(gè)讀事件之后,會(huì)速度交由之前注冊(cè)好的事件處理函數(shù)ngx_event_accept來處理。


在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é),可通過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ù)包過來,于是設(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ù)過來(設(shè)置了deferred accept),則會(huì)直接調(diào)用ngx_http_init_request函數(shù)來處理該請(qǐng)求,反之則設(shè)置一個(gè)定時(shí)器并在事件處理模型上掛載一個(gè)讀事件,等待數(shù)據(jù)到來或者超時(shí)。當(dāng)然這里不管是已經(jīng)有數(shù)據(jù)到來,或者需要等待數(shù)據(jù)到來,又或者等待超時(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)求過來需要處理,ngx_http_init_request函數(shù)首先在連接的內(nèi)存池中為該請(qǐng)求分配一個(gè)ngx_http_request_t結(jié)構(gòu),這個(gè)結(jié)構(gòu)將用來保存該請(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屬性用來標(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)的域名)來區(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ù)用來解析請(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ì)說到這里為什么會(huì)將read_event_handler設(shè)置為此函數(shù);
為這個(gè)請(qǐng)求分配一個(gè)緩沖區(qū)用來保存它的請(qǐng)求頭,地址保存在header_in字段,默認(rèn)大小為1024個(gè)字節(jié),可以使用client_header_buffer_size指令修改,這里需要注意一下,nginx用來保存請(qǐng)求頭的緩沖區(qū)是在該請(qǐng)求所在連接的內(nèi)存池中分配,而且會(huì)將地址保存一份在連接的buffer字段中,這樣做的目的也是為了給該連接的下一次請(qǐng)求重用這個(gè)緩沖區(qū),另外如果客戶端發(fā)過來的請(qǐng)求頭大于1024個(gè)字節(jié),nginx會(huì)重新分配更大的緩存區(qū),默認(rèn)用于大請(qǐng)求的頭的緩沖區(qū)最大為8K,最多4個(gè),這2個(gè)值可以用large_client_header_buffers指令設(shè)置,后面還會(huì)說到請(qǐng)求行和一個(gè)請(qǐng)求頭都不能超過一個(gè)最大緩沖區(qū)的大小;
同樣的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í)刻,將被用來計(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ù)來真正的解析客戶端發(fā)過來的數(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í)沒有發(fā)任何數(shù)據(jù)過來,并返回NGX_AGAIN,返回之前會(huì)做2件事情:1,設(shè)置一個(gè)定時(shí)器,時(shí)長(zhǎng)默認(rèn)為60s,可以通過指令client_header_timeout設(shè)置,如果定時(shí)事件到達(dá)之前沒有任何可讀事件,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ù)來解析,這個(gè)函數(shù)根據(jù)http協(xié)議規(guī)范中對(duì)請(qǐng)求行的定義實(shí)現(xiàn)了一個(gè)有限狀態(tài)機(jī),經(jīng)過這個(gè)狀態(tài)機(jī),nginx會(huì)記錄請(qǐng)求行中的請(qǐng)求方法(Method),請(qǐng)求uri以及http協(xié)議版本在緩沖區(qū)中的起始位置,在解析過程中還會(huì)記錄一些其他有用的信息,以便后面的處理過程中使用。如果解析請(qǐng)求行的過程中沒有產(chǎn)生任何問題,該函數(shù)會(huì)返回NGX_OK;如果請(qǐng)求行不滿足協(xié)議規(guī)范,該函數(shù)會(huì)立即終止解析過程,并返回相應(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ì)帶來較低的運(yùn)行時(shí)效率。nginx在需要做內(nèi)存拷貝的地方盡量只拷貝內(nèi)存的起始和結(jié)束地址而不是內(nèi)存本身,這樣做的話僅僅只需要兩個(gè)賦值操作而已,大大降低了開銷,當(dāng)然這樣帶來的影響是后續(xù)的操作不能修改內(nèi)存本身,如果修改的話,會(huì)影響到所有引用到該內(nèi)存區(qū)間的地方,所以必須很小心的管理,必要的時(shí)候需要拷貝一份。這里不得不提到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ù)返回了錯(cuò)誤,則直接給客戶端返回400錯(cuò)誤;
如果返回NGX_AGAIN,則需要判斷一下是否是由于緩沖區(qū)空間不夠,還是已讀數(shù)據(jù)不夠。如果是緩沖區(qū)大小不夠了,nginx會(huì)調(diào)用ngx_http_alloc_large_header_buffer函數(shù)來分配另一塊大緩沖區(qū),如果大緩沖區(qū)還不夠裝下整個(gè)請(qǐng)求行,nginx則會(huì)返回414錯(cuò)誤給客戶端,否則分配了更大的緩沖區(qū)并拷貝之前的數(shù)據(jù)之后,繼續(xù)調(diào)用ngx_http_read_request_header函數(shù)讀取數(shù)據(jù)來進(jìn)入請(qǐng)求行自動(dòng)機(jī)處理,直到請(qǐng)求行解析結(jié)束;
如果返回了NGX_OK,則表示請(qǐng)求行被正確的解析出來了,這時(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ā)過的請(qǐng)求體,可以調(diào)用nginx核心提供的ngx_http_discard_request_body()接口,主動(dòng)丟棄的原因可能有很多種,如模塊的業(yè)務(wù)邏輯壓根不需要請(qǐng)求體 ,客戶端發(fā)送了過大的請(qǐng)求體,另外為了兼容http1.1協(xié)議的pipeline請(qǐng)求,模塊有義務(wù)主動(dòng)丟棄不需要的請(qǐng)求體。總之為了保持良好的客戶端兼容性,nginx必須主動(dòng)丟棄無用的請(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),這里把它完整的列出來了,函數(shù)的開始同樣先判斷了不需要再做處理的情況:子請(qǐng)求不需要處理,已經(jīng)調(diào)用過此函數(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并沒有這樣做,它只是簡(jiǎn)單的讓客戶端把請(qǐng)求體發(fā)送過來,然后丟棄掉。接下來,函數(shù)刪掉了讀事件上的定時(shí)器,因?yàn)檫@時(shí)本身就不需要請(qǐng)求體,所以也無所謂客戶端發(fā)送的快還是慢了,當(dāng)然后面還會(huì)將到,當(dāng)nginx已經(jīng)處理完該請(qǐng)求但客戶端還沒有發(fā)送完無用的請(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ù)就直接返回了。

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



發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 灵山县| 丰城市| 营山县| 盘山县| 安溪县| 资中县| 安福县| 凤山县| 高州市| 革吉县| 汶川县| 宁津县| 高清| 濮阳县| 平罗县| 盐城市| 门头沟区| 蒙山县| 上犹县| 皮山县| 龙泉市| 瓮安县| 比如县| 崇礼县| 涞水县| 图木舒克市| 呈贡县| 常宁市| 偃师市| 扎赉特旗| 花莲市| 德保县| 奎屯市| 嘉鱼县| 泽库县| 游戏| 岳普湖县| 静安区| 浠水县| 翁牛特旗| 东源县|