上一篇介绍Nginx是如何丢弃body,虽然是丢弃body但是仍然需要乖乖的从socket缓冲区中读取报文才行。本篇将介绍实际接收流程。
接收流程是比较复杂的,主要涉及到两个方面考虑:body过长如何保存以及一次接收不完body应该如何设置下次接收。Nginx采用如下方式解决上述问题:
1、如果一个buffer缓冲区不能够容纳body,则会把body写入到临时文件中。
2、如果一次接收不完则会重新设置epoll可读事件,并且修改回调函数。这点在上一篇中也有提到。第一次接收body的回调函数和第二次接收的回调函数不一样。
该函数是入口函数且上层应用唯一能够使用的接口函数,我们先来看一下它的流程图:
由于函数ngx_http_read_client_request_body逻辑比较复杂,这里将分段显示
/**
* 调用接收body
* @param r 请求
* @param post_handler body处理的回调函数
*/
ngx_int_t
ngx_http_read_client_request_body(ngx_http_request_t *r,
ngx_http_client_body_handler_pt post_handler)
{
size_t preread;
ssize_t size;
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
r->main->count++;
/**
* 如果不是原始请求、已经标记是丢弃body、request_body不空则调用回调函数
* request_body不空说明已经读取到完整body
* 为什么说明这里request_body不空就能说明已经读取到完整body呢?
* 原因: 此函数在业务处理流程只会被调用一次 即使一次epoll读取事件不能把所有
* body都读取成功,下一次读取操作不会由该函数处理而是由
* ngx_http_read_client_request_body_handler处理
*/
if (r != r->main || r->request_body || r->discard_body) {
r->request_body_no_buffering = 0;
post_handler(r); //回调函数必须调用类似ngx_http_finalize_request将count进行自减
return NGX_OK;
}
#if (NGX_HTTP_V2)
if (r->stream) {
rc = ngx_http_v2_read_request_body(r, post_handler);
goto done;
}
#endif
if (ngx_http_test_expect(r) != NGX_OK) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
说明:
1)函数第二参数post_handler是用户指定的回调函数。当body接收完毕后会主动调用该参数指向的回调函数。
2)判断body是否有效,如果有效则直接调用post_handler回调函数。注释中已经详细说明。
/* 表明开始接收body 分配request_body对象 */
rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
if (rb == NULL) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
/*
* set by ngx_pcalloc():
*
* rb->bufs = NULL;
* rb->buf = NULL;
* rb->free = NULL;
* rb->busy = NULL;
* rb->chunked = NULL;
*/
rb->rest = -1; /* 后续流程会赋值 初始值为content-length */
rb->post_handler = post_handler;
r->request_body = rb;
/* 表示body小于0且不是chunked模式 则认为没有接收到body 立即调用回调函数 */
if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) {
r->request_body_no_buffering = 0;
post_handler(r);
return NGX_OK;
}
说明:
1)创建用于保存body的request_body对象
2)判断headers_in中content_length是否小于0,如果小于0则说明没有body,则直接调用post_handler回调函数。
/* 进入此函数表示 HTTP header内容已经处理完毕 剩余的内容就是body */
preread = r->header_in->last - r->header_in->pos;
if (preread) {
/**
* there is the pre-read part of the request body
* 表示已经读取到一部分body
*/
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"http client request body preread %uz", preread);
out.buf = r->header_in;
out.next = NULL;
/* 过滤出body */
rc = ngx_http_request_body_filter(r, &out);
if (rc != NGX_OK) {
goto done;
}
r->request_length += preread - (r->header_in->last - r->header_in->pos);
/**
* 进入下面if分支条件:
* 1、非chunked模式
* 2、body还没接收完成,且header_in剩余空间足够接收剩下的body
* 做的主要工作:
* 重置读写事件,进行socket读取
*/
if (!r->headers_in.chunked
&& rb->rest > 0
&& rb->rest <= (off_t) (r->header_in->end - r->header_in->last))
{
/**
* the whole request body may be placed in r->header_in
*
*/
b = ngx_calloc_buf(r->pool);
if (b == NULL) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
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;
rb->buf = b;
/* 设置读写事件回调函数 */
r->read_event_handler = ngx_http_read_client_request_body_handler;
r->write_event_handler = ngx_http_request_empty_handler;
rc = ngx_http_do_read_client_request_body(r);
goto done;
}
} else {
/** 表示当前header_in中没有接收到body
* 同时对rb->rest进行赋值
*/
if (ngx_http_request_body_filter(r, NULL) != NGX_OK) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
}
说明:
1、preread大于0,说明header_in缓冲区内存在部分body(也可能是完整body),这个是需要剥离body(也可以理解成解析出body数据)存到request_body对象中。
2、 当执行完剥离body之后,会再次判断request_body中rest是否为0,如果不为0表示还有body没有接收完毕。因此需要创建一个buf用接收新的body数据。
3、如果preread为0,表示header_in缓冲区中没有body,这里需要设置request_body对象中rest为content_length。
if (rb->rest == 0) {/* 表示整个body已经读取完毕 执行回调函数进行处理 */
/* the whole request body was pre-read */
r->request_body_no_buffering = 0;
post_handler(r);
return NGX_OK;
}
if (rb->rest < 0) {
ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
"negative request body rest");
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
/**
* 以下流程表示 rest > 0 表示需要再次读取body
* 主要工作是创建buf、设置回调函数以及读取socket缓冲区
*/
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
size = clcf->client_body_buffer_size; //默认1M
size += size >> 2;// 1M+256K
/* TODO: honor r->request_body_in_single_buf */
if (!r->headers_in.chunked && rb->rest < size) {
size = (ssize_t) rb->rest;
if (r->request_body_in_single_buf) {
size += preread;
}
} else {
size = clcf->client_body_buffer_size;
}
/* 创建接收缓冲区 */
rb->buf = ngx_create_temp_buf(r->pool, size);
if (rb->buf == NULL) {
rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
goto done;
}
/* 设置读写事件回调函数 */
r->read_event_handler = ngx_http_read_client_request_body_handler;
r->write_event_handler = ngx_http_request_empty_handler;
/* 真正从socket中读取数据 */
rc = ngx_http_do_read_client_request_body(r);
说明:这部分代码主要流程,创建一个全新的buffer用于接收body,设置回调函数,执行函数进行socket recv操作。
done:
if (r->request_body_no_buffering
&& (rc == NGX_OK || rc == NGX_AGAIN))
{
if (rc == NGX_OK) {
r->request_body_no_buffering = 0;
} else {
/* rc == NGX_AGAIN */
r->reading_body = 1;
}
r->read_event_handler = ngx_http_block_reading;
post_handler(r);
}
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
r->main->count--; //如果返回是大于300表示出现错误 需要将count自减
}
return rc;
}
这部分代码,理解不是很清楚,但是大部分流程都进入标签done中。
在上面流程中,涉及到一个非常重要的结构--ngx_http_request_body_t,该结构体对于读取body相关功能,非常重要。其中比较难于理解的就是bufs、buf,在下面的注释已经写得很清楚了,具体定义如下:
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_read_client_request_body_handler。表明下次接收并且处理body的函数为ngx_http_read_client_request_body_handler,而不在是ngx_http_read_client_request_body。函数实现比较简单,具体如下所示:
static void
ngx_http_read_client_request_body_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
if (r->connection->read->timedout) {//如果是超时事件 则直接结束http请求
r->connection->timedout = 1;
ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
return;
}
rc = ngx_http_do_read_client_request_body(r); //真正读取body函数
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
ngx_http_finalize_request(r, rc); //返回错直接结束http请求
}
}
/**
* 真正读取body的函数
* @param r http请求
*/
static ngx_int_t
ngx_http_do_read_client_request_body(ngx_http_request_t *r)
{
off_t rest;
size_t size;
ssize_t n;
ngx_int_t rc;
ngx_chain_t out;
ngx_connection_t *c;
ngx_http_request_body_t *rb;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
rb = r->request_body;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http read client request body");
for ( ;; ) {
for ( ;; ) {
if (rb->buf->last == rb->buf->end) {//空间已满
if (rb->buf->pos != rb->buf->last) {
/**
* pass buffer to request body filter chain
* 表示当前buf中报文还没有处理 将当前buf追加到request_body中
*/
out.buf = rb->buf;
out.next = NULL;
/* 将body保存在request_body对象中 */
rc = ngx_http_request_body_filter(r, &out);
if (rc != NGX_OK) {
return rc;
}
} else {
/* update chains */
rc = ngx_http_request_body_filter(r, NULL);
if (rc != NGX_OK) {
return rc;
}
}
/* 表示缓冲区中还有body没有被处理 需要再次触发事件驱动 */
if (rb->busy != NULL) {
if (r->request_body_no_buffering) {//重新注册读事件
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_AGAIN;
}
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/**
* 这里为什么可以重置标志位:
* Nginx内部实现 当buf缓冲区满时会把缓冲区内存写到临时文件中
*/
rb->buf->pos = rb->buf->start;
rb->buf->last = rb->buf->start;
}
size = rb->buf->end - rb->buf->last; //当前buf可用接收数据的空间大小
rest = rb->rest - (rb->buf->last - rb->buf->pos); //还有多少body没有接收
if ((off_t) size > rest) {
size = (size_t) rest;
}
/* 真正从socket中 读取报文 */
n = c->recv(c, rb->buf->last, size);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http client request body recv %z", n);
if (n == NGX_AGAIN) {
break;
}
if (n == 0) {
ngx_log_error(NGX_LOG_INFO, c->log, 0,
"client prematurely closed connection");
}
if (n == 0 || n == NGX_ERROR) {
c->error = 1;
return NGX_HTTP_BAD_REQUEST;
}
rb->buf->last += n;
r->request_length += n;
if (n == rest) {
/* pass buffer to request body filter chain */
out.buf = rb->buf;
out.next = NULL;
//rb->rest在此函数中会被修改
rc = ngx_http_request_body_filter(r, &out);
if (rc != NGX_OK) {
return rc;
}
}
if (rb->rest == 0) {//表示body全部内容都已经接收完毕
break;
}
if (rb->buf->last < rb->buf->end) {
break;
}
}//最内层for循环结束
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
"http client request body rest %O", rb->rest);
if (rb->rest == 0) {//表示body全部内容都已经接收完毕
break;
}
if (!c->read->ready) {//如果是失效事件 需要重新注册事件
if (r->request_body_no_buffering
&& rb->buf->pos != rb->buf->last)
{
/* pass buffer to request body filter chain */
out.buf = rb->buf;
out.next = NULL;
rc = ngx_http_request_body_filter(r, &out);
if (rc != NGX_OK) {
return rc;
}
}
//重新注册 读取事件
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
ngx_add_timer(c->read, clcf->client_body_timeout);
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
return NGX_AGAIN;
}
}//外层for循环
/* 注意: 进入下面代码 表示读取body完成 */
if (c->read->timer_set) {
ngx_del_timer(c->read);
}
if (!r->request_body_no_buffering) {
r->read_event_handler = ngx_http_block_reading;
rb->post_handler(r);//调用回调 处理body
}
return NGX_OK;
}
虽然该函数内容比较多,单逻辑不是很复杂,在注释中我已经做了标记。
至此,本篇将Nginx是如何接收body大体流程已经介绍完毕,但是仍然没有解决我们心中的疑惑?请看下一篇。