如何在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中。

 

posted @ 2012-09-07 17:21  donj  阅读(6682)  评论(1编辑  收藏  举报