国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

【Nginx源碼研究】FastCGI模塊詳解總結篇

awkj / 3545人閱讀

摘要:定義了一個統一結構的個字節消息頭,用來標識每個消息的消息體,以及實現消息數據的分割。

運營研發 李樂

1.初識FastCGI協議

FastCGI 是一種協議,規定了FastCGI應用和支持FastCGI的Web服務器之間的接口。FastCGI是二進制連續傳遞的。

1.1消息頭

FastCGI定義了多種類型的消息;nginx對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

一般情況下,最先發送的是BEGIN_REQUEST類型的消息,然后是PARAMS和STDIN類型的消息;

當FastCGI響應處理完后,將發送STDOUT和STDERR類型的消息,最后以END_REQUEST表示請求的結束。

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字節的整數倍,當實際內容長度不足時,需要填充若干字節;填充代碼如下所示:

padding = 8 - len % 8;
padding = (padding == 8) ? 0 : padding;
1.2消息體舉例

BEGIN_REQUEST類型的消息標識FastCGI請求的開始,結構固定,定義如下:

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存儲,計算方法為:

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;
}
2.基礎知識 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, //只能出現在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;
}
2.2FastCGI配置預處理

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的內部存儲結構入下圖:

3.構造FastCGI請求

方法ngx_http_fastcgi_create_request創建FastCGI請求,初始化請求內容(包括BEGIN_REQUEST、PARAMS和STDIN類型的請求消息);

3.1FastCGI請求結構

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鏈中即可;第三部分內容固定;

3.2 計算請求第一部分長度

為第一部分分配buf時,首先需要計算buf所需空間的大小;第一部分空間分為fastcgi_param參數與HTTP請求header;計算方法見下文:

1)計算fastcgi_param參數所需空間大小:

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;
    }
}

2)HTTP請求header所需空間大小

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;
    }
}

3)創建第一部分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填充請求第一部分

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指向的是這段數據的結束位置在文件中的偏移量

1)填充fastcgi_param參數

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;
}

2)填充HTTP請求頭

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;
    }
}
3.4填充請求第二三部分

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);
4.實戰 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腳本,只是簡單的將post入參返回即可:

$v){
    $ret["ret-".$key] = "ret-".$v;
}
echo json_encode($ret);
4.2FastCGI請求包

我們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 =