nginx中對POST數據的讀取是異步進行的,也就是說你不必在content handler中等待數據讀完然后返回。對客戶端的響應是通過:
ngx_http_send_header(r);
ngx_http_output_filter(r,&out);
兩個調用完成,content handler的return並不意味着請求處理的完成。
既然是異步調用,而且caller可以立即返回,那就意味着需要定義一個回調函數:
typedef void (*ngx_http_client_body_handler_pt)(ngx_http_request_t *r)
真正進行數據讀取的是這個函數ngx_http_read_client_request_body,這個函數有兩個參數,一個是request_rec另外一個就是回調函數指針。
在ngx_http_read_client_request_body這個函數中,進行一系列的檢查和空間分配之后,
//當content-length為0時,nginx直接調用回調函數。
if (r->headers_in.content_length_n == 0) {
…
}
接下來,nginx會先處理已經讀進來的一些數據,通過計算r->header_in->last - r->header_in->pos的值來判斷是否有已讀入且未處理的數據。
如果有這樣的數據那么會申請一塊新的buf:
b = ngx_calloc_buf(r->pool);
然后將讀入的數據映射到這個buf中:
b->temporary = 1;
b->start = r->header_in->pos;
b->pos = r->header_in->pos;
b->last = r->header_in->last;
b->end = r->header_in->end;
然后申請一塊buf chain:
rb->bufs = ngx_alloc_chain_link(r->pool);
將剛創建的buf加入buf chain:
rb->bufs->buf = b;
rb->bufs->next = NULL;
在將數據buffer完成后,就要看下是否所有的數據都已經讀進來了:
if ((off_t) preread >= r->headers_in.content_length_n)
如果此時所有的數據都已經讀進來了那么就直接處理掉就好了:
r->header_in->pos += (size_t) r->headers_in.content_length_n;
r->request_length += r->headers_in.content_length_n;
b->last = r->header_in->pos;
if (r->request_body_in_file_only) {
if (ngx_http_write_request_body(r, rb->bufs) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
}
post_handler(r);
如果還有數據沒有讀到:
這句是將pos指針移到目前讀到的數據末尾,保證每次buffer掉數據后,pos始終指向數據末尾的位置。
r->header_in->pos = r->header_in->last;
然后看下還有多少沒有讀到:
rb->rest = r->headers_in.content_length_n - preread;
這里需要做個判斷,如果沒讀到的數據比當前申請到的buf空間都大的話,那么就需要重新申請一塊buffer了:
next = &rb->bufs->next;
否則的話就進入:
ngx_http_do_read_client_request_body
讀取content body
到這里,nginx已經為content_body分配好了空間並讀入了隨着請求過來的部分數據。由於只能在讀到header之后才知道數據的實際大小並且request_rec中業已拷貝了,所以要么在讀到header的時候分配好需要的buf大小,然后執行拷貝,將preread的數據拷貝到這個buf里,要么區別處理,即不分配preread到的數據,因為這部分的數據已經拷貝進了header_in,這種情況下,只需分配一個buf,將其映射到header_in中的數據區,然后對於剩下的尚未讀取到的數據分配新的buf,並將其鏈入buf chain。
顯而易見,后者會節省一次拷貝操作,並且對於特定應用,如果預分配的空間足夠大,那么完全不需要第二次的考慮操作。nginx用的策略就是后者。
上面的流程其實是一個優化處理的結果,即在content body隨着request一起提交上來時,就無需設置event並回調,直接處理掉就好了,和GET方法一樣。
如果在收到請求header時,並非所有的數據都已經提交上來,那么就需要類似上面的處理,
為r->request_body->buf申請空間,注意這里在分配空間時,並非完全根據content_length進行,是有上限的,而且一經分配就進入讀取階段,並不會再次分配。結合上面的內容,可見,對於POST數據,最多有兩個buf。
rb->buf = ngx_create_temp_buf(r->pool, size);
然后申請一個buffer chain,並將buffer chain的第一個buf指向我們剛申請的rb->buf:
cl = ngx_alloc_chain_link(r->pool);
if (cl == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}cl->buf = rb->buf;
cl->next = NULL;
最后通過:
*next = cl;
cl初始化完成后就可以將其值賦值給r->request_body->bufs。
並最終同樣進入:
ngx_http_do_read_client_request_body
這個函數進行實際的數據讀取工作,邏輯很簡單:調用recv,一直讀到返回again或error,或已經讀完了所有數據,error的時候直接返回,again表明還有數據沒讀則設置一個超時器,加入讀事件進入事件隊列,然后等待下次進程調度執行。
如果已經讀完了,那么先刪掉上次的定時器。如果
rb->temp_file || r->request_body_in_file_only
兩個值被設置了,那么就將request_body保存進一個臨時文件中。
最后,帶着已經讀取完畢的post數據,調用我們設置的post_handler回調函數:
rb->post_handler(r);
相信讀到這里,POST數據如何獲取已經一目了然了。另外,如果設置了標記將post數據寫入file的話,存放file信息的buf最終會鏈入bufs chain中。