菜鳥學習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相關內容。