### 請求頭讀取 (99%)[](http://tengine.taobao.org/book/chapter_12.html#id2 "永久鏈接至標題")
這一節介紹nginx中請求頭的解析,nginx的請求處理流程中,會涉及到2個非常重要的數據結構,ngx_connection_t和ngx_http_request_t,分別用來表示連接和請求,這2個數據結構在本書的前篇中已經做了比較詳細的介紹,沒有印象的讀者可以翻回去復習一下,整個請求處理流程從頭到尾,對應著這2個數據結構的分配,初始化,使用,重用和銷毀。
nginx在初始化階段,具體是在init process階段的ngx_event_process_init函數中會為每一個監聽套接字分配一個連接結構(ngx_connection_t),并將該連接結構的讀事件成員(read)的事件處理函數設置為ngx_event_accept,并且如果沒有使用accept互斥鎖的話,在這個函數中會將該讀事件掛載到nginx的事件處理模型上(poll或者epoll等),反之則會等到init process階段結束,在工作進程的事件處理循環中,某個進程搶到了accept鎖才能掛載該讀事件。
[](http:// "點擊提交Issue,反饋你的意見...")
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 */
/* 為每個監聽套接字分配一個連接結構 */
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;
/* 標識此讀事件為新請求連接事件 */
rev->accept = 1;
...
#if (NGX_WIN32)
/* windows環境下不做分析,但原理類似 */
#else
/* 將讀事件結構的處理函數設置為ngx_event_accept */
rev->handler = ngx_event_accept;
/* 如果使用accept鎖的話,要在后面搶到鎖才能將監聽句柄掛載上事件處理模型上 */
if (ngx_use_accept_mutex) {
continue;
}
/* 否則,將該監聽句柄直接掛載上事件處理模型 */
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;
}
當一個工作進程在某個時刻將監聽事件掛載上事件處理模型之后,nginx就可以正式的接收并處理客戶端過來的請求了。這時如果有一個用戶在瀏覽器的地址欄內輸入一個域名,并且域名解析服務器將該域名解析到一臺由nginx監聽的服務器上,nginx的事件處理模型接收到這個讀事件之后,會交給之前注冊好的事件處理函數ngx_event_accept來處理。
在ngx_event_accept函數中,nginx調用accept函數,從已連接隊列得到一個連接以及對應的套接字,接著分配一個連接結構(ngx_connection_t),并將新得到的套接字保存在該連接結構中,這里還會做一些基本的連接初始化工作:
1, 首先給該連接分配一個內存池,初始大小默認為256字節,可通過connection_pool_size指令設置;
2, 分配日志結構,并保存在其中,以便后續的日志系統使用;
3, 初始化連接相應的io收發函數,具體的io收發函數和使用的事件模型及操作系統相關;
4, 分配一個套接口地址(sockaddr),并將accept得到的對端地址拷貝在其中,保存在sockaddr字段;
5, 將本地套接口地址保存在local_sockaddr字段,因為這個值是從監聽結構ngx_listening_t中可得,而監聽結構中保存的只是配置文件中設置的監聽地址,但是配置的監聽地址可能是通配符*,即監聽在所有的地址上,所以連接中保存的這個值最終可能還會變動,會被確定為真正的接收地址;
6, 將連接的寫事件設置為已就緒,即設置ready為1,nginx默認連接第一次為可寫;
7, 如果監聽套接字設置了TCP_DEFER_ACCEPT屬性,則表示該連接上已經有數據包過來,于是設置讀事件為就緒;
8, 將sockaddr字段保存的對端地址格式化為可讀字符串,并保存在addr_text字段;
最后調用ngx_http_init_connection函數初始化該連接結構的其他部分。
ngx_http_init_connection函數最重要的工作是初始化讀寫事件的處理函數:將該連接結構的寫事件的處理函數設置為ngx_http_empty_handler,這個事件處理函數不會做任何操作,實際上nginx默認連接第一次可寫,不會掛載寫事件,如果有數據需要發送,nginx會直接寫到這個連接,只有在發生一次寫不完的情況下,才會掛載寫事件到事件模型上,并設置真正的寫事件處理函數,這里后面的章節還會做詳細介紹;讀事件的處理函數設置為ngx_http_init_request,此時如果該連接上已經有數據過來(設置了deferred accept),則會直接調用ngx_http_init_request函數來處理該請求,反之則設置一個定時器并在事件處理模型上掛載一個讀事件,等待數據到來或者超時。當然這里不管是已經有數據到來,或者需要等待數據到來,又或者等待超時,最終都會進入讀事件的處理函數-ngx_http_init_request。
ngx_http_init_request函數主要工作即是初始化請求,由于它是一個事件處理函數,它只有唯一一個ngx_event_t *類型的參數,ngx_event_t 結構在nginx中表示一個事件,事件處理的上下文類似于一個中斷處理的上下文,為了在這個上下文得到相關的信息,nginx中一般會將連接結構的引用保存在事件結構的data字段,請求結構的引用則保存在連接結構的data字段,這樣在事件處理函數中可以方便的得到對應的連接結構和請求結構。進入函數內部看一下,首先判斷該事件是否是超時事件,如果是的話直接關閉連接并返回;反之則是指之前accept的連接上有請求過來需要處理。
ngx_http_init_request函數首先在連接的內存池中為該請求分配一個ngx_http_request_t結構,這個結構將用來保存該請求所有的信息。分配完之后,這個結構的引用會被包存在連接的hc成員的request字段,以便于在長連接或pipelined請求中復用該請求結構。在這個函數中,nginx根據該請求的接收端口和地址找到一個默認虛擬服務器配置(listen指令的default_server屬性用來標識一個默認虛擬服務器,否則監聽在相同端口和地址的多個虛擬服務器,其中第一個定義的則為默認)。
nginx配置文件中可以設置多個監聽在不同端口和地址的虛擬服務器(每個server塊對應一個虛擬服務器),另外還根據域名(server_name指令可以配置該虛擬服務器對應的域名)來區分監聽在相同端口和地址的虛擬服務器,每個虛擬服務器可以擁有不同的配置內容,而這些配置內容決定了nginx在接收到一個請求之后如何處理該請求。找到之后,相應的配置被保存在該請求對應的ngx_http_request_t結構中。注意這里根據端口和地址找到的默認配置只是臨時使用一下,最終nginx會根據域名找到真正的虛擬服務器配置,隨后的初始化工作還包括:
1, 將連接的讀事件的處理函數設置為ngx_http_process_request_line函數,這個函數用來解析請求行,將請求的read_event_handler設置為ngx_http_block_reading函數,這個函數實際上什么都不做(當然在事件模型設置為水平觸發時,唯一做的事情就是將事件從事件模型監聽列表中刪除,防止該事件一直被觸發),后面會說到這里為什么會將read_event_handler設置為此函數;
2, 為這個請求分配一個緩沖區用來保存它的請求頭,地址保存在header_in字段,默認大小為1024個字節,可以使用client_header_buffer_size指令修改,這里需要注意一下,nginx用來保存請求頭的緩沖區是在該請求所在連接的內存池中分配,而且會將地址保存一份在連接的buffer字段中,這樣做的目的也是為了給該連接的下一次請求重用這個緩沖區,另外如果客戶端發過來的請求頭大于1024個字節,nginx會重新分配更大的緩存區,默認用于大請求的頭的緩沖區最大為8K,最多4個,這2個值可以用large_client_header_buffers指令設置,后面還會說到請求行和一個請求頭都不能超過一個最大緩沖區的大小;
3, 為這個請求分配一個內存池,后續所有與該請求相關的內存分配一般都會使用該內存池,默認大小為4096個字節,可以使用request_pool_size指令修改;
4, 為這個請求分配響應頭鏈表,初始大小為20;
5, 創建所有模塊的上下文ctx指針數組,變量數據;
6, 將該請求的main字段設置為它本身,表示這是一個主請求,nginx中對應的還有子請求概念,后面的章節會做詳細的介紹;
7, 將該請求的count字段設置為1,count字段表示請求的引用計數;
8, 將當前時間保存在start_sec和start_msec字段,這個時間是該請求的起始時刻,將被用來計算一個請求的處理時間(request time),nginx使用的這個起始點和apache略有差別,nginx中請求的起始點是接收到客戶端的第一個數據包的事件開始,而apache則是接收到客戶端的整個request line后開始算起;
9, 初始化請求的其他字段,比如將uri_changes設置為11,表示最多可以將該請求的uri改寫10次,subrequests被設置為201,表示一個請求最多可以發起200個子請求;
做完所有這些初始化工作之后,ngx_http_init_request函數會調用讀事件的處理函數來真正的解析客戶端發過來的數據,也就是會進入ngx_http_process_request_line函數中處理。
- 上篇:nginx模塊開發篇
- nginx平臺初探
- 初探nginx架構
- nginx基礎概念
- connection
- request
- keepalive
- pipe
- lingering_close
- 基本數據結構
- ngx_str_t
- ngx_pool_t
- ngx_array_t
- ngx_hash_t
- ngx_hash_wildcard_t
- ngx_hash_combined_t
- ngx_hash_keys_arrays_t
- ngx_chain_t
- ngx_buf_t
- ngx_list_t
- ngx_queue_t
- nginx的配置系統
- 指令參數
- 指令上下文
- nginx的模塊化體系結構
- 模塊的分類
- nginx的請求處理
- handler模塊
- handler模塊簡介
- 模塊的基本結構
- 模塊配置結構
- 模塊配置指令
- 模塊上下文結構
- 模塊的定義
- handler模塊的基本結構
- handler模塊的掛載
- handler的編寫步驟
- 示例: hello handler 模塊
- handler模塊的編譯和使用
- 更多handler模塊示例分析
- http access module
- http static module
- http log module
- 過濾模塊
- 過濾模塊簡介
- 過濾模塊的分析
- upstream模塊
- upstream模塊
- upstream模塊接口
- memcached模塊分析
- 本節回顧
- 負載均衡模塊
- 配置
- 指令
- 鉤子
- 初始化配置
- 初始化請求
- peer.get和peer.free回調函數
- 本節回顧
- 其他模塊
- core模塊
- event模塊
- 模塊開發高級篇
- 變量
- 下篇:nginx原理解析篇
- nginx架構詳解
- nginx的源碼目錄結構
- nginx的configure原理
- 模塊編譯順序
- nginx基礎設施
- 內存池
- nginx的啟動階段
- 概述
- 共有流程
- 配置解析
- nginx的請求處理階段
- 接收請求流程
- http請求格式簡介
- 請求頭讀取
- 解析請求行
- 解析請求頭
- 請求體讀取
- 讀取請求體
- 丟棄請求體
- 多階段處理請求
- 多階段執行鏈
- POST_READ階段
- SERVER_REWRITE階段
- FIND_CONFIG階段
- REWRITE階段
- POST_REWRITE階段
- PREACCESS階段
- ACCESS階段
- POST_ACCESS階段
- TRY_FILES階段
- CONTENT階段
- LOG階段
- Nginx filter
- header filter分析
- body filter分析
- ngx_http_copy_filter_module分析
- ngx_http_write_filter_module分析
- subrequest原理解析
- https請求處理解析
- 附錄A 編碼風格
- 附錄B 常用API
- 附錄C 模塊編譯,調試與測試