开源软件|菜鸟学习nginx之HTTP body接收(3)

【开源软件|菜鸟学习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相关内容。

    推荐阅读