運營研發 李樂php
FastCGI 是一種協議,規定了FastCGI應用和支持FastCGI的Web服務器之間的接口。FastCGI是二進制連續傳遞的。html
FastCGI定義了多種類型的消息;nginx對FastCGI消息類型定義以下:linux
#define NGX_HTTP_FASTCGI_BEGIN_REQUEST 1 #define NGX_HTTP_FASTCGI_ABORT_REQUEST 2 #define NGX_HTTP_FASTCGI_END_REQUEST 3 #define NGX_HTTP_FASTCGI_PARAMS 4 #define NGX_HTTP_FASTCGI_STDIN 5 #define NGX_HTTP_FASTCGI_STDOUT 6 #define NGX_HTTP_FASTCGI_STDERR 7 #define NGX_HTTP_FASTCGI_DATA 8
通常狀況下,最早發送的是BEGIN_REQUEST類型的消息,而後是PARAMS和STDIN類型的消息;nginx
當FastCGI響應處理完後,將發送STDOUT和STDERR類型的消息,最後以END_REQUEST表示請求的結束。json
FastCGI定義了一個統一結構的8個字節消息頭,用來標識每一個消息的消息體,以及實現消息數據的分割。結構體定義以下:數組
typedef struct { u_char version; //FastCGI協議版本 u_char type; //消息類型 u_char request_id_hi; //請求ID u_char request_id_lo; u_char content_length_hi; //內容 u_char content_length_lo; u_char padding_length; //內容填充長度 u_char reserved; //保留 } ngx_http_fastcgi_header_t;
咱們看到請求ID與內容長度分別用兩個u_char存儲,實際結果的計算方法以下:服務器
requestId = (request_id_hi << 8) + request_id_lo; contentLength = (content_length_hi << 8) + content_length_lo;
消息體的長度始終是8字節的整數倍,當實際內容長度不足時,須要填充若干字節;填充代碼以下所示:app
padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding;
BEGIN_REQUEST類型的消息標識FastCGI請求的開始,結構固定,定義以下:ssh
typedef struct { u_char role_hi; //標記FastCGI應用應該扮演的角色 u_char role_lo; u_char flags; u_char reserved[5]; } ngx_http_fastcgi_begin_request_t;
角色一樣使用兩個u_char存儲,計算方法爲:curl
role = (role_hi << 8) + role_lo;
最經常使用的是響應器(Responder)角色,FastCGI應用接收全部與HTTP請求相關的信息,併產生一個HTTP響應。
nginx配置文件中,fastcgi_param指令配置的若干參數,以及HTTP請求的消息頭,都是經過FCGI_PARAMS類型的消息傳遞的,此消息就是若干個名—值對(此名—值對在php中能夠經過$_SERVER[ ]獲取);
傳輸格式爲nameLength+valueLength+name+value。
爲了節省空間,對於0~127長度的值,Length使用了一個char來表示,第一位爲0,對於大於127的長度的值,Length使用了4個char來表示,第一位爲1;以下圖所示:
Length字段編碼的邏輯以下:
if (val_len > 127) { *b->last++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80); *b->last++ = (u_char) ((val_len >> 16) & 0xff); *b->last++ = (u_char) ((val_len >> 8) & 0xff); *b->last++ = (u_char) (val_len & 0xff); } else { *b->last++ = (u_char) val_len; }
代碼中搜索ngx_http_fastcgi_commands,查看fastcgi模塊提供的配置指令;
static ngx_command_t ngx_http_fastcgi_commands[] = { { ngx_string("fastcgi_pass"), NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF|NGX_CONF_TAKE1, //只能出如今location塊中 ngx_http_fastcgi_pass, NGX_HTTP_LOC_CONF_OFFSET, 0, NULL }, { ngx_string("fastcgi_param"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE23, //能夠出如今http配置塊、server配置塊、location配置塊中 ngx_http_upstream_param_set_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_fastcgi_loc_conf_t, params_source), //ngx_http_fastcgi_loc_conf_t結構的params_source字段是存儲配置參數的array, NULL }, ………… }
fastcgi_pass指令用於配置上游FastCGI應用的ip:port,ngx_http_fastcgi_pass方法解析此指令(設置handler爲ngx_http_fastcgi_handler方法,命中當前location規則的HTTP請求,請求處理的內容產生階段會調用此handler);
fastcgi_param用於配置nginx向FastCGI應用傳遞的參數,在php中,咱們能夠經過$_SERVER[" "]獲取這些參數;
解析fastcgi_param配置的代碼實現以下:
char * ngx_http_upstream_param_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { a = (ngx_array_t **) (p + cmd->offset); //ngx_http_fastcgi_loc_conf_t結構首地址加params_source字段的偏移 param = ngx_array_push(*a); value = cf->args->elts; param->key = value[1]; param->value = value[2]; param->skip_empty = 0; if (cf->args->nelts == 4) { //if_not_empty用於配置參數是否必傳(若是配置,當值爲空時不會傳向FastCGI應用傳遞此參數) if (ngx_strcmp(value[3].data, "if_not_empty") != 0) { return NGX_CONF_ERROR; } param->skip_empty = 1; } return NGX_CONF_OK; }
fastcgi_param配置的全部參數會會存儲在ngx_http_fastcgi_loc_conf_t結構體的params_source字段;
nginx爲了方便生成fastcgi請求數據,會提早對params_source作一些預處理,預先初始化號每一個名—值對的長度以及數據拷貝方法等;
2.1節查看fastcgi模塊提供的配置指令時發現,某些配置指令出如今location配置塊,有些配置卻能夠出現http配置塊、server配置塊和location配置塊;便可能出現同一個指令同時出如今好幾個配置塊中,此時如何解析配置?
對於這些配置指令,nginx最終會執行一個merge操做,合併多個配置爲一個;觀察nginx的HTTP模塊,大多模塊都會存在一個merge_loc_conf字段(函數指針),用於merge配置;
fastcgi模塊的merge操做由ngx_http_fastcgi_merge_loc_conf完成,其同時對params_source進行了一些預處理;代碼以下:
static char * ngx_http_fastcgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) { ngx_conf_merge_msec_value(conf->upstream.connect_timeout, prev->upstream.connect_timeout, 60000); ngx_conf_merge_value(conf->upstream.pass_request_headers, prev->upstream.pass_request_headers, 1); //配置HTTP頭部是否傳遞給FastCGI應用,默認爲1 ngx_conf_merge_value(conf->upstream.pass_request_body, prev->upstream.pass_request_body, 1); //配置HTTP body是否傳遞給FastCGI應用,默認爲1 ………… if (ngx_http_fastcgi_merge_params(cf, conf, prev) != NGX_OK) { //重點,merger並預處理傳遞給FastCGI應用的參數 return NGX_CONF_ERROR; } }
ngx_http_fastcgi_merge_params方法主要params_source作了一些預處理,主要處理邏輯以下:
注意:配置參數的名稱以HTTP_開始時,此參數可能仍是HTTP請求頭,須要記錄這些參數,以便傳遞HTTP請求頭時排除掉。
static ngx_int_t ngx_http_fastcgi_merge_params(ngx_conf_t *cf, ngx_http_fastcgi_loc_conf_t *conf, ngx_http_fastcgi_loc_conf_t *prev) { if (conf->params_source) { src = conf->params_source->elts; nsrc = conf->params_source->nelts; } conf->params_len = ngx_array_create(cf->pool, 64, 1); //params_len用於計算參數名—值的長度 conf->params = ngx_array_create(cf->pool, 512, 1); //params用於名—值對數據內容的處理(拷貝) if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK){ //存儲以HTTP_開始的配置參數,hash表 return NGX_ERROR; } for (i = 0; i < nsrc; i++) { //以HTTP_開始,存儲在headers_names hash表 if (src[i].key.len > sizeof("HTTP_") - 1 && ngx_strncmp(src[i].key.data, "HTTP_", sizeof("HTTP_") - 1) == 0){ hk = ngx_array_push(&headers_names); hk->key.len = src[i].key.len - 5; hk->key.data = src[i].key.data + 5; hk->key_hash = ngx_hash_key_lc(hk->key.data, hk->key.len); hk->value = (void *) 1; } //ngx_http_script_copy_code_t結構體包含兩個字段:code函數指針,用於計算參數名稱的長度(方法內部直接返回了了len字段);len是參數名稱的長度 copy = ngx_array_push_n(conf->params_len, sizeof(ngx_http_script_copy_code_t)); copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = src[i].key.len; //這裏的len表示參數是否必傳;對於非必傳參數,當此參數的值爲空時,能夠不傳遞此參數;(ngx_http_script_copy_len_code方法內部直接返回了了len字段,即skip_empty) copy = ngx_array_push_n(conf->params_len, sizeof(ngx_http_script_copy_code_t)); copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = src[i].skip_empty; //ngx_http_script_copy_code_t結構體包含兩個字段:code函數指針,實現參數名稱內容的拷貝;len數參數名稱的長度 //空間大小爲ngx_http_script_copy_code_t結構體長度,加參數名稱的長度;最後再8字節對齊 size = (sizeof(ngx_http_script_copy_code_t) + src[i].key.len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_array_push_n(conf->params, size); copy->code = ngx_http_script_copy_code; copy->len = src[i].key.len; //拷貝數據 p = (u_char *) copy + sizeof(ngx_http_script_copy_code_t); ngx_memcpy(p, src[i].key.data, src[i].key.len); //params_len與params分別存儲NULL,以實現存儲空間的分隔;及參數與參數之間使用NULL進行隔離; code = ngx_array_push_n(conf->params_len, sizeof(uintptr_t)); *code = (uintptr_t) NULL; code = ngx_array_push_n(conf->params, sizeof(uintptr_t)); *code = (uintptr_t) NULL; } conf->header_params = headers_names.nelts; //以HTTP_開始的參數存儲在conf的header_params與headers_hash字段 hash.hash = &conf->headers_hash; …… return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); }
根據上面的代碼邏輯,很容易畫出params_len與params的內部存儲結構:
問題:參數是名—值對,這裏的代碼只對參數名稱進行了預處理,參數的值呢?參數的值應該與請求相對應的,在解析配置文件時,並無請求對應的信息,如何預處理參數的值呢?
通常fastcgi的參數是如下這些配置:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; …………
參數的值其實就是nginx提供的一系列能夠直接使用變量(在ngx_http_variable.c文件中查找ngx_http_core_variables數組,即nginx提供的變量),每一個變量都有一個索引值;
預處理fastcgi的配置參數時,其實只須要初始化參數值對應的變量索引便可;(注意參數的值多是由多個nginx變量組合而成)
注意到ngx_http_fastcgi_merge_params方法中還有如下一段代碼:
for (i = 0; i < nsrc; i++) { sc.cf = cf; sc.source = &src[i].value; sc.flushes = &conf->flushes; sc.lengths = &conf->params_len; sc.values = &conf->params; if (ngx_http_script_compile(&sc) != NGX_OK) { return NGX_ERROR; } }
咱們看到sc的這些字段values(params)、lengths(params_len)、source(src[i].value,即參數的值);ngx_http_script_compile能夠對params和params_len字段進行修改;其實現以下:
ngx_int_t ngx_http_script_compile(ngx_http_script_compile_t *sc) { for (i = 0; i < sc->source->len; /* void */ ) { //針對$document_root$fastcgi_script_name這種配置,會執行兩次 if (sc->source->data[i] == '$') { if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) { //name是變量名稱 return NGX_ERROR; } } } } //同一個參數,值可能由多個變量組合而成,同一個參數可能會調用此方法屢次 static ngx_int_t ngx_http_script_add_var_code(ngx_http_script_compile_t *sc, ngx_str_t *name) { index = ngx_http_get_variable_index(sc->cf, name); //獲取變量的索引 //ngx_http_script_var_code_t結構體包含兩個字段:code函數指針,計算爲變量長度(方法內部查找索引爲index的變量,返回其長度);index爲變量索引 code = ngx_http_script_add_code(*sc->lengths, sizeof(ngx_http_script_var_code_t), NULL); //存儲到lengths,即params_len code->code = (ngx_http_script_code_pt) ngx_http_script_copy_var_len_code; code->index = (uintptr_t) index; //ngx_http_script_var_code_t結構體包含兩個字段:code函數指針,拷貝變量內容(方法內部查找索引爲index的變量,拷貝變量內容);index爲變量索引 code = ngx_http_script_add_code(*sc->values, sizeof(ngx_http_script_var_code_t), &sc->main); //存儲到values,即params code->code = ngx_http_script_copy_var_code; code->index = (uintptr_t) index; return NGX_OK; }
最終params_len與params的內部存儲結構入下圖:
方法ngx_http_fastcgi_create_request建立FastCGI請求,初始化請求內容(包括BEGIN_REQUEST、PARAMS和STDIN類型的請求消息);
FastCGI應用即爲nginx的upstream,輸出緩衝區的類型爲ngx_chain_t,是由多個buf組成的鏈表
struct ngx_chain_s { ngx_buf_t *buf; ngx_chain_t *next; };
nginx將FastCGI請求分爲三個部分,由三個buf鏈成一個ngx_chain_s;nginx構造的FastCGI請求結構以下圖所示;
其中第一部分主要包括fastcgi_param配置的參數以及HTTP請求的header,其餘內容固定不變;第二部分是HTTP請求的body,其buf在解析HTTP請求時已經初始化好了,此處只須要將此buf添加到ngx_chain_s鏈中便可;第三部份內容固定;
爲第一部分分配buf時,首先須要計算buf所需空間的大小;第一部分空間分爲fastcgi_param參數與HTTP請求header;計算方法見下文:
if (flcf->params_len) { ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); ngx_http_script_flush_no_cacheable_variables(r, flcf->flushes); le.flushed = 1; le.ip = flcf->params_len->elts; //le.ip即爲params_len存儲的元素 le.request = r; while (*(uintptr_t *) le.ip) { //循環計算索引參數key與value長度之和 lcode = *(ngx_http_script_len_code_pt *) le.ip; //key長度,lcode指向方法ngx_http_script_copy_len_code key_len = lcode(&le); lcode = *(ngx_http_script_len_code_pt *) le.ip; //是否必傳,lcode指向方法ngx_http_script_copy_len_code skip_empty = lcode(&le); for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { //value長度,lcode指向方法ngx_http_script_copy_var_len_code(注意value可能又多個值組合而成) lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); //跳參數之間分割的NULL if (skip_empty && val_len == 0) { //非必傳參數,值爲空時可跳過 continue; } len += 1 + key_len + ((val_len > 127) ? 4 : 1) + val_len; } }
if (flcf->upstream.pass_request_headers) { //是否須要向FastCGI應用傳遞header part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { //header_params記錄fastcgi_param是否配置了以HTTP_開始的參數,headers_hash存儲此種類型的配置參數 if (flcf->header_params) { for (n = 0; n < header[i].key.len; n++) { ch = header[i].key.data[n]; if (ch >= 'A' && ch <= 'Z') { ch |= 0x20; } else if (ch == '-') { ch = '_'; } hash = ngx_hash(hash, ch); lowcase_key[n] = ch; } if (ngx_hash_find(&flcf->headers_hash, hash, lowcase_key, n)) { //查詢此HTTP請求頭是否已經由fastcgi_param指令配置;有則忽略此HTTP請求頭 ignored[header_params++] = &header[i]; continue; } n += sizeof("HTTP_") - 1; //請求頭添加HTTP_前綴(n已經累加到header[i].key.len了) } else { n = sizeof("HTTP_") - 1 + header[i].key.len; //請求頭添加HTTP_前綴 } len += ((n > 127) ? 4 : 1) + ((header[i].value.len > 127) ? 4 : 1) + n + header[i].value.len; } }
if (len > 65535) { return NGX_ERROR; } padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding; size = sizeof(ngx_http_fastcgi_header_t) + sizeof(ngx_http_fastcgi_begin_request_t) + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */ + len + padding + sizeof(ngx_http_fastcgi_header_t) /* NGX_HTTP_FASTCGI_PARAMS */ + sizeof(ngx_http_fastcgi_header_t); /* NGX_HTTP_FASTCGI_STDIN */ b = ngx_create_temp_buf(r->pool, size); cl = ngx_alloc_chain_link(r->pool); cl->buf = b;
nginx的緩衝區buf主要關注如下四個字段:
struct ngx_buf_s { u_char *pos; //當buf所指向的數據在內存裏的時候,pos指向的是這段數據開始的位置 u_char *last; //當buf所指向的數據在內存裏的時候,last指向的是這段數據結束的位置 off_t file_pos; //當buf所指向的數據是在文件裏的時候,file_pos指向的是這段數據的開始位置在文件中的偏移量 off_t file_last;//當buf所指向的數據是在文件裏的時候,file_last指向的是這段數據的結束位置在文件中的偏移量
if (flcf->params_len) { e.ip = flcf->params->elts; //e.ip是params e.pos = b->last; le.ip = flcf->params_len->elts; ////le.ip是params_len while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; //key的長度 key_len = (u_char) lcode(&le); lcode = *(ngx_http_script_len_code_pt *) le.ip; //是否必傳 skip_empty = lcode(&le); for (val_len = 0; *(uintptr_t *) le.ip; val_len += lcode(&le)) { //value的長度 lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); if (skip_empty && val_len == 0) { //跳過 ………… } *e.pos++ = (u_char) key_len; //填充key_len //填充value_len if (val_len > 127) { *e.pos++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80); *e.pos++ = (u_char) ((val_len >> 16) & 0xff); *e.pos++ = (u_char) ((val_len >> 8) & 0xff); *e.pos++ = (u_char) (val_len & 0xff); } else { *e.pos++ = (u_char) val_len; } //填充key和value的數據內容;key的填充方法爲ngx_http_script_copy_code,value的填充方法ngx_http_script_copy_var_code, while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } e.ip += sizeof(uintptr_t); //跳過參數之間分割的NULL } b->last = e.pos; }
if (flcf->upstream.pass_request_headers) { part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { for (n = 0; n < header_params; n++) { //上一步計算長度時,會記錄跳過的header在ignored;填充階段直接跳過 if (&header[i] == ignored[n]) { goto next; } } key_len = sizeof("HTTP_") - 1 + header[i].key.len; //填充key長度 if (key_len > 127) { *b->last++ = (u_char) (((key_len >> 24) & 0x7f) | 0x80); *b->last++ = (u_char) ((key_len >> 16) & 0xff); *b->last++ = (u_char) ((key_len >> 8) & 0xff); *b->last++ = (u_char) (key_len & 0xff); } else { *b->last++ = (u_char) key_len; } val_len = header[i].value.len; //填充value長度 if (val_len > 127) { *b->last++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80); *b->last++ = (u_char) ((val_len >> 16) & 0xff); *b->last++ = (u_char) ((val_len >> 8) & 0xff); *b->last++ = (u_char) (val_len & 0xff); } else { *b->last++ = (u_char) val_len; } b->last = ngx_cpymem(b->last, "HTTP_", sizeof("HTTP_") - 1); //填充HTTP_前綴 for (n = 0; n < header[i].key.len; n++) { //填充key數據內容 ch = header[i].key.data[n]; if (ch >= 'a' && ch <= 'z') { ch &= ~0x20; } else if (ch == '-') { ch = '_'; } *b->last++ = ch; } b->last = ngx_copy(b->last, header[i].value.data, val_len); //填充value數據內容 next: continue; } }
HTTP請求的body一樣存儲在ngx_chain_t結構中,nginx須要遍歷鏈表的全部buf,構造fastcgi的請求數據;
注意:nginx構造fastcgi請求時,第二部分請求(http_body)的長度最長爲32K,當超過此限制時,HTTP請求體會被分割爲多個http_body請求;入下圖所示:
do { b = ngx_alloc_buf(r->pool); b->pos = pos; pos += 32 * 1024; if (pos >= body->buf->last) { //數據小於32k,next賦值爲1,結束while循環;不然就切割爲了32K大小的數據包 pos = body->buf->last; next = 1; } b->last = pos; len = (ngx_uint_t) (pos - b->pos); padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding; cl->next = ngx_alloc_chain_link(r->pool); cl = cl->next; //添加http_body請求包到buf鏈表中 cl->buf = b; ………… b = ngx_create_temp_buf(r->pool, sizeof(ngx_http_fastcgi_header_t) + padding); cl->next = ngx_alloc_chain_link(r->pool); cl = cl->next; //添加padding與header請求包到buf鏈表中 cl->buf = b; } while (!next);
nginx配置以下:
http{ ………… fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; server { listen 80; server_name localhost; root /home/xiaoju; index index.php index.html; location / { fastcgi_index index.php; fastcgi_pass 127.0.0.1:9000; include fastcgi.conf; } } } fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param QUERY_STRING $query_string; fastcgi_param REQUEST_METHOD $request_method; fastcgi_param CONTENT_TYPE $content_type; fastcgi_param CONTENT_LENGTH $content_length; ………………
編寫PHP腳本,只是簡單的將post入參返回便可:
<?php foreach($_POST as $key=>$v){ $ret['ret-'.$key] = 'ret-'.$v; } echo json_encode($ret);
咱們GDB nginx worker進程;
注意:爲了方便調試,nginx配置文件中,worker_processes配置爲1,即只能存在一個work進程。
查看FastCGI請求參數,在ngx_http_fastcgi_create_request方法添加斷點,執行到函數最後一行(此時請求數據已經構造完成),輸出數據存儲在表達式r->upstream->request_bufs表示的緩衝區;
查看FastCGI應用(php-fpm)返回的數據,在ngx_http_fastcgi_process_record方法添加斷點,方法入參ngx_http_fastcgi_ctx_t的pos和last分別指向讀入數據的開始與結尾,此方法杜澤解析讀入數據;
添加斷點以下:
Num Type Disp Enb Address What 1 breakpoint keep y 0x0000000000418f05 in ngx_process_events_and_timers at src/event/ngx_event.c:203 inf 3, 2, 1 breakpoint already hit 17 times 2 breakpoint keep y 0x000000000045b7fa in ngx_http_fastcgi_create_request at src/http/modules/ngx_http_fastcgi_module.c:735 inf 3, 2, 1 breakpoint already hit 4 times 3 breakpoint keep y 0x000000000045c2af in ngx_http_fastcgi_create_request at src/http/modules/ngx_http_fastcgi_module.c:1190 inf 3, 2, 1 breakpoint already hit 4 times 4 breakpoint keep y 0x000000000045a573 in ngx_http_fastcgi_process_record at src/http/modules/ngx_http_fastcgi_module.c:2145 inf 3, 2, 1 breakpoint already hit 1 time
執行到ngx_http_fastcgi_create_request函數結尾(斷點3),打印r->upstream->request_bufs三個buf:
注意:gdb使用命令p打印字符串時,需設置set print element 0纔不會省略部分字符串,不然字符串不會打印徹底;@符號表示打印多少個字符(fastcgi請求時二進制數據,不能依據0判斷結尾);
字符串顯示時,顯示‘222’時,爲8進製表示,需轉換爲10進制計算才行;
(gdb) p *r->upstream->request_bufs->buf->pos@1000 $18 = \001\001\000\001\000\b\000\000 //8字節頭部,type=1(BEGIN_REQUEST) \000\001\000\000\000\000\000\000 //8字節BEGIN_REQUEST數據包 \001\004\000\001\002\222\006\000 //8字節頭部,type=4(PARAMS),數據內容長度=2*256+146=658(不是8字節整數倍,須要填充6個字節) \017\025SCRIPT_FILENAME/home/xiaoju/test.php //key-value,格式爲:keylen+valuelen+key+value \f\000QUERY_STRING\016\004REQUEST_METHODPOST \f!CONTENT_TYPEapplication/x-www-form-urlencoded \016\002CONTENT_LENGTH19 \v\tSCRIPT_NAME/test.php \v\nREQUEST_URI//test.php \f\tDOCUMENT_URI/test.php \r\fDOCUMENT_ROOT/home/xiaoju \017\bSERVER_PROTOCOLHTTP/1.1 \021\aGATEWAY_INTERFACECGI/1.1 \017\vSERVER_SOFTWAREnginx/1.6.2 \v\tREMOTE_ADDR127.0.0.1 \v\005REMOTE_PORT54276 \v\tSERVER_ADDR127.0.0.1 \v\002SERVER_PORT80 \v\tSERVER_NAMElocalhost \017\003REDIRECT_STATUS200 \017dHTTP_USER_AGENTcurl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2 \t\tHTTP_HOSTlocalhost \v\003HTTP_ACCEPT*/* \023\002HTTP_CONTENT_LENGTH19 \021!HTTP_CONTENT_TYPEapplication/x-www-form-urlencoded \000\000\000\000\000\000 //6字節內容填充 \001\004\000\001\000\000\000\000 //8字節頭部,type=4(PARAMS),表示PARAMS請求結束 \001\005\000\001\000\023\005\000 //8字節頭部,type=5(STDIN),請求體數據長度19個字節 (gdb) p *r->upstream->request_bufs->next->buf->pos@20 $19 = "name=hello&gender=1" //HTTP請求體,長度19字節,需填充5個字節 (gdb) p *r->upstream->request_bufs->next->next->buf->pos@20 $20 = \000\000\000\000\000 //5字節填充 \001\005\000\001\000\000\000 //8字節頭部,type=5(STDIN),表示STDIN請求結束
執行到方法ngx_http_fastcgi_process_record,打印讀入請求數據:
p *f->pos@1000 $26 = \001\006\000\001\000\377\001\000 //8字節頭部,type=6(STDOUT),返回數據長度爲255字節(須要填充1個字節) Set-Cookie: PHPSESSID=3h9lmb2mvp6qlk1rg11id3akd3; path=/\r\n //返回數據內容,以換行符分隔 Expires: Thu, 19 Nov 1981 08:52:00 GMT\r\n Cache-Control: no-store, no-cache, must-revalidate\r\n Pragma: no-cache\r\n Content-type: text/html; charset=UTF-8\r\n \r\n {\"ret-name\":\"ret-hello\",\"ret-gender\":\"ret-1\"} \000 \001\003\000\001\000\b\000\000 //8字節頭部,type=3(END_REQUEST),表示fastcgi請求結束,數據長度爲8 \000\000\000\000\000\000\000\000 //8字節END_REQUEST數據
返回數據包見下圖:
END_REQUEST body數據體8個字節,其定義能夠在php源碼中查看:
typedef struct _fcgi_end_request { unsigned char appStatusB3; ////結束狀態,0爲正常 unsigned char appStatusB2; unsigned char appStatusB1; unsigned char appStatusB0; unsigned char protocolStatus; //爲協議所處的狀態,0爲正常狀態 unsigned char reserved[3]; } fcgi_end_request;
本文經過分析ngx_http_fastcgi_module模塊構造FastCGI請求的代碼,學習FastCGI協議格式,並經過GDB打印FastCGI請求與相應數據,以此對FastCGI協議有了更直觀的理解。