摘要:定義了一個(gè)統(tǒng)一結(jié)構(gòu)的個(gè)字節(jié)消息頭,用來(lái)標(biāo)識(shí)每個(gè)消息的消息體,以及實(shí)現(xiàn)消息數(shù)據(jù)的分割。
運(yùn)營(yíng)研發(fā) 李樂(lè)
1.初識(shí)FastCGI協(xié)議FastCGI 是一種協(xié)議,規(guī)定了FastCGI應(yīng)用和支持FastCGI的Web服務(wù)器之間的接口。FastCGI是二進(jìn)制連續(xù)傳遞的。
1.1消息頭FastCGI定義了多種類型的消息;nginx對(duì)FastCGI消息類型定義如下:
#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
一般情況下,最先發(fā)送的是BEGIN_REQUEST類型的消息,然后是PARAMS和STDIN類型的消息;
當(dāng)FastCGI響應(yīng)處理完后,將發(fā)送STDOUT和STDERR類型的消息,最后以END_REQUEST表示請(qǐng)求的結(jié)束。
FastCGI定義了一個(gè)統(tǒng)一結(jié)構(gòu)的8個(gè)字節(jié)消息頭,用來(lái)標(biāo)識(shí)每個(gè)消息的消息體,以及實(shí)現(xiàn)消息數(shù)據(jù)的分割。結(jié)構(gòu)體定義如下:
typedef struct { u_char version; //FastCGI協(xié)議版本 u_char type; //消息類型 u_char request_id_hi; //請(qǐng)求ID u_char request_id_lo; u_char content_length_hi; //內(nèi)容 u_char content_length_lo; u_char padding_length; //內(nèi)容填充長(zhǎng)度 u_char reserved; //保留 } ngx_http_fastcgi_header_t;
我們看到請(qǐng)求ID與內(nèi)容長(zhǎng)度分別用兩個(gè)u_char存儲(chǔ),實(shí)際結(jié)果的計(jì)算方法如下:
requestId = (request_id_hi << 8) + request_id_lo; contentLength = (content_length_hi << 8) + content_length_lo;
消息體的長(zhǎng)度始終是8字節(jié)的整數(shù)倍,當(dāng)實(shí)際內(nèi)容長(zhǎng)度不足時(shí),需要填充若干字節(jié);填充代碼如下所示:
padding = 8 - len % 8; padding = (padding == 8) ? 0 : padding;1.2消息體舉例
BEGIN_REQUEST類型的消息標(biāo)識(shí)FastCGI請(qǐng)求的開始,結(jié)構(gòu)固定,定義如下:
typedef struct { u_char role_hi; //標(biāo)記FastCGI應(yīng)用應(yīng)該扮演的角色 u_char role_lo; u_char flags; u_char reserved[5]; } ngx_http_fastcgi_begin_request_t;
角色同樣使用兩個(gè)u_char存儲(chǔ),計(jì)算方法為:
role = (role_hi << 8) + role_lo;
最常用的是響應(yīng)器(Responder)角色,F(xiàn)astCGI應(yīng)用接收所有與HTTP請(qǐng)求相關(guān)的信息,并產(chǎn)生一個(gè)HTTP響應(yīng)。
nginx配置文件中,fastcgi_param指令配置的若干參數(shù),以及HTTP請(qǐng)求的消息頭,都是通過(guò)FCGI_PARAMS類型的消息傳遞的,此消息就是若干個(gè)名—值對(duì)(此名—值對(duì)在php中可以通過(guò)$_SERVER[ ]獲取);
傳輸格式為nameLength+valueLength+name+value。
為了節(jié)省空間,對(duì)于0~127長(zhǎng)度的值,Length使用了一個(gè)char來(lái)表示,第一位為0,對(duì)于大于127的長(zhǎng)度的值,Length使用了4個(gè)char來(lái)表示,第一位為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; }2.基礎(chǔ)知識(shí) 2.1 FastCGI配置
代碼中搜索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, //只能出現(xiàn)在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, //可以出現(xiàn)在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結(jié)構(gòu)的params_source字段是存儲(chǔ)配置參數(shù)的array, NULL }, ………… }
fastcgi_pass指令用于配置上游FastCGI應(yīng)用的ip:port,ngx_http_fastcgi_pass方法解析此指令(設(shè)置handler為ngx_http_fastcgi_handler方法,命中當(dāng)前l(fā)ocation規(guī)則的HTTP請(qǐng)求,請(qǐng)求處理的內(nèi)容產(chǎn)生階段會(huì)調(diào)用此handler);
fastcgi_param用于配置nginx向FastCGI應(yīng)用傳遞的參數(shù),在php中,我們可以通過(guò)$_SERVER[" "]獲取這些參數(shù);
解析fastcgi_param配置的代碼實(shí)現(xiàn)如下:
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結(jié)構(gòu)首地址加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用于配置參數(shù)是否必傳(如果配置,當(dāng)值為空時(shí)不會(huì)傳向FastCGI應(yīng)用傳遞此參數(shù)) if (ngx_strcmp(value[3].data, "if_not_empty") != 0) { return NGX_CONF_ERROR; } param->skip_empty = 1; } return NGX_CONF_OK; }2.2FastCGI配置預(yù)處理
fastcgi_param配置的所有參數(shù)會(huì)會(huì)存儲(chǔ)在ngx_http_fastcgi_loc_conf_t結(jié)構(gòu)體的params_source字段;
nginx為了方便生成fastcgi請(qǐng)求數(shù)據(jù),會(huì)提前對(duì)params_source做一些預(yù)處理,預(yù)先初始化號(hào)每個(gè)名—值對(duì)的長(zhǎng)度以及數(shù)據(jù)拷貝方法等;
2.1節(jié)查看fastcgi模塊提供的配置指令時(shí)發(fā)現(xiàn),某些配置指令出現(xiàn)在location配置塊,有些配置卻可以出現(xiàn)http配置塊、server配置塊和location配置塊;即可能出現(xiàn)同一個(gè)指令同時(shí)出現(xiàn)在好幾個(gè)配置塊中,此時(shí)如何解析配置?
對(duì)于這些配置指令,nginx最終會(huì)執(zhí)行一個(gè)merge操作,合并多個(gè)配置為一個(gè);觀察nginx的HTTP模塊,大多模塊都會(huì)存在一個(gè)merge_loc_conf字段(函數(shù)指針),用于merge配置;
fastcgi模塊的merge操作由ngx_http_fastcgi_merge_loc_conf完成,其同時(shí)對(duì)params_source進(jìn)行了一些預(yù)處理;代碼如下:
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應(yīng)用,默認(rèn)為1 ngx_conf_merge_value(conf->upstream.pass_request_body, prev->upstream.pass_request_body, 1); //配置HTTP body是否傳遞給FastCGI應(yīng)用,默認(rèn)為1 ………… if (ngx_http_fastcgi_merge_params(cf, conf, prev) != NGX_OK) { //重點(diǎn),merger并預(yù)處理傳遞給FastCGI應(yīng)用的參數(shù) return NGX_CONF_ERROR; } }
ngx_http_fastcgi_merge_params方法主要params_source做了一些預(yù)處理,主要處理邏輯如下:
注意:配置參數(shù)的名稱以HTTP_開始時(shí),此參數(shù)可能還是HTTP請(qǐng)求頭,需要記錄這些參數(shù),以便傳遞HTTP請(qǐng)求頭時(shí)排除掉。
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用于計(jì)算參數(shù)名—值的長(zhǎng)度 conf->params = ngx_array_create(cf->pool, 512, 1); //params用于名—值對(duì)數(shù)據(jù)內(nèi)容的處理(拷貝) if (ngx_array_init(&headers_names, cf->temp_pool, 4, sizeof(ngx_hash_key_t)) != NGX_OK){ //存儲(chǔ)以HTTP_開始的配置參數(shù),hash表 return NGX_ERROR; } for (i = 0; i < nsrc; i++) { //以HTTP_開始,存儲(chǔ)在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結(jié)構(gòu)體包含兩個(gè)字段:code函數(shù)指針,用于計(jì)算參數(shù)名稱的長(zhǎng)度(方法內(nèi)部直接返回了了len字段);len是參數(shù)名稱的長(zhǎng)度 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表示參數(shù)是否必傳;對(duì)于非必傳參數(shù),當(dāng)此參數(shù)的值為空時(shí),可以不傳遞此參數(shù);(ngx_http_script_copy_len_code方法內(nèi)部直接返回了了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結(jié)構(gòu)體包含兩個(gè)字段:code函數(shù)指針,實(shí)現(xiàn)參數(shù)名稱內(nèi)容的拷貝;len數(shù)參數(shù)名稱的長(zhǎng)度 //空間大小為ngx_http_script_copy_code_t結(jié)構(gòu)體長(zhǎng)度,加參數(shù)名稱的長(zhǎng)度;最后再8字節(jié)對(duì)齊 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; //拷貝數(shù)據(jù) 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分別存儲(chǔ)NULL,以實(shí)現(xiàn)存儲(chǔ)空間的分隔;及參數(shù)與參數(shù)之間使用NULL進(jìn)行隔離; 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_開始的參數(shù)存儲(chǔ)在conf的header_params與headers_hash字段 hash.hash = &conf->headers_hash; …… return ngx_hash_init(&hash, headers_names.elts, headers_names.nelts); }
根據(jù)上面的代碼邏輯,很容易畫出params_len與params的內(nèi)部存儲(chǔ)結(jié)構(gòu):
問(wèn)題:參數(shù)是名—值對(duì),這里的代碼只對(duì)參數(shù)名稱進(jìn)行了預(yù)處理,參數(shù)的值呢?參數(shù)的值應(yīng)該與請(qǐng)求相對(duì)應(yīng)的,在解析配置文件時(shí),并沒(méi)有請(qǐng)求對(duì)應(yīng)的信息,如何預(yù)處理參數(shù)的值呢?
一般fastcgi的參數(shù)是以下這些配置:
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; …………
參數(shù)的值其實(shí)就是nginx提供的一系列可以直接使用變量(在ngx_http_variable.c文件中查找ngx_http_core_variables數(shù)組,即nginx提供的變量),每個(gè)變量都有一個(gè)索引值;
預(yù)處理fastcgi的配置參數(shù)時(shí),其實(shí)只需要初始化參數(shù)值對(duì)應(yīng)的變量索引即可;(注意參數(shù)的值可能是由多個(gè)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,即參數(shù)的值);ngx_http_script_compile可以對(duì)params和params_len字段進(jìn)行修改;其實(shí)現(xiàn)如下:
ngx_int_t ngx_http_script_compile(ngx_http_script_compile_t *sc) { for (i = 0; i < sc->source->len; /* void */ ) { //針對(duì)$document_root$fastcgi_script_name這種配置,會(huì)執(zhí)行兩次 if (sc->source->data[i] == "$") { if (ngx_http_script_add_var_code(sc, &name) != NGX_OK) { //name是變量名稱 return NGX_ERROR; } } } } //同一個(gè)參數(shù),值可能由多個(gè)變量組合而成,同一個(gè)參數(shù)可能會(huì)調(diào)用此方法多次 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結(jié)構(gòu)體包含兩個(gè)字段:code函數(shù)指針,計(jì)算為變量長(zhǎng)度(方法內(nèi)部查找索引為index的變量,返回其長(zhǎng)度);index為變量索引 code = ngx_http_script_add_code(*sc->lengths, sizeof(ngx_http_script_var_code_t), NULL); //存儲(chǔ)到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結(jié)構(gòu)體包含兩個(gè)字段:code函數(shù)指針,拷貝變量?jī)?nèi)容(方法內(nèi)部查找索引為index的變量,拷貝變量?jī)?nèi)容);index為變量索引 code = ngx_http_script_add_code(*sc->values, sizeof(ngx_http_script_var_code_t), &sc->main); //存儲(chǔ)到values,即params code->code = ngx_http_script_copy_var_code; code->index = (uintptr_t) index; return NGX_OK; }
最終params_len與params的內(nèi)部存儲(chǔ)結(jié)構(gòu)入下圖:
3.構(gòu)造FastCGI請(qǐng)求方法ngx_http_fastcgi_create_request創(chuàng)建FastCGI請(qǐng)求,初始化請(qǐng)求內(nèi)容(包括BEGIN_REQUEST、PARAMS和STDIN類型的請(qǐng)求消息);
3.1FastCGI請(qǐng)求結(jié)構(gòu)FastCGI應(yīng)用即為nginx的upstream,輸出緩沖區(qū)的類型為ngx_chain_t,是由多個(gè)buf組成的鏈表
struct ngx_chain_s { ngx_buf_t *buf; ngx_chain_t *next; };
nginx將FastCGI請(qǐng)求分為三個(gè)部分,由三個(gè)buf鏈成一個(gè)ngx_chain_s;nginx構(gòu)造的FastCGI請(qǐng)求結(jié)構(gòu)如下圖所示;
其中第一部分主要包括fastcgi_param配置的參數(shù)以及HTTP請(qǐng)求的header,其他內(nèi)容固定不變;第二部分是HTTP請(qǐng)求的body,其buf在解析HTTP請(qǐng)求時(shí)已經(jīng)初始化好了,此處只需要將此buf添加到ngx_chain_s鏈中即可;第三部分內(nèi)容固定;
3.2 計(jì)算請(qǐng)求第一部分長(zhǎng)度為第一部分分配buf時(shí),首先需要計(jì)算buf所需空間的大小;第一部分空間分為fastcgi_param參數(shù)與HTTP請(qǐng)求header;計(jì)算方法見下文:
1)計(jì)算fastcgi_param參數(shù)所需空間大小:
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存儲(chǔ)的元素 le.request = r; while (*(uintptr_t *) le.ip) { //循環(huán)計(jì)算索引參數(shù)key與value長(zhǎng)度之和 lcode = *(ngx_http_script_len_code_pt *) le.ip; //key長(zhǎng)度,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長(zhǎng)度,lcode指向方法ngx_http_script_copy_var_len_code(注意value可能又多個(gè)值組合而成) lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); //跳參數(shù)之間分割的NULL if (skip_empty && val_len == 0) { //非必傳參數(shù),值為空時(shí)可跳過(guò) continue; } len += 1 + key_len + ((val_len > 127) ? 4 : 1) + val_len; } }
2)HTTP請(qǐng)求header所需空間大小
if (flcf->upstream.pass_request_headers) { //是否需要向FastCGI應(yīng)用傳遞header part = &r->headers_in.headers.part; header = part->elts; for (i = 0; /* void */; i++) { //header_params記錄fastcgi_param是否配置了以HTTP_開始的參數(shù),headers_hash存儲(chǔ)此種類型的配置參數(shù) 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請(qǐng)求頭是否已經(jīng)由fastcgi_param指令配置;有則忽略此HTTP請(qǐng)求頭 ignored[header_params++] = &header[i]; continue; } n += sizeof("HTTP_") - 1; //請(qǐng)求頭添加HTTP_前綴(n已經(jīng)累加到header[i].key.len了) } else { n = sizeof("HTTP_") - 1 + header[i].key.len; //請(qǐng)求頭添加HTTP_前綴 } len += ((n > 127) ? 4 : 1) + ((header[i].value.len > 127) ? 4 : 1) + n + header[i].value.len; } }
3)創(chuàng)建第一部分buf
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;3.3填充請(qǐng)求第一部分
nginx的緩沖區(qū)buf主要關(guān)注以下四個(gè)字段:
struct ngx_buf_s { u_char *pos; //當(dāng)buf所指向的數(shù)據(jù)在內(nèi)存里的時(shí)候,pos指向的是這段數(shù)據(jù)開始的位置 u_char *last; //當(dāng)buf所指向的數(shù)據(jù)在內(nèi)存里的時(shí)候,last指向的是這段數(shù)據(jù)結(jié)束的位置 off_t file_pos; //當(dāng)buf所指向的數(shù)據(jù)是在文件里的時(shí)候,file_pos指向的是這段數(shù)據(jù)的開始位置在文件中的偏移量 off_t file_last;//當(dāng)buf所指向的數(shù)據(jù)是在文件里的時(shí)候,file_last指向的是這段數(shù)據(jù)的結(jié)束位置在文件中的偏移量
1)填充fastcgi_param參數(shù)
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的長(zhǎng)度 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的長(zhǎng)度 lcode = *(ngx_http_script_len_code_pt *) le.ip; } le.ip += sizeof(uintptr_t); if (skip_empty && val_len == 0) { //跳過(guò) ………… } *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的數(shù)據(jù)內(nèi)容;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); //跳過(guò)參數(shù)之間分割的NULL } b->last = e.pos; }
2)填充HTTP請(qǐng)求頭
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++) { //上一步計(jì)算長(zhǎng)度時(shí),會(huì)記錄跳過(guò)的header在ignored;填充階段直接跳過(guò) if (&header[i] == ignored[n]) { goto next; } } key_len = sizeof("HTTP_") - 1 + header[i].key.len; //填充key長(zhǎng)度 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長(zhǎng)度 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數(shù)據(jù)內(nèi)容 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數(shù)據(jù)內(nèi)容 next: continue; } }3.4填充請(qǐng)求第二三部分
HTTP請(qǐng)求的body同樣存儲(chǔ)在ngx_chain_t結(jié)構(gòu)中,nginx需要遍歷鏈表的所有buf,構(gòu)造fastcgi的請(qǐng)求數(shù)據(jù);
注意:nginx構(gòu)造fastcgi請(qǐng)求時(shí),第二部分請(qǐng)求(http_body)的長(zhǎng)度最長(zhǎng)為32K,當(dāng)超過(guò)此限制時(shí),HTTP請(qǐng)求體會(huì)被分割為多個(gè)http_body請(qǐng)求;入下圖所示:
do { b = ngx_alloc_buf(r->pool); b->pos = pos; pos += 32 * 1024; if (pos >= body->buf->last) { //數(shù)據(jù)小于32k,next賦值為1,結(jié)束while循環(huán);否則就切割為了32K大小的數(shù)據(jù)包 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請(qǐng)求包到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請(qǐng)求包到buf鏈表中 cl->buf = b; } while (!next);4.實(shí)戰(zhàn) 4.1配置
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腳本,只是簡(jiǎn)單的將post入?yún)⒎祷丶纯桑?/p>
$v){ $ret["ret-".$key] = "ret-".$v; } echo json_encode($ret);4.2FastCGI請(qǐng)求包
我們GDB nginx worker進(jìn)程;
注意:為了方便調(diào)試,nginx配置文件中,worker_processes配置為1,即只能存在一個(gè)work進(jìn)程。
查看FastCGI請(qǐng)求參數(shù),在ngx_http_fastcgi_create_request方法添加斷點(diǎn),執(zhí)行到函數(shù)最后一行(此時(shí)請(qǐng)求數(shù)據(jù)已經(jīng)構(gòu)造完成),輸出數(shù)據(jù)存儲(chǔ)在表達(dá)式r->upstream->request_bufs表示的緩沖區(qū);
查看FastCGI應(yīng)用(php-fpm)返回的數(shù)據(jù),在ngx_http_fastcgi_process_record方法添加斷點(diǎn),方法入?yún)gx_http_fastcgi_ctx_t的pos和last分別指向讀入數(shù)據(jù)的開始與結(jié)尾,此方法杜澤解析讀入數(shù)據(jù);
添加斷點(diǎn)如下:
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
執(zhí)行到ngx_http_fastcgi_create_request函數(shù)結(jié)尾(斷點(diǎn)3),打印r->upstream->request_bufs三個(gè)buf:
注意:gdb使用命令p打印字符串時(shí),需設(shè)置set print element 0才不會(huì)省略部分字符串,否則字符串不會(huì)打印完全;@符號(hào)表示打印多少個(gè)字符(fastcgi請(qǐng)求時(shí)二進(jìn)制數(shù)據(jù),不能依據(jù)0判斷結(jié)尾);
字符串顯示時(shí),顯示‘222’時(shí),為8進(jìn)制表示,需轉(zhuǎn)換為10進(jìn)制計(jì)算才行;
(gdb) p *r->upstream->request_bufs->buf->pos@1000 $18 =