【开源软件|菜鸟学习nginx之HTTP body接收(3)】上一篇介绍了,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_trest;
/* 该值代表还有多少字节的body未读取 */
off_treceived;
/* 用于http V2版本 */
ngx_chain_t*free;
ngx_chain_t*busy;
ngx_http_chunked_t*chunked;
/* chunked信息 */
ngx_http_client_body_handler_ptpost_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_tsize;
ngx_int_trc;
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相关内容。
推荐阅读
- Go|Docker后端部署详解(Go+Nginx)
- 后台|NATAPP内网穿透通过nginx实现一个端口访问多个不同端口服务
- nginx-1.20.2安装使用
- NGINX 创始人 Igor Sysoev 退出 F5(20 年发展“简史”令人肃然起敬)
- Faker.js 作者“删库跑路”引开源圈“震动”(该开源项目现已被社区控制)
- Nginx|Nginx~从入门到入坑。
- nginx|Mac配置PHP环境(brew安装nginx+php)
- 使用ngx_lua构建高并发应用(2)
- svn|前瞻(Spring Boot 2.4.0 第二个里程碑版本发布)
- nginx cache踩坑