菜鸟学习nginx之HTTP body接收(1)

上一篇介绍了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,先来看一下流程图:

菜鸟学习nginx之HTTP body接收(1)_第1张图片

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的流程。

 

你可能感兴趣的:(开源软件,Nginx)