if (c->read->ready) {
ngx_http_upstream_process_header(r, u); //讀事件觸發 準備處理http頭部資訊
return;
}
向上遊伺服器發送資料完畢後就會檢測是否收到上遊伺服器的響應:
static void
ngx_http_upstream_process_header(ngx_http_request_t *r, ngx_http_upstream_t *u)
{//讀取FCGI頭部資料,或者proxy頭部資料。ngx_http_upstream_send_request發送完資料後,
//會調用這裡,或者有可寫事件的時候會調用這裡。
//ngx_http_upstream_connect函數連接配接fastcgi後,會設定這個回調函數為fcgi連接配接的可讀事件回調。
ssize_t n;
ngx_int_t rc;
ngx_connection_t *c;
c = u->peer.connection;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http upstream process header, fd:%d, buffer_size:%uz", c->fd, u->conf->buffer_size);
c->log->action = "reading response header from upstream";
if (c->read->timedout) {//讀逾時了,輪詢下一個。 ngx_event_expire_timers逾時後走到這裡
//該定時器添加地方在ngx_http_upstream_send_request
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_TIMEOUT);
return;
}
if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {
////請求未發送到上遊伺服器 同時連接配接測試不通過 轉移到下一個server
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
return;
}
if (u->buffer.start == NULL) { //配置設定一塊緩存,用來存放接受回來的資料。
u->buffer.start = ngx_palloc(r->pool, u->conf->buffer_size);
//頭部行部分(也就是第一個fastcgi data辨別資訊,裡面也會攜帶一部分網頁資料)的fastcgi辨別資訊開辟的空間用buffer_size配置指定
if (u->buffer.start == NULL) {
ngx_http_upstream_finalize_request(r, u,
NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
u->buffer.pos = u->buffer.start;
u->buffer.last = u->buffer.start;
u->buffer.end = u->buffer.start + u->conf->buffer_size;
u->buffer.temporary = 1;
u->buffer.tag = u->output.tag;
//初始化headers_in存放頭部資訊,後端FCGI,proxy解析後的HTTP頭部将放入這裡
if (ngx_list_init(&u->headers_in.headers, r->pool, 8,
sizeof(ngx_table_elt_t))
!= NGX_OK)
{
ngx_http_upstream_finalize_request(r, u,
NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
#if (NGX_HTTP_CACHE)
/*
pVpVZ"
KEY: /test.php
//下面是後端實際傳回的内容,上面的是預留的頭部
IX-Powered-By: PHP/5.2.13
Content-type: text/html
<Html>
<Head>
<title>Your page Subject and domain name</title>
*/
if (r->cache) { //注意這裡跳過了預留的頭部記憶體,用于存儲cache寫入檔案時候的頭部部分,見
u->buffer.pos += r->cache->header_start;
u->buffer.last = u->buffer.pos;
}
#endif
}
for ( ;; ) {
//recv 為 ngx_unix_recv,讀取資料放在u->buffer.last的位置,傳回讀到的大小u->peer.connection->read->ready == 1 (size==n) 時;
//也就是 u->buffer.end-u->buffer.last == size == n; 也就是size 大小的緩存去讀資料時, 傳回的n == szie;也就是核心還有資料沒有讀出來,
//此時 u->perr.connection->read->ready 不為0 标示還有資料沒有讀
n = c->recv(c, u->buffer.last, u->buffer.end - u->buffer.last);
if (n == NGX_AGAIN) { //核心緩沖區已經沒資料了
#if 0
ngx_add_timer(rev, u->read_timeout);
#endif
if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) {
ngx_http_upstream_finalize_request(r, u,
NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
return;
}
if (n == 0) {
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"upstream prematurely closed connection");
}
if (n == NGX_ERROR || n == 0) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
return;
}
u->buffer.last += n;
//ngx_http_xxx_process_header ngx_http_proxy_process_header ngx_http_proxy_process_status_line
rc = u->process_header(r);//ngx_http_fastcgi_process_header等,進行資料處理,比如後端傳回的資料頭部解析,body讀取等。
if (rc == NGX_AGAIN) {
ngx_log_debugall(c->log, 0, " ngx_http_upstream_process_header u->process_header() return NGX_AGAIN");
if (u->buffer.last == u->buffer.end) { //配置設定的用來存儲fastcgi STDOUT頭部行包體的buf已經用完了頭部行都還沒有解析完成,
ngx_log_error(NGX_LOG_ERR, c->log, 0,
"upstream sent too big header");
ngx_http_upstream_next(r, u,
NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
return;
}
continue;
}
break;
}
if (rc == NGX_HTTP_UPSTREAM_INVALID_HEADER) {
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_INVALID_HEADER);
return;
}
if (rc == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
/* rc == NGX_OK */
u->state->header_time = ngx_current_msec - u->state->response_time;
if (u->headers_in.status_n >= NGX_HTTP_SPECIAL_RESPONSE) {
if (ngx_http_upstream_test_next(r, u) == NGX_OK) {
return;
}
if (ngx_http_upstream_intercept_errors(r, u) == NGX_OK) {
return;
}
}
//到這裡,FCGI等格式的資料已經解析為标準HTTP的表示形式了(除了BODY),是以可以進行upstream的process_headers。
//上面的 u->process_header(r)已經進行FCGI等格式的解析了。下面将頭部資料拷貝到headers_out.headers數組中。
if (ngx_http_upstream_process_headers(r, u) != NGX_OK) {
return;
}
if (!r->subrequest_in_memory) {//如果沒有子請求了,那就直接發送響應給用戶端吧。
當請求的ngx_http_request_t結構體中subrequest_in_memory标志位為1時,将采用第1種方式,即upstream不轉發響應包體到下遊,
由HTTP子產品實作的input_filter方法處理包體;當subrequest_in_memory為0時,upstream會轉發響應包體。
當ngx_http_upstream_conf_t配置結構體中的buffering标志位為1時,将開啟更多的記憶體和磁盤檔案用于緩存上遊的響應包體,這意味上遊網速更快;
當buffering為0時,将使用固定大小的緩沖區(就是上面介紹的buffer緩沖區)來轉發響應包體
//buffering方式和非buffering方式在函數ngx_http_upstream_send_response分惜
ngx_http_upstream_send_response(r, u);//給用戶端發送響應,裡面會處理header,body分開發送的情況
return;
}
/* subrequest content in memory */
//子請求,并且後端資料需要儲存到記憶體在
//注意下面隻是把後端資料存到buf中,但是沒有發送到用戶端,實際發送一般是由ngx_http_finalize_request->ngx_http_set_write_handler實作
if (u->input_filter == NULL) {
u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;
u->input_filter = ngx_http_upstream_non_buffered_filter;
u->input_filter_ctx = r;
}
if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
n = u->buffer.last - u->buffer.pos;
if (n) {
u->buffer.last = u->buffer.pos;
u->state->response_length += n;
if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
}
if (u->length == 0) {
ngx_http_upstream_finalize_request(r, u, 0);
return;
}
u->read_event_handler = ngx_http_upstream_process_body_in_memory;//設定body部分的讀事件回調。
ngx_http_upstream_process_body_in_memory(r, u);
}
http request 結構體

/*
對于每個ngx_http_request_t請求來說,隻能通路一個上遊伺服器,但對于一個用戶端請求來說,可以派生出許多子請求,任何一個子請求都
可以通路一個上遊伺服器,這些子請求的結果組合起來就可以使來自用戶端的請求處理複雜的業務。
*/
//ngx_http_parse_request_line解析請求行, ngx_http_process_request_headers解析頭部行(請求頭部)
//請求的所有資訊都可以都可以在ngx_http_request_s結構擷取到
struct ngx_http_request_s { //當接收到用戶端請求資料後,調用ngx_http_create_request中建立并指派
uint32_t signature; /* "HTTP" */
/*
在接收到用戶端資料後,會建立一個ngx_http_request_s,其connection成員指向對應的accept成功後擷取到的連接配接資訊
ngx_connection_t,見ngx_http_create_request
這個請求對應的用戶端連接配接 如果該r是子請求,其connection成員指向頂層root父請求的ngx_connection_t,因為它們都是對應的同一個用戶端連接配接,見ngx_http_subrequest
*/
ngx_connection_t *connection;
/*
ctx與ngx_http_conf_ctxt結構的3個數組成員非常相似,它們都
表示指向void指針的數組。HTTP架構就是在ctx數組中儲存所有HTTP子產品上下文結構體的指針的,所有子產品的請求上下文空間在
ngx_http_create_request中建立。擷取和設定分别在ngx_http_get_module_ctx和ngx_http_set_ctx,為每個請求建立ngx_http_request_s的時候
都會為該請求的ctx[]為所有的子產品建立一個指針,也就是每個子產品在ngx_http_request_s中有一個ctx
*/ //在應答完後,在ngx_http_filter_finalize_request會把ctx指向的空間全部清0 參考4.5節
void **ctx; //指向存放所有HTTP子產品的上下文結構體的指針數組,實際上發送給用戶端的應答完成後,會把ctx全部置0
/*
當用戶端建立連接配接後,并發送請求資料過來後,在ngx_http_create_request中從ngx_http_connection_t->conf_ctx擷取這三個值,也就是根據用戶端連接配接
本端所處IP:port所對應的預設server{}塊上下文,如果是以下情況:ip:port相同,單在不同的server{}塊中,那麼有可能用戶端請求過來的時候攜帶的host
頭部項的server_name不在預設的server{}中,而在另外的server{}中,是以需要通過ngx_http_set_virtual_server重新擷取server{}和location{}上下文配置
例如:
server { #1
listen 1.1.1.1:80;
server_name aaa
}
server { #2
listen 1.1.1.1:80;
server_name bbb
}
這個配置在ngx_http_init_connection中把ngx_http_connection_t->conf_ctx指向ngx_http_addr_conf_s->default_server,也就是指向#1,然後
ngx_http_create_request中把main_conf srv_conf loc_conf 指向#1,
但如果請求行的頭部的host:bbb,那麼需要重新擷取對應的server{} #2,見ngx_http_set_virtual_server
*/ //ngx_http_create_request和ngx_http_set_virtual_server 已經rewrite過程中(例如ngx_http_core_find_location
//ngx_http_core_post_rewrite_phase ngx_http_internal_redirect ngx_http_internal_redirect 子請求ngx_http_subrequest)都可能對他們指派
void **main_conf; //指向請求對應的存放main級别配置結構體的指針數組
void **srv_conf; //指向請求對應的存放srv級别配置結構體的指針數組 指派見ngx_http_set_virtual_server
void **loc_conf; //指向請求對應的存放loc級别配置結構體的指針數組 指派見ngx_http_set_virtual_server
/*
在接收完HTTP頭部,第一次在業務上處理HTTP請求時,HTTP架構提供的處理方法是ngx_http_process_request。但如果該方法無法一次處
理完該請求的全部業務,在歸還控制權到epoll事件子產品後,該請求再次被回調時,将通過ngx_http_request_handler方法來處理,而這個
方法中對于可讀事件的處理就是調用read_event_handler處理請求。也就是說,HTTP子產品希望在底層處理請求的讀事件時,重新實作read_event_handler方法
//在讀取用戶端來的包體時,指派為ngx_http_read_client_request_body_handler
丢棄用戶端的包體時,指派為ngx_http_discarded_request_body_handler
*/ //注意ngx_http_upstream_t和ngx_http_request_t都有該成員 分别在ngx_http_request_handler和ngx_http_upstream_handler中執行
ngx_http_event_handler_pt read_event_handler;
/* 與read_event_handler回調方法類似,如果ngx_http_request_handler方法判斷目前事件是可寫事件,則調用write_event_handler處理請求 */
/*請求行和請求頭部解析完成後,會在ngx_http_handler中指派為ngx_http_core_run_phases 子請求的的handler為ngx_http_handler
當發送響應的時候,如果一次沒有發送完,則設在為ngx_http_writer
*/ //注意ngx_http_upstream_t和ngx_http_request_t都有該成員 分别在ngx_http_request_handler和ngx_http_upstream_handler中執行
//如果采用buffer方式緩存後端包體,則在發送包體給用戶端浏覽器的時候,會把用戶端連接配接的write_e_hand置為ngx_http_upstream_process_downstream
//在觸發epoll_in的同時也會觸發epoll_out,進而會執行該函數
ngx_http_event_handler_pt write_event_handler;//父請求重新激活後的回調方法
#if (NGX_HTTP_CACHE)
//通過ngx_http_upstream_cache_get擷取
ngx_http_cache_t *cache;//在用戶端請求過來後,在ngx_http_upstream_cache->ngx_http_file_cache_new中指派r->caceh = ngx_http_cache_t
#endif
/*
如果沒有使用upstream機制,那麼ngx_http_request_t中的upstream成員是NULL空指針,在ngx_http_upstream_create中建立空間
*/
ngx_http_upstream_t *upstream; //upstream機制用到的結構體
ngx_array_t *upstream_states; //建立空間和指派見ngx_http_upstream_init_request
/* of ngx_http_upstream_state_t */
/*
表示這個請求的記憶體池,在ngx_http_free_request方法中銷毀。它與ngx_connection-t中的記憶體池意義不同,當請求釋放時,TCP連接配接可能并
沒有關閉,這時請求的記憶體池會銷毀,但ngx_connection_t的記憶體池并不會銷毀
*/
ngx_pool_t *pool;
//其中,header_in指向Nginx收到的未經解析的HTTP頭部,這裡暫不關注它(header_in就是接收HTTP頭部的緩沖區)。 header_in存放請求行,headers_in存放頭部行
//請求行和請求頭部内容都在該buffer中
ngx_buf_t *header_in;//用于接收HTTP請求内容的緩沖區,主要用于接收HTTP頭部,該指針指向ngx_connection_t->buffer
//類型的headers_in則存儲已經解析過的HTTP頭部。
/*常用的HTTP頭部資訊可以通過r->headers_in擷取,不常用的HTTP頭部則需要周遊r->headers_in.headers來周遊擷取*/
/*
ngx_http_process_request_headers方法在接收、解析完HTTP請求的頭部後,會把解析完的每一個HTTP頭部加入到headers_in的headers連結清單中,同時會構造headers_in中的其他成員
*/ //參考ngx_http_headers_in,通過該數組中的回調hander來存儲解析到的請求行name:value中的value到headers_in的響應成員中,見ngx_http_process_request_headers
//注意:在需要把用戶端請求頭發送到後端的話,在請求頭後面可能添加有HTTP_相關變量,例如fastcgi,見ngx_http_fastcgi_create_request
ngx_http_headers_in_t headers_in; //http頭部行解析後的内容都由該成員存儲 header_in存放請求行,headers_in存放頭部行
//隻要指定headers_out中的成員,就可以在調用ngx_http_send_header時正确地把HTTP頭部發出
//HTTP子產品會把想要發送的HTTP響應資訊放到headers_out中,期望HTTP架構将headers_out中的成員序列化為HTTP響應包發送給使用者
ngx_http_headers_out_t headers_out;
//如果是upstream指派的來源是後端伺服器會有的頭部行中拷貝,參考ngx_http_upstream_headers_in中的copy_handler
/*
接收完請求的包體後,可以在r->request_body->temp_file->file中擷取臨時檔案(假定将r->request_body_in_file_only标志位設為1,那就一定可以
在這個變量擷取到包體。)。file是一個ngx_file_t類型。這裡,我們可以從
r->request_body->temp_file->file.name中擷取Nginx接收到的請求包體所在檔案的名稱(包括路徑)。
*/ //在ngx_http_read_client_request_body中配置設定存儲空間 讀取的用戶端包體存儲在r->request_body->bufs連結清單和臨時檔案r->request_body->temp_file中 ngx_http_read_client_request_body
//讀取客戶包體即使是存入臨時檔案中,當所有包體讀取完畢後(見ngx_http_do_read_client_request_body),還是會讓r->request_body->bufs指向檔案中的相關偏移記憶體位址
//向上遊發送包體u->request_bufs(ngx_http_fastcgi_create_request),接收用戶端的包體在r->request_body
ngx_http_request_body_t *request_body; //接收HTTP請求中包體的資料結構,為NULL表示還沒有配置設定空間
//min(lingering_time,lingering_timeout)這段時間内可以繼續讀取資料,如果用戶端有發送資料過來,見ngx_http_set_lingering_close
time_t lingering_time; //延遲關閉連接配接的時間
//ngx_http_request_t結構體中有兩個成員表示這個請求的開始處理時間:start sec成員和start msec成員
/*
目前請求初始化時的時間。start sec是格林威治時間1970年1月1日淩晨0點0分0秒到目前時間的秒數。如果這個請求是子請求,則該時間
是子請求的生成時間;如果這個請求是使用者發來的請求,則是在建立起TCP連接配接後,第一次接收到可讀事件時的時間
*/
time_t start_sec;
ngx_msec_t start_msec;//與start_sec配合使用,表示相對于start_set秒的毫秒偏移量
//以下9個成員都是ngx_http_proces s_request_line方法在接收、解析HTTP請求行時解析出的資訊
/*
注意 Nginx中對記憶體的控制相當嚴格,為了避免不必要的記憶體開銷,許多需要用到的成員都不是重新配置設定記憶體後存儲的,而是直接指向使用者請求中的相應位址。
例如,method_name.data、request_start這兩個指針實際指向的都是同一個位址。而且,因為它們是簡單的記憶體指針,不是指向字元串的指針,是以,在大部分情況下,都不能将這些u_char*指針當做字元串使用。
*/ //NGX_HTTP_GET | NGX_HTTP_HEAD等,為NGX_HTTP_HEAD表示隻需要發送HTTP頭部字段
/* HTTP2的method指派見ngx_http_v2_parse_method */
ngx_uint_t method; //對應用戶端請求中請求行的請求方法GET、POS等,取值見NGX_HTTP_GET,也可以用下面的method_name進行字元串比較
/*
http_protocol指向使用者請求中HTTP的起始位址。
http_version是Nginx解析過的協定版本,它的取值範圍如下:
#define NGX_HTTP_VERSION_9 9
#define NGX_HTTP_VERSION_10 1000
#define NGX_HTTP_VERSION_11 1001
建議使用http_version分析HTTP的協定版本。
最後,使用request_start和request_end可以擷取原始的使用者請求行。
*/
ngx_uint_t http_version;//http_version是Nginx解析過的協定版本,它的取值範圍如下:
/* 如果是HTTP2,則指派見ngx_http_v2_construct_request_line */
ngx_str_t request_line; //請求行内容
//ngx_str_t類型的uri成員指向使用者請求中的URI。同理,u_char*類型的uri_start和uri_end也與request_start、method_end的用法相似,唯一不
//同的是,method_end指向方法名的最後一個字元,而uri_end指向URI結束後的下一個位址,也就是最後一個字元的下一個字元位址(HTTP架構的行為),
//這是大部分u_char*類型指針對“xxx_start”和“xxx_end”變量的用法。
//http://10.135.10.167/mytest中的/mytest http://10.135.10.167/mytest?abc?ttt中的/mytest
//同時"GET /mytest?abc?ttt HTTP/1.1"中的mytest和uri中的一樣
ngx_str_t uri;
//arg指向使用者請求中的URL參數。 http://10.135.10.167/mytest?abc?ttt中的abc?ttt
//同時"GET /mytest?abc?ttt HTTP/1.1"中的mytest?abc?ttt和uri中的一樣
/*把請求中GET /download/nginx-1.9.2.rar?st=xhWL03HbtjrojpEAfiD6Mw&e=1452139931 HTTP/1.1的st和e形成變量$arg_st #arg_e,value分别
為xhWL03HbtjrojpEAfiD6Mw 1452139931即$arg_st=xhWL03HbtjrojpEAfiD6Mw,#arg_e=1452139931,見ngx_http_arg */
ngx_str_t args;
/*
ngx_str_t類型的extern成員指向使用者請求的檔案擴充名。例如,在通路“GET /a.txt HTTP/1.1”時,extern的值是{len = 3, data = "txt"},
而在通路“GET /a HTTP/1.1”時,extern的值為空,也就是{len = 0, data = 0x0}。
uri_ext指針指向的位址與extern.data相同。
*/
ngx_str_t exten; //http://10.135.10.167/mytest/ac.txt中的txt
/*
url參數中出現+、空格、=、%、&、#等字元的解決辦法
url出現了有+,空格,/,?,%,#,&,=等特殊符号的時候,可能在伺服器端無法獲得正确的參數值,如何是好?
解決辦法
将這些字元轉化成伺服器可以識别的字元,對應關系如下:
URL字元轉義
用其它字元替代吧,或用全角的。
+ URL 中+号表示空格 %2B
空格 URL中的空格可以用+号或者編碼 %20
/ 分隔目錄和子目錄 %2F
? 分隔實際的URL和參數 %3F
% 指定特殊字元 %25
# 表示書簽 %23
& URL 中指定的參數間的分隔符 %26
= URL 中指定參數的值 %3D
*/
//unparsed_uri表示沒有進行URL解碼的原始請求。例如,當uri為“/a b”時,unparsed_uri是“/a%20b”(空格字元做完編碼後是%20)。
ngx_str_t unparsed_uri;//參考:為什麼要對URI進行編碼:
/* HTTP2的method指派見ngx_http_v2_parse_method,在組新的HTTP2頭部行後,指派見ngx_http_v2_construct_request_line */
ngx_str_t method_name;//見method GET POST等
ngx_str_t http_protocol;//GET /sample.jsp HTTP/1.1 中的HTTP/1.1
/* 當ngx_http_header_filter方法無法一次性發送HTTP頭部時,将會有以下兩個現象同時發生:請求的out成員中将會儲存剩餘的響應頭部,見ngx_http_header_filter */
/* 表示需要發送給用戶端的HTTP響應。out中儲存着由headers_out中序列化後的表示HTTP頭部的TCP流。在調用ngx_http_output_filter方法後,
out中還會儲存待發送的HTTP包體,它是實作異步發送HTTP響應的關鍵 */
ngx_chain_t *out;//ngx_http_write_filter把in中的資料拼接到out後面,然後調用writev發送,沒有發送完
/* 目前請求既可能是使用者發來的請求,也可能是派生出的子請求,而main則辨別一系列相關的派生子請
求的原始請求,我們一般可通過main和目前請求的位址是否相等來判斷目前請求是否為使用者發來的原始請求 */
//main成員始終指向一系列有親緣關系的請求中的唯一的那個原始請求,初始指派見ngx_http_create_request
//用戶端的建立連接配接的時候r->main =r(ngx_http_create_request),如果是建立子請求,sr->main = r->main(ngx_http_subrequest)子請求->main=最上層的r
/* 主請求儲存在main字段中,這裡其實就是最上層跟請求,例如目前是四層子請求,則main始終指向第一層父請求,
而不是第三次父請求,parent指向第三層父請求 */
ngx_http_request_t *main; //指派見ngx_http_subrequest
ngx_http_request_t *parent;//目前請求的父請求。注意,父請求未必是原始請求 指派見ngx_http_subrequest
//ngx_http_subrequest中指派,表示對應的子請求r,該結構可以表示子請求資訊
//postponed删除在ngx_http_finalize_request
//當用戶端請求需要通過多個subrequest通路後端的時候,就需要對這多個後端的應答進行合适的順序整理才能發往用戶端
ngx_http_postponed_request_t *postponed; //與subrequest子請求相關的功能 postponed中資料依次發送參考ngx_http_postpone_filter方法
ngx_http_post_subrequest_t *post_subrequest;/* 儲存回調handler及資料,在子請求執行完,将會調用 */
/* 所有的子請求都是通過posted_requests這個單連結清單來連結起來的,執行post子請求時調用的
ngx_http_run_posted_requests方法就是通過周遊該單連結清單來執行子請求的 */
//ngx_http_post_request中建立ngx_http_posted_request_t空間
//ngx_http_post_request将該子請求挂載在主請求的posted_requests連結清單隊尾,在ngx_http_run_posted_requests中執行
ngx_http_posted_request_t *posted_requests; //通過posted_requests就把各個子請求以單向連結清單的資料結構形式組織起來
/*
全局的ngx_http_phase_engine_t結構體中定義了一個ngx_http_phase_handler_t回調方法組成的數組,而phase_handler成員則與該數組配合使用,
表示請求下次應當執行以phase_handler作為序号指定的數組中的回調方法。HTTP架構正是以這種方式把各個HTTP摸塊內建起來處理請求的
*///phase_handler實際上是該階段的處理方法函數在ngx_http_phase_engine_t->handlers數組中的位置
ngx_int_t phase_handler;
//表示NGX HTTP CONTENT PHASE階段提供給HTTP子產品處理請求的一種方式,content handler指向HTTP子產品實作的請求處理方法,在ngx_http_core_content_phase中執行
//ngx_http_proxy_handler ngx_http_redis2_handler ngx_http_fastcgi_handler等
ngx_http_handler_pt content_handler; ////在ngx_http_update_location_config中指派給r->content_handler = clcf->handler;
/*
在NGX_HTTP_ACCESS_PHASE階段需要判斷請求是否具有通路權限時,通過access_code來傳遞HTTP子產品的handler回調方法的傳回值,如果access_code為0,
則表示請求具備通路權限,反之則說明請求不具備通路權限
NGXHTTPPREACCESSPHASE、NGX_HTTP_ACCESS_PHASE、NGX HTTPPOST_ACCESS_PHASE,很好了解,做通路權限檢查的前期、中期、後期工作,
其中後期工作是固定的,判斷前面通路權限檢查的結果(狀态碼存故在字段r->access_code内),如果目前請求沒有通路權限,那麼直接傳回狀
态403錯誤,是以這個階段也無法去挂載額外的回調函數。
*/
ngx_uint_t access_code; //指派見ngx_http_core_access_phase
/*
ngx_http_core_main_conf_t->variables數組成員的結構式ngx_http_variable_s, ngx_http_request_s->variables數組成員結構是ngx_variable_value_t,
這兩個結構的關系很密切,一個所謂變量,一個所謂變量值
r->variables這個變量和cmcf->variables是一一對應的,形成var_ name與var_value對,是以兩個數組裡的同一個下标位置元素剛好就是
互相對應的變量名和變量值,而我們在使用某個變量時總會先通過函數ngx_http_get_variable_index獲得它在變量名數組裡的index下标,也就是變
量名裡的index字段值,然後利用這個index下标進而去變量值數組裡取對應的值
*/ //配置設定的節點數見ngx_http_create_request,和ngx_http_core_main_conf_t->variables一一對應
//變量ngx_http_script_var_code_t->index表示Nginx變量$file在ngx_http_core_main_conf_t->variables數組内的下标,對應每個請求的變量值存儲空間就為r->variables[code->index],參考ngx_http_script_set_var_code
ngx_http_variable_value_t *variables; //注意和ngx_http_core_main_conf_t->variables的差別
#if (NGX_PCRE)
/*
例如正規表達式語句re.name= ^(/download/.*)/media/(.*)/tt/(.*)$, s=/download/aa/media/bdb/tt/ad,則他們會比對,同時比對的
變量數有3個,則傳回值為3+1=4,如果不比對則傳回-1
這裡*2是因為擷取前面例子中的3個變量對應的值需要成對使用r->captures,參考ngx_http_script_copy_capture_code等
*/
ngx_uint_t ncaptures; //指派見ngx_http_regex_exec //最大的$n*2
int *captures; //每個不同的正則解析之後的結果,存放在這裡。$1,$2等
u_char *captures_data; //進行正規表達式比對的原字元串,例如http://10.135.2.1/download/aaa/media/bbb.com中的/download/aaa/media/bbb.com
#endif
/* limit_rate成員表示發送響應的最大速率,當它大于0時,表示需要限速。limit rate表示每秒可以發送的位元組數,超過這個數字就需要限速;
然而,限速這個動作必須是在發送了limit_rate_after位元組的響應後才能生效(對于小響應包的優化設計) */
//實際最後通過ngx_writev_chain發送資料的時候,還會限制一次
size_t limit_rate; //限速的相關計算方法參考ngx_http_write_filter
size_t limit_rate_after;
/* used to learn the Apache compatible response length without a header */
size_t header_size; //所有頭部行内容之和,可以參考ngx_http_header_filter
off_t request_length; //HTTP請求的全部長度,包括HTTP包體
ngx_uint_t err_status; //錯誤碼,取值為NGX_HTTP_BAD_REQUEST等
//當連接配接建立成功後,當收到用戶端的第一個請求的時候會通過ngx_http_wait_request_handler->ngx_http_create_request建立ngx_http_request_t
//同時把r->http_connection指向accept用戶端連接配接成功時候建立的ngx_http_connection_t,這裡面有存儲server{}上下文ctx和server_name等資訊
//該ngx_http_request_t會一直有效,除非關閉連接配接。是以該函數隻會調用一次,也就是第一個用戶端請求封包過來的時候建立,一直持續到連接配接關閉
//該結構存儲了伺服器端接收用戶端連接配接時,伺服器端所在的server{]上下文ctx server_name等配置資訊
ngx_http_connection_t *http_connection; //存儲ngx_connection_t->data指向的ngx_http_connection_t,見ngx_http_create_request
#if (NGX_HTTP_SPDY)
ngx_http_spdy_stream_t *spdy_stream;
#endif
#if (NGX_HTTP_V2)
/* 指派見ngx_http_v2_create_stream */
ngx_http_v2_stream_t *stream;
#endif
ngx_http_log_handler_pt log_handler;
//在這個請求中如果打開了某些資源,并需要在請求結束時釋放,那麼都需要在把定義的釋放資源方法添加到cleanup成員中
/*
如果沒有需要清理的資源,則cleanup為空指針,否則HTTP子產品可以向cleanup中以單連結清單的形式無限制地添加ngx_http_cleanup_t結構體,
用以在請求結束時釋放資源 */
ngx_http_cleanup_t *cleanup;
//預設值r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;見ngx_http_create_request
unsigned subrequests:8; //該r最多還可以處理多少個子請求
/*
在閱讀HTTP反向代理子產品(ngx_http_proxy_module)源代碼時,會發現它并沒有調用r->main->count++,其中proxy子產品是這樣啟動upstream機制的:
ngx_http_read_client_request_body(r,ngx_http_upstream_init);,這表示讀取完使用者請求的HTTP包體後才會調用ngx_http_upstream_init方法
啟動upstream機制。由于ngx_http_read_client_request_body的第一行有效語句是r->maln->count++,是以HTTP反向代理子產品不能
再次在其代碼中執行r->main->count++。
這個過程看起來似乎讓人困惑。為什麼有時需要把引用計數加1,有時卻不需要呢?因為ngx_http_read- client_request_body讀取請求包體是
一個異步操作(需要epoll多次排程方能完成的可稱其為異步操作),ngx_http_upstream_init方法啟用upstream機制也是一個異步操作,是以,
從理論上來說,每執行一次異步操作應該把引用計數加1,而異步操作結束時應該調用ngx_http_finalize_request方法把引用計數減1。另外,
ngx_http_read_client_request_body方法内是加過引用計數的,而ngx_http_upstream_init方法内卻沒有加過引用計數(或許Nginx将來會修改
這個問題)。在HTTP反向代理子產品中,它的ngx_http_proxy_handler方法中用“ngx_http_read- client_request_body(r,ngx_http_upstream_init);”
語句同時啟動了兩個異步操作,注意,這行語句中隻加了一次引用計數。執行這行語句的ngx_http_proxy_handler方法傳回時隻調用
ngx_http_finalize_request方法一次,這是正确的。對于mytest子產品也一樣,務必要保證對引用計數的增加和減少是配對進行的。
*/
/*
表示目前請求的引用次數。例如,在使用subrequest功能時,依附在這個請求上的子請求數目會傳回到count上,每增加一個子請求,count數就要加1。
其中任何一個子請求派生出新的子請求時,對應的原始請求(main指針指向的請求)的count值都要加1。又如,當我們接收HTTP包體時,由于這也是
一個異步調用,是以count上也需要加1,這樣在結束請求時,就不會在count引用計數未清零時銷毀請求
*/
unsigned count:8; //應用計數 ngx_http_close_request中-1
/* 如果AIO上下文中還在處理這個請求,blocked必然是大于0的,這時ngx_http_close_request方法不能結束請求
ngx_http_copy_aio_handler會自增,當核心把資料發送出去後會在ngx_http_copy_aio_event_handler自剪
*/
unsigned blocked:8; //阻塞标志位,目前僅由aio使用 為0,表示沒有HTTP子產品還需要處理請求
//ngx_http_copy_aio_handler handler ngx_http_copy_aio_event_handler執行後,會置回到0
//ngx_http_copy_thread_handler ngx_http_copy_thread_event_handler置0
//ngx_http_cache_thread_handler置1, ngx_http_cache_thread_event_handler置0
//ngx_http_file_cache_aio_read中置1,
unsigned aio:1; //标志位,為1時表示目前請求正在使用異步檔案IO
unsigned http_state:4; //指派見ngx_http_state_e中的成員
/* URI with "/." and on Win32 with "//" */
unsigned complex_uri:1;
/* URI with "%" */
unsigned quoted_uri:1;
/* URI with "+" */
unsigned plus_in_uri:1;
/* URI with " " */
unsigned space_in_uri:1; //uri中是否帶有空格
//頭部幀内容部分header合法性檢查,見ngx_http_v2_validate_header
unsigned invalid_header:1; //頭部行解析不正确,見ngx_http_parse_header_line
unsigned add_uri_to_alias:1;
unsigned valid_location:1; //ngx_http_handler中置1
//如果有rewrite 内部重定向 uri帶有args等會直接置0,否則如果uri中有空格會置1
unsigned valid_unparsed_uri:1;//r->valid_unparsed_uri = r->space_in_uri ? 0 : 1;
/*
将uri_changed設定為0後,也就标志說URL沒有變化,那麼,在ngx_http_core_post_rewrite_phase中就不會執行裡面的if語句,也就不會
再次走到find config的過程了,而是繼續處理後面的。不然正常情況,rewrite成功後是會重新來一次的,相當于一個全新的請求。
*/ // 例如rewrite ^.*$ www.galaxywind.com last;就會多次執行rewrite ngx_http_script_regex_start_code中置1
unsigned uri_changed:1; //标志位,為1時表示URL發生過rewrite重寫 隻要不是rewrite xxx bbb sss;aaa不是break結束都會置1
//表示使用rewrite重寫URL的次數。因為目前最多可以更改10次,是以uri_changes初始化為11,而每重寫URL -次就把uri_changes減1,
//一旦uri_changes等于0,則向使用者傳回失敗
unsigned uri_changes:4; //NGX_HTTP_MAX_URI_CHANGES + 1;
unsigned request_body_in_single_buf:1;//client_body_in_single_buffer on | off;設定
//置1包體需要存入臨時檔案中 如果request_body_no_buffering為1表示不用緩存包體,那麼request_body_in_file_only也為0,因為不用緩存包體,那麼就不用寫到臨時檔案中
/*注意:如果每次開辟的client_body_buffer_size空間都存儲滿了還沒有讀取到完整的包體,則還是會把之前讀滿了的buf中的内容拷貝到臨時檔案,參考
ngx_http_do_read_client_request_body -> ngx_http_request_body_filter和ngx_http_read_client_request_body -> ngx_http_request_body_filter
*/
unsigned request_body_in_file_only:1; //"client_body_in_file_only on |clean"設定 和request_body_no_buffering是互斥的
unsigned request_body_in_persistent_file:1; //"client_body_in_file_only on"設定
unsigned request_body_in_clean_file:1;//"client_body_in_file_only clean"設定
unsigned request_body_file_group_access:1; //是否有組權限,如果有一般為0600
unsigned request_body_file_log_level:3;
//預設是為0的表示需要緩存用戶端包體,決定是否需要轉發用戶端包體到後端,如果request_body_no_buffering為1表示不用緩存包體,那麼request_body_in_file_only也為0,因為不用緩存包體,那麼就不用寫到臨時檔案中
unsigned request_body_no_buffering:1; //是否緩存HTTP包體,如果不緩存包體,和request_body_in_file_only是互斥的,見ngx_http_read_client_request_body
/*
upstream有3種處理上遊響應包體的方式,但HTTP子產品如何告訴upstream使用哪一種方式處理上遊的響應包體呢?
當請求的ngx_http_request_t結構體中subrequest_in_memory标志位為1時,将采用第1種方式,即upstream不轉發響應包體
到下遊,由HTTP子產品實作的input_filter方法處理包體;當subrequest_in_memory為0時,upstream會轉發響應包體。當ngx_http_upstream_conf_t
配置結構體中的buffering标志位為1時,将開啟更多的記憶體和磁盤檔案用于緩存上遊的響應包體,這意味上遊網速更快;當buffering
為0時,将使用固定大小的緩沖區(就是上面介紹的buffer緩沖區)來轉發響應包體。
*/
unsigned subrequest_in_memory:1; //ngx_http_subrequest中指派 NGX_HTTP_SUBREQUEST_IN_MEMORY
unsigned waited:1; //ngx_http_subrequest中指派 NGX_HTTP_SUBREQUEST_WAITED
#if (NGX_HTTP_CACHE)
unsigned cached:1;//如果用戶端請求過來有讀到緩存檔案,則置1,見ngx_http_file_cache_read ngx_http_upstream_cache_send
#endif
#if (NGX_HTTP_GZIP)
unsigned gzip_tested:1;
unsigned gzip_ok:1;
unsigned gzip_vary:1;
#endif
unsigned proxy:1;
unsigned bypass_cache:1;
unsigned no_cache:1;
/*
* instead of using the request context data in
* ngx_http_limit_conn_module and ngx_http_limit_req_module
* we use the single bits in the request structure
*/
unsigned limit_conn_set:1;
unsigned limit_req_set:1;
#if 0
unsigned cacheable:1;
#endif
unsigned pipeline:1;
//如果後端發送過來的頭部行中不帶有Content-length:xxx 這種情況1.1版本HTTP直接設定chunked為1, 見ngx_http_chunked_header_filter
//如果後端帶有Transfer-Encoding: chunked會置1
unsigned chunked:1; //chunk編碼方式組包實際組包過程參考ngx_http_chunked_body_filter
//當下遊的r->method == NGX_HTTP_HEAD請求方法隻請求頭部行,則會在ngx_http_header_filter中置1
//HTTP2頭部幀發送在ngx_http_v2_header_filter中置1
unsigned header_only:1; //表示是否隻有行、頭部,沒有包體 ngx_http_header_filter中置1
//在1.0以上版本預設是長連接配接,1.0以上版本預設置1,如果在請求頭裡面沒有設定連接配接方式,見ngx_http_handler
//标志位,為1時表示目前請求是keepalive請求 1長連接配接 0短連接配接 長連接配接時間通過請求頭部的Keep-Alive:設定,參考ngx_http_headers_in_t
unsigned keepalive:1; //指派見ngx_http_handler
//延遲關閉标志位,為1時表示需要延遲關閉。例如,在接收完HTTP頭部時如果發現包體存在,該标志位會設為1,而放棄接收包體時則會設為o
unsigned lingering_close:1;
//如果discard_body為1,則證明曾經執行過丢棄包體的方法,現在包體正在被丢棄中,見ngx_http_read_client_request_body
unsigned discard_body:1;//标志住,為1時表示正在丢棄HTTP請求中的包體
unsigned reading_body:1; //标記包體還沒有讀完,需要繼續讀取包體,見 ngx_http_read_client_request_body
/* 在這一步驟中,把phase_handler序号設為server_rewrite_index,這意味着無論之前執行到哪一個階段,馬上都要重新從NGX_HTTP_SERVER_REWRITE_PHASE
階段開始再次執行,這是Nginx的請求可以反複rewrite重定向的基礎。見ngx_http_handler */
//ngx_http_internal_redirect置1 建立子請求的時候,子請求也要置1,見ngx_http_subrequest,所有子請求需要做重定向
//内部重定向是從NGX_HTTP_SERVER_REWRITE_PHASE處繼續執行(ngx_http_internal_redirect),而重新rewrite是從NGX_HTTP_FIND_CONFIG_PHASE處執行(ngx_http_core_post_rewrite_phase)
unsigned internal:1;//t标志位,為1時表示請求的目前狀态是在做内部跳轉,
unsigned error_page:1; //預設0,在ngx_http_special_response_handler中可能置1
unsigned filter_finalize:1;
unsigned post_action:1;//ngx_http_post_action中置1 預設為0,除非post_action XXX配置
unsigned request_complete:1;
unsigned request_output:1;//表示有資料需要往用戶端發送,ngx_http_copy_filter中置1
//為I時表示發送給用戶端的HTTP響應頭部已經發送。在調用ngx_http_send_header方法後,若已經成功地啟動響應頭部發送流程,
//該标志位就會置為1,用來防止反複地發送頭部
unsigned header_sent:1;
unsigned expect_tested:1;
unsigned root_tested:1;
unsigned done:1;
unsigned logged:1;
/* ngx_http_copy_filter中指派 */
unsigned buffered:4;//表示緩沖中是否有待發送内容的标志位,參考ngx_http_copy_filter
unsigned main_filter_need_in_memory:1;
unsigned filter_need_in_memory:1;
unsigned filter_need_temporary:1;
unsigned allow_ranges:1; //支援斷點續傳 參考3.8.3節
unsigned single_range:1;
//
unsigned disable_not_modified:1; //r->disable_not_modified = !u->cacheable;是以預設為0
#if (NGX_STAT_STUB)
unsigned stat_reading:1;
unsigned stat_writing:1;
#endif
/* used to parse HTTP headers */ //狀态機解析HTTP時使用state來表示目前的解析狀态
ngx_uint_t state; //解析狀态,見ngx_http_parse_header_line
//header_hash為Accept-Language:zh-cn中Accept-Language所有字元串做hash運算的結果
ngx_uint_t header_hash; //頭部行中一行所有内容計算ngx_hash的結構,參考ngx_http_parse_header_line
//lowcase_index為Accept-Language:zh-cn中Accept-Language字元數,也就是15個位元組
ngx_uint_t lowcase_index; // 參考ngx_http_parse_header_line
//存儲Accept-Language:zh-cn中的Accept-Language字元串到lowcase_header。如果是AAA_BBB:CCC,則該數組存儲的是_BBB
u_char lowcase_header[NGX_HTTP_LC_HEADER_LEN]; //http頭部内容,不包括應答行或者請求行,參考ngx_http_parse_header_line
/*
例如:Accept:image/gif.image/jpeg,**
Accept對應于key,header_name_start header_name_end分别指向這個Accept字元串的頭和尾
image/gif.image/jpeg,** 為value部分,header_start header_end分别對應value的頭和尾,可以參考mytest_upstream_process_header
*/
//header_name_start指向Accept-Language:zh-cn中的A處
u_char *header_name_start; //解析到的一行http頭部行中的一行的name開始處 //指派見ngx_http_parse_header_line
//header_name_start指向Accept-Language:zh-cn中的:處
u_char *header_name_end; //解析到的一行http頭部行中的一行的name的尾部 //指派見ngx_http_parse_header_line
u_char *header_start;//header_start指向Accept-Language:zh-cn中的z字元處
u_char *header_end;//header_end指向Accept-Language:zh-cn中的末尾換行處
/*
* a memory that can be reused after parsing a request line
* via ngx_http_ephemeral_t
*/
//ngx_str_t類型的uri成員指向使用者請求中的URI。同理,u_char*類型的uri_start和uri_end也與request_start、request_end的用法相似,唯一不
//同的是,method_end指向方法名的最後一個字元,而uri_end指向URI結束後的下一個位址,也就是最後一個字元的下一個字元位址(HTTP架構的行為),
//這是大部分u_char*類型指針對“xxx_start”和“xxx_end”變量的用法。
u_char *uri_start;//HTTP2的指派見ngx_http_v2_parse_path
u_char *uri_end;//HTTP2的指派見ngx_http_v2_parse_path
/*
ngx_str_t類型的extern成員指向使用者請求的檔案擴充名。例如,在通路“GET /a.txt HTTP/1.1”時,extern的值是{len = 3, data = "txt"},
而在通路“GET /a HTTP/1.1”時,extern的值為空,也就是{len = 0, data = 0x0}。
uri_ext指針指向的位址與extern.data相同。
*/ //GET /sample.jsp HTTP/1.1 後面的檔案如果有.字元,則指向該.後面的jsp字元串,表示檔案擴充名
u_char *uri_ext;
//"GET /aaaaaaaa?bbbb.txt HTTP/1.1"中的bbb.txt字元串頭位置處
u_char *args_start;//args_start指向URL參數的起始位址,配合uri_end使用也可以獲得URL參數。
/* 通過request_start和request_end可以獲得使用者完整的請求行 */
u_char *request_start; //請求行開始處
u_char *request_end; //請求行結尾處
u_char *method_end; //GET POST字元串結尾處
//HTTP2的指派見ngx_http_v2_parse_scheme
u_char *schema_start;
u_char *schema_end;
u_char *host_start;
u_char *host_end;
u_char *port_start;
u_char *port_end;
// HTTP/1.1前面的1代表major,後面的1代表minor
unsigned http_minor:16;
unsigned http_major:16;
};
View Code
http server發送響應到請求端處理
//發送後端傳回回來的資料給用戶端。裡面會處理header,body分開發送的情況的
static void
ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
{
int tcp_nodelay;
ssize_t n;
ngx_int_t rc;
ngx_event_pipe_t *p;
ngx_connection_t *c;
ngx_http_core_loc_conf_t *clcf;
int flag;
time_t now, valid;
rc = ngx_http_send_header(r);//先發header,再發body //調用每一個filter過濾,處理頭部資料。最後将資料發送給用戶端。調用ngx_http_top_header_filter
if (rc == NGX_ERROR || rc > NGX_OK || r->post_action) {
ngx_http_upstream_finalize_request(r, u, rc);
return;
}
u->header_sent = 1;//标記已經發送了頭部字段,至少是已經挂載出去,經過了filter了。
if (u->upgrade) {
ngx_http_upstream_upgrade(r, u);
return;
}
c = r->connection;
if (r->header_only) {//如果隻需要發送頭部資料,比如用戶端用curl -I 通路的。傳回204狀态碼即可。
if (!u->buffering) { //配置不需要緩存包體,或者後端要求不配置緩存包體,直接結束
ngx_http_upstream_finalize_request(r, u, rc);
return;
}
if (!u->cacheable && !u->store) { //如果定義了#if (NGX_HTTP_CACHE)則可能置1
ngx_http_upstream_finalize_request(r, u, rc);
return;
}
u->pipe->downstream_error = 1; //命名用戶端隻請求頭部行,但是上遊雀配置或者要求緩存或者存儲包體
}
if (r->request_body && r->request_body->temp_file) { //用戶端發送過來的包體存儲在臨時檔案中,則需要把存儲臨時檔案删除
ngx_pool_run_cleanup_file(r->pool, r->request_body->temp_file->file.fd);
//之前臨時檔案内容已經不需要了,因為在ngx_http_fastcgi_create_request(ngx_http_xxx_create_request)中已經把臨時檔案中的内容
//指派給u->request_bufs并通過發送到了後端伺服器,現在需要發往用戶端的内容為上遊應答回來的包體,是以此臨時檔案内容已經沒用了
r->request_body->temp_file->file.fd = NGX_INVALID_FILE;
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
/*
如果開啟緩沖,那麼Nginx将盡可能多地讀取後端伺服器的響應資料,等達到一定量(比如buffer滿)再傳送給最終用戶端。如果關閉,
那麼Nginx對資料的中轉就是一個同步的過程,即從後端伺服器接收到響應資料就立即将其發送給用戶端。
*/
flag = u->buffering;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "ngx_http_upstream_send_response, buffering flag:%d", flag);
if (!u->buffering) {
//buffering為1,表示上遊來的包體先緩存上遊發送來的包體,然後在發送到下遊,如果該值為0,則接收多少上遊包體就向下遊轉發多少包體
if (u->input_filter == NULL) { //如果input_filter為空,則設定預設的filter,然後準備發送資料到用戶端。然後試着讀讀FCGI
u->input_filter_init = ngx_http_upstream_non_buffered_filter_init;
//ngx_http_upstream_non_buffered_filter将u->buffer.last - u->buffer.pos之間的資料放到u->out_bufs發送緩沖去連結清單裡面。
//根據具體的到上遊轉發的方式,選擇使用fastcgi memcached等,ngx_http_xxx_filter
u->input_filter = ngx_http_upstream_non_buffered_filter; //一般就設定為這個預設的,memcache為ngx_http_memcached_filter
u->input_filter_ctx = r;
}
//設定upstream的讀事件回調,設定用戶端連接配接的寫事件回調。
u->read_event_handler = ngx_http_upstream_process_non_buffered_upstream;
r->write_event_handler =
ngx_http_upstream_process_non_buffered_downstream;//調用過濾子產品一個個過濾body,最終發送出去。
r->limit_rate = 0;
//ngx_http_XXX_input_filter_init(如ngx_http_fastcgi_input_filter_init ngx_http_proxy_input_filter_init ngx_http_proxy_input_filter_init)
//隻有memcached會執行ngx_http_memcached_filter_init,其他方式什麼也沒做
if (u->input_filter_init(u->input_filter_ctx) == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
if (clcf->tcp_nodelay && c->tcp_nodelay == NGX_TCP_NODELAY_UNSET) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "tcp_nodelay");
tcp_nodelay = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY,
(const void *) &tcp_nodelay, sizeof(int)) == -1)
{
ngx_connection_error(c, ngx_socket_errno,
"setsockopt(TCP_NODELAY) failed");
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
c->tcp_nodelay = NGX_TCP_NODELAY_SET;
}
n = u->buffer.last - u->buffer.pos;
/*
不是還沒接收包體嘛,為什麼就開始發送了呢? 這是因為在前面的ngx_http_upstream_process_header接收fastcgi頭部行辨別包體處理的時候,有可能會把一部分fastcgi包體辨別也收過了,
是以需要處理
*/
if (n) {//得到将要發送的資料的大小,每次有多少就發送多少。不等待upstream了 因為這是不緩存方式發送包體到用戶端
u->buffer.last = u->buffer.pos;
u->state->response_length += n;//統計請求的傳回包體資料(不包括請求行)長度。
//下面input_filter隻是簡單的拷貝buffer上面的資料總共n長度的,到u->out_bufs裡面去,以待發送。
//ngx_http_xxx_non_buffered_filter(如ngx_http_fastcgi_non_buffered_filter)
if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
ngx_http_upstream_process_non_buffered_downstream(r);
} else {
u->buffer.pos = u->buffer.start;
u->buffer.last = u->buffer.start;
if (ngx_http_send_special(r, NGX_HTTP_FLUSH) == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
/------------/u->peer.connection->read->ready == 1 (size==n)
if (u->peer.connection->read->ready || u->length == 0) {
ngx_http_upstream_process_non_buffered_upstream(r, u);
}
}
return; //這裡會傳回回去
}
/* TODO: preallocate event_pipe bufs, look "Content-Length" */
----------------------------
----------------------------
p->preread_bufs = ngx_alloc_chain_link(r->pool);
if (p->preread_bufs == NULL) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
p->preread_bufs->buf = &u->buffer; //把包體部分的pos和last存儲到p->preread_bufs->buf
p->preread_bufs->next = NULL;
u->buffer.recycled = 1;
//之前讀取後端頭部行資訊的時候的buf還有剩餘資料,這部分資料就是包體資料,也就是讀取頭部行fastcgi辨別資訊的時候把部分包體資料讀取了
p->preread_size = u->buffer.last - u->buffer.pos;
if (u->cacheable) { //注意走到這裡的時候,前面已經把後端頭部行資訊解析出來了,u->buffer.pos指向的是實際資料部分
p->buf_to_file = ngx_calloc_buf(r->pool);
if (p->buf_to_file == NULL) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
//指向的是為擷取後端頭部行的時候配置設定的第一個緩沖區,buf大小由xxx_buffer_size(fastcgi_buffer_size proxy_buffer_size memcached_buffer_size)指定
/*
這裡面隻存儲了頭部行buffer中頭部行的内容部分,因為後面寫臨時檔案的時候,需要把後端頭部行也寫進來,由于前面讀取頭部行後指針已經指向了資料部分
是以需要臨時用buf_to_file->start指向頭部行部分開始,pos指向資料部分開始,也就是頭部行部分結尾
*/
p->buf_to_file->start = u->buffer.start;
p->buf_to_file->pos = u->buffer.start;
p->buf_to_file->last = u->buffer.pos;
p->buf_to_file->temporary = 1;
}
-----------------------------------
//buffering方式,後端頭部資訊已經讀取完畢了,如果後端還有包體需要發送,則本端通過該方式讀取
u->read_event_handler = ngx_http_upstream_process_upstream;
r->write_event_handler = ngx_http_upstream_process_downstream; //當可寫事件促發的時候,通過該函數繼續寫資料
ngx_http_upstream_process_upstream(r, u);
}
處理響應包體轉發
/*
ngx_http_upstream_send_response發送完HERDER後,如果是非緩沖模式,會調用這裡将資料發送出去的。
這個函數實際上判斷一下逾時後,就調用ngx_http_upstream_process_non_buffered_request了。nginx老方法。
*/
static void
//buffring模式通過ngx_http_upstream_process_upstream該函數處理,非buffring模式通過ngx_http_upstream_process_non_buffered_downstream處理
ngx_http_upstream_process_non_buffered_downstream(ngx_http_request_t *r)
{
ngx_event_t *wev;
ngx_connection_t *c;
ngx_http_upstream_t *u;
c = r->connection;
u = r->upstream;
wev = c->write;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http upstream process non buffered downstream");
c->log->action = "sending to client";
if (wev->timedout) {
c->timedout = 1;
ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out");
ngx_http_upstream_finalize_request(r, u, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
//下面開始将out_bufs裡面的資料發送出去,然後讀取資料,然後發送,如此循環。
ngx_http_upstream_process_non_buffered_request(r, 1);
}
/*
調用過濾子產品,将資料發送出去,do_write為是否要給用戶端發送資料。
1.如果要發送,就調用ngx_http_output_filter将資料發送出去。
2.然後ngx_unix_recv讀取資料,放入out_bufs裡面去。如此循環
*/
static void
ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r,
ngx_uint_t do_write)
{
size_t size;
ssize_t n;
ngx_buf_t *b;
ngx_int_t rc;
ngx_connection_t *downstream, *upstream;
ngx_http_upstream_t *u;
ngx_http_core_loc_conf_t *clcf;
u = r->upstream;
downstream = r->connection;//找到這個請求的用戶端連接配接
upstream = u->peer.connection;//找到上遊的連接配接
b = &u->buffer; //找到這坨要發送的資料,不過大部分都被input filter放到out_bufs裡面去了。
do_write = do_write || u->length == 0; //do_write為1時表示要立即發送給用戶端。
for ( ;; ) {
if (do_write) { //要立即發送。
//out_bufs中的資料是從ngx_http_fastcgi_non_buffered_filter擷取
if (u->out_bufs || u->busy_bufs) {
//如果u->out_bufs不為NULL則說明有需要發送的資料,這是u->input_filter_init(u->input_filter_ctx)(ngx_http_upstream_non_buffered_filter)拷貝到這裡的。
//u->busy_bufs代表是在讀取fastcgi請求頭的時候,可能裡面會帶有包體資料,就是通過這裡發送
rc = ngx_http_output_filter(r, u->out_bufs);
if (rc == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
//就是把ngx_http_output_filter調用後未發送完畢的資料buf添加到busy_bufs中,如果下次再次調用ngx_http_output_filter後把busy_bufs中上一次沒有發送完的發送出去了,則把對應的buf移除添加到free中
//下面将out_bufs的元素移動到busy_bufs的後面;将已經發送完畢的busy_bufs連結清單元素移動到free_bufs裡面
ngx_chain_update_chains(r->pool, &u->free_bufs, &u->busy_bufs,
&u->out_bufs, u->output.tag);
}
if (u->busy_bufs == NULL) {//busy_bufs沒有了,都發完了。想要發送的資料都已經發送完畢
if (u->length == 0
|| (upstream->read->eof && u->length == -1)) //包體資料已經讀完了
{
ngx_http_upstream_finalize_request(r, u, 0);
return;
}
if (upstream->read->eof) {
ngx_log_error(NGX_LOG_ERR, upstream->log, 0,
"upstream prematurely closed connection");
ngx_http_upstream_finalize_request(r, u,
NGX_HTTP_BAD_GATEWAY);
return;
}
if (upstream->read->error) {
ngx_http_upstream_finalize_request(r, u,
NGX_HTTP_BAD_GATEWAY);
return;
}
b->pos = b->start;//重置u->buffer,以便與下次使用,從開始起。b指向的空間可以繼續讀資料了
b->last = b->start;
}
}
size = b->end - b->last;//得到目前buf的剩餘空間
if (size && upstream->read->ready) {
//為什麼可能走到這裡?因為在 ngx_http_upstream_process_header 中讀取後端資料的時候,buf大小預設為頁面大小ngx_pagesize
//單有可能後端發送過來的資料比ngx_pagesize大,是以就沒有讀完,也就是recv中不會吧ready置0,是以這裡可以繼續讀
ngx_http_upstream_process_header
n = upstream->recv(upstream, b->last, size);
if (n == NGX_AGAIN) { //說明已經核心緩沖區資料已經讀完,退出循環,然後根據epoll事件來繼續觸發讀取後端資料
break;
}
if (n > 0) {
u->state->response_length += n;
if (u->input_filter(u->input_filter_ctx, n) == NGX_ERROR) {
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
}
do_write = 1;//因為剛剛無論如何n大于0,是以讀取了資料,那麼下一個循環會将out_bufs的資料發送出去的。
continue;
}
break;
}
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
if (downstream->data == r) {
if (ngx_handle_write_event(downstream->write, clcf->send_lowat, NGX_FUNC_LINE)
!= NGX_OK)
{
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
}
if (downstream->write->active && !downstream->write->ready) {
//例如我把資料把資料寫到核心協定棧到寫滿協定棧緩存,但是對端一直不讀取的時候,資料一直發不出去了,也不會觸發epoll_wait寫事件,
//這裡加個定時器就是為了避免這種情況發生
ngx_add_timer(downstream->write, clcf->send_timeout, NGX_FUNC_LINE);
} else if (downstream->write->timer_set) {
ngx_del_timer(downstream->write, NGX_FUNC_LINE);
}
if (ngx_handle_read_event(upstream->read, 0, NGX_FUNC_LINE) != NGX_OK) { //epoll在accept的時候讀寫已經加入epoll中,是以對epoll來說沒用
ngx_http_upstream_finalize_request(r, u, NGX_ERROR);
return;
}
if (upstream->read->active && !upstream->read->ready) {
ngx_add_timer(upstream->read, u->conf->read_timeout, NGX_FUNC_LINE);
} else if (upstream->read->timer_set) {
ngx_del_timer(upstream->read, NGX_FUNC_LINE);
}
}
http代理伺服器(3-4-7層代理)-網絡事件庫公共元件、核心kernel驅動 攝像頭驅動 tcpip網絡協定棧、netfilter、bridge 好像看過!!!!
但行好事 莫問前程
--身高體重180的胖子