上一篇介绍了,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相关内容。