上一篇介绍了Nginx是如何处理HTTP请求的,其实对于一个真正的HTTP请求,往往是有body的,只有处理完body才算真正处理完该请求。
对于HTTP body来说一般处理有两种方式,要么是丢弃body,要么是接收body。对于接收body面临比较大的挑战,因为body是不定长,我们无法预先分配内存来接收body。况且http body可能很大,例如都上传一个高清电影,可能就有几个G大小。直接申请内存也是不现实的。接下来看一下Nginx是如何处理这种场景,为我们以后提供了指点方向。
一、丢弃body
有些时候我们可能不需要处理body,但是客户端却发来了body。对于这种场景我们需要丢弃body。这里的丢弃不是不进行处理,我们仍然需要从协议栈中读取数据,只不过读取之后直接丢弃,不做任何处理。为什么必须要从协议栈中读取呢?因为很多HTTP客户端是有超时定时器的,如果不读取客户端会认为服务端没有接收到,客户端就会进行超时处理。
1.1、流程图
Nginx实现丢弃body函数为ngx_http_discard_request_body,先来看一下流程图:
1.2、ngx_http_discard_request_body
/**
* 丢弃http body
* @param r 请求
*/
ngx_int_t
ngx_http_discard_request_body(ngx_http_request_t *r)
{
ssize_t size;
ngx_int_t rc;
ngx_event_t *rev;
/* 如果不是原始请求、已经标记是丢弃body、request_body不空则直接返回 */
if (r != r->main || r->discard_body || r->request_body) {
return NGX_OK;
}
#if (NGX_HTTP_V2)
if (r->stream) {
r->stream->skip_data = 1;
return NGX_OK;
}
#endif
/* 处理http1.1 expect */
if (ngx_http_test_expect(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
rev = r->connection->read;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http set discard body");
/* 当前连接是否存在定时器 */
if (rev->timer_set) {
ngx_del_timer(rev);
}
/* 判断http body是否有效 */
if (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked) {
return NGX_OK;
}
size = r->header_in->last - r->header_in->pos;
/**
* 1、size不为0 表示header_in中还有未处理的缓冲数据 即header_in中已经接收了
* 一部分body
* 2、http是chunked结构
* 使用header_in作为接收缓冲区
*/
if (size || r->headers_in.chunked) {
/* 从header_in过滤出body 执行丢弃动作 */
rc = ngx_http_discard_request_body_filter(r, r->header_in);
if (rc != NGX_OK) {
return rc;
}
if (r->headers_in.content_length_n == 0) {//表明body已经处理完毕
return NGX_OK;
}
}
/* 单独申请buffer 用于接收 */
rc = ngx_http_read_discarded_request_body(r);
/* 返回NGX_OK表示丢弃成功 */
if (rc == NGX_OK) {
r->lingering_close = 0;
return NGX_OK;
}
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
return rc;
}
/* rc == NGX_AGAIN */
/**
* 丢弃body工作没有彻底完成,需要再次执行 而下次执行丢弃动作的函数为
* ngx_http_discarded_request_body_handler
*/
r->read_event_handler = ngx_http_discarded_request_body_handler;
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
r->count++; //引用计数自增 保证能够顺利丢弃body
r->discard_body = 1;
return NGX_OK;
}
该函数注释内容已经很清晰了。这里简要总结一下丢弃body函数主要处理逻辑:
1、有些时候我们HTTP body内容很小,可能在接收HTTP header的时候就把完整的body一起接收到了。所以就有上面处理header_in的场景
2、当我们的body很大的时候,例如上传文件。可能再一次处理并不能完后才能读操作,因此需要重新设置读事件,以便后续能够再次执行丢弃动作。但是第二次调度时处理函数已经发生变化,不在是ngx_http_discard_request_body而改成ngx_http_discarded_request_body_handler。
1.3、ngx_http_discarded_request_body_handler
/**
* 非首次处理丢弃body动作
* @param r http请求
*/
void
ngx_http_discarded_request_body_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_msec_t timer;
ngx_event_t *rev;
ngx_connection_t *c;
ngx_http_core_loc_conf_t *clcf;
c = r->connection;
rev = c->read;
if (rev->timedout) {/* 如果是超时事件 则直接结束HTTP请求 */
c->timedout = 1;
c->error = 1;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
if (r->lingering_time) {/* 是否延迟关闭 */
timer = (ngx_msec_t) r->lingering_time - (ngx_msec_t) ngx_time();
if ((ngx_msec_int_t) timer <= 0) {/* 延迟关闭已经过期 需要立即关闭 */
r->discard_body = 0;
r->lingering_close = 0;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
} else {
timer = 0;
}
/* 执行recv动作 进行socket读取 */
rc = ngx_http_read_discarded_request_body(r);
if (rc == NGX_OK) {/* 处理成功 关闭HTTP请求 */
r->discard_body = 0;
r->lingering_close = 0;
ngx_http_finalize_request(r, NGX_DONE);
return;
}
if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
c->error = 1;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
/* rc == NGX_AGAIN */
/**
* 丢弃body工作没有彻底完成,需要再次执行 而下次执行丢弃动作的函数为
* ngx_http_discarded_request_body_handler
*/
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
c->error = 1;
ngx_http_finalize_request(r, NGX_ERROR);
return;
}
if (timer) {/* 设置定时器 */
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
timer *= 1000;
if (timer > clcf->lingering_timeout) {
timer = clcf->lingering_timeout;
}
ngx_add_timer(rev, timer);
}
}
上面函数是第二次以后需要执行丢弃动作的回调函数。该函数逻辑比较简单,此处不做深入解答。
二、总结
对于丢弃body的处理逻辑比较简单。下一篇介绍真正接收body的流程。