1. 程式人生 > >菜鳥學習nginx之HTTP body接收(3)

菜鳥學習nginx之HTTP body接收(3)

上一篇介紹了,Nginx是如何接收body主體流程,但是我們仍然沒有了解到,Nginx是如何儲存body的?也就是第一篇中提到的問題。本篇主要分析ngx_http_request_body_filter函式,該函式會幫助我們解決。

一、ngx_http_request_body_t結構體

typedef struct {
    ngx_temp_file_t                  *temp_file; /* 當指標不空說明以檔案方式儲存body */
    /**
     * 儲存body的連結串列 完整body在這裡面 因此我們在編寫業務邏輯需要特別注意 
     * 這裡還需要注意一點 bufs中ngx_buf_t結構既支援記憶體結構又支援檔案結構
     * 當我們處理body時 取出buf後需要判斷in_file變數是否為1
     */
    ngx_chain_t                      *bufs;
    /**
     * 用於接收socket資料 即接收body 在ngx_http_read_client_request_body中賦值
     * buf是用於socket recv函式 所以當body很大的時候 這個buf可能不能滿足body長度
     * 因此會buf指向的記憶體拷貝到bufs中
     */
    ngx_buf_t                        *buf;
    off_t                             rest; /* 該值代表還有多少位元組的body未讀取 */
    off_t                             received; /* 用於http V2版本 */
    ngx_chain_t                      *free;
    ngx_chain_t                      *busy;
    ngx_http_chunked_t               *chunked; /* chunked資訊 */
    ngx_http_client_body_handler_pt   post_handler; /* 使用者設定的回撥函式 用於處理body */
} ngx_http_request_body_t;

上一篇已經介紹了該結構體,這裡純粹是為了方便下面閱讀。

二、ngx_http_request_body_filter

/**
 * 從in中過濾出body資料 放到request_body中chain中
 * @param r http請求
 * @param in 緩衝區
 */
static ngx_int_t
ngx_http_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    if (r->headers_in.chunked) {
        return ngx_http_request_body_chunked_filter(r, in);

    } else {
        return ngx_http_request_body_length_filter(r, in);
    }
}

該函式主要功能是將入參in中body過濾出來,並且儲存到request中。這裡將會區分是否為chunked,我們以非chunked方式來進行分析,因這種場景相對簡單一些,來看一下ngx_http_request_body_length_filter函式具體實現內容:

/**
 * 非chunked模式下的 body處理
 * @param r http請求
 * @param in 緩衝區
 */
static ngx_int_t
ngx_http_request_body_length_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    size_t                     size;
    ngx_int_t                  rc;
    ngx_buf_t                 *b;
    ngx_chain_t               *cl, *tl, *out, **ll;
    ngx_http_request_body_t   *rb;

    rb = r->request_body;
    /**
     * 如果rest是-1表示還沒有處理過body 因此將HTTP header中content_length賦給它
     * rest代表需要處理body長度
     */
    if (rb->rest == -1) {
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http request body content length filter");

        rb->rest = r->headers_in.content_length_n;//body長度
    }

    out = NULL;
    ll = &out;
    /* 迴圈遍歷 連結串列 */
    for (cl = in; cl; cl = cl->next) {

        if (rb->rest == 0) {//表示body處理完畢
            break;
        }
        /* 獲取新的chain鏈物件 */
        tl = ngx_chain_get_free_buf(r->pool, &rb->free);
        if (tl == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        b = tl->buf;
        /* 初始化操作 */
        ngx_memzero(b, sizeof(ngx_buf_t));        
        b->temporary = 1; //臨時記憶體
        b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body;
        b->start = cl->buf->pos;
        b->pos = cl->buf->pos;
        b->last = cl->buf->last;
        b->end = cl->buf->end;
        b->flush = r->request_body_no_buffering;
        /* 待處理的報文長度 當前buf儲存實際body長度 */
        size = cl->buf->last - cl->buf->pos;

        if ((off_t) size < rb->rest) {//表示當前buffer中沒有完全包含body
            cl->buf->pos = cl->buf->last;
            rb->rest -= size;

        } else {//表示body 都已經在buffer中
            cl->buf->pos += (size_t) rb->rest;
            rb->rest = 0;
            b->last = cl->buf->pos;
            b->last_buf = 1;
        }
        /* 插入連結串列 */
        *ll = tl;
        ll = &tl->next;
    }
    /* ngx_http_top_request_body_filter回撥函式 ngx_http_request_body_save_filter */
    rc = ngx_http_top_request_body_filter(r, out);
 
    ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out,
                            (ngx_buf_tag_t) &ngx_http_read_client_request_body);

    return rc;
}

 說明:

1、上面詳細介紹了rest代表含義,一次socket讀取事件並不能把所有body都讀取出來,因此需要設定統計變數。

2、上面for結束,並不能說明body完全接收完畢,這一點需要明確。

3、不是很清楚為什麼這個地方設定成一個回撥函式,而且ngx_http_top_request_body_filter只有一個地方賦值。Nginx是打算以後擴充套件嗎?不是特別清楚。以目前版本來看這個函式指標指向的函式是ngx_http_request_body_save_filter。該方法用於將out指向的buf儲存到request_body物件中。

4、對於ngx_chain_update_chains函式具體說明,可參考文章《菜鳥學習Nginx之ngx_buf_t》

接下來看一下ngx_http_request_body_save_filter函式具體實現內容:

/**
 * 將已經讀取到body儲存到request_body物件中chain中
 * @param r http請求
 * @param in 讀取到body緩衝區
 */
ngx_int_t
ngx_http_request_body_save_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_buf_t                 *b;
    ngx_chain_t               *cl;
    ngx_http_request_body_t   *rb;

    rb = r->request_body;

    /* TODO: coalesce neighbouring buffers */
    /**
     * 這個函式雖然是copy但並非真正資料拷貝 而是指標指向
     * 將in連結串列掛載到bufs中 用於儲存已經讀取的body
     */
    if (ngx_chain_add_copy(r->pool, &rb->bufs, in) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (r->request_body_no_buffering) {
        return NGX_OK;
    }

    if (rb->rest > 0) {//還有body沒有接收

        /**
         * buf表示接收body的緩衝區 
         * last == end表示緩衝區已滿 則嘗試將緩衝區內容寫到檔案中         
         */
        if (rb->buf && rb->buf->last == rb->buf->end
            && ngx_http_write_request_body(r) != NGX_OK)
        {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        return NGX_OK;
    }

    /**
     * 執行到這裡 表示 rb->rest == 0 即所有body均已經處理完畢
     * 需要判斷是否開啟了臨時檔案儲存body功能,若開啟了需要把request_body中
     * 儲存的body寫入到臨時檔案中
     */
    if (rb->temp_file || r->request_body_in_file_only) {

        if (ngx_http_write_request_body(r) != NGX_OK) {//寫入臨時檔案
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (rb->temp_file->file.offset != 0) {
            /* 雖然body用臨時檔案儲存 但是仍然需要用request_body中bufs管理起來 */
            cl = ngx_chain_get_free_buf(r->pool, &rb->free);
            if (cl == NULL) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            b = cl->buf;

            ngx_memzero(b, sizeof(ngx_buf_t));

            b->in_file = 1;//表明body採用臨時檔案儲存
            b->file_last = rb->temp_file->file.offset;
            b->file = &rb->temp_file->file;

            rb->bufs = cl;
        }
    }

    return NGX_OK;
}

從上面程式碼可知,Nginx將body寫入到檔案中有兩個條件,滿足其一即可:

1、Nginx預設會把body儲存到記憶體中,如果body太大,超過buf最大容量(預設是1M,可以設定nginx.conf配置項client_max_body_size)就會把body寫入到檔案中。程式碼如下:

        /**
         * buf表示接收body的緩衝區 
         * last == end表示緩衝區已滿 則嘗試將緩衝區內容寫到檔案中         
         */
        if (rb->buf && rb->buf->last == rb->buf->end
            && ngx_http_write_request_body(r) != NGX_OK)
        {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

2、在配置檔案開啟request_body_in_file_only選項,也會把body寫到檔案中。

三、總結

至此,把Nginx接收HTTP請求以及處理就全部介紹完畢。這部分內容是Nginx核心內容,所以花的篇幅比較多。這是我學習Nginx是一個比較好的總結。這部分內容我個人認為需要知道如下兩點:

1、Nginx接收HTTP header是如何設定大小的?Nginx接收HTTP header預設大小是1k,最大是8k(當然可以通過nginx.conf配置檔案設定),如果超過8k則報錯。

2、Nginx接收HTTP body的buffer大小是怎麼管理的?Nginx設定預設大小為1M,當超過1M的body就會寫入到檔案中。

下一篇介紹,傳送HTTP Response相關內容。