如何在nginx中讀取POST上來的數據


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中。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM