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

上一篇介绍Nginx是如何丢弃body,虽然是丢弃body但是仍然需要乖乖的从socket缓冲区中读取报文才行。本篇将介绍实际接收流程。

一、概要

接收流程是比较复杂的,主要涉及到两个方面考虑:body过长如何保存以及一次接收不完body应该如何设置下次接收。Nginx采用如下方式解决上述问题:

1、如果一个buffer缓冲区不能够容纳body,则会把body写入到临时文件中。

2、如果一次接收不完则会重新设置epoll可读事件,并且修改回调函数。这点在上一篇中也有提到。第一次接收body的回调函数和第二次接收的回调函数不一样。

二、首次接收body函数ngx_http_read_client_request_body

2.1、流程图

该函数是入口函数且上层应用唯一能够使用的接口函数,我们先来看一下它的流程图:

2.2、代码

由于函数ngx_http_read_client_request_body逻辑比较复杂,这里将分段显示

/**
 * 调用接收body
 * @param r 请求
 * @param post_handler body处理的回调函数
 */
ngx_int_t
ngx_http_read_client_request_body(ngx_http_request_t *r,
    ngx_http_client_body_handler_pt post_handler)
{
    size_t                     preread;
    ssize_t                    size;
    ngx_int_t                  rc;
    ngx_buf_t                 *b;
    ngx_chain_t                out;
    ngx_http_request_body_t   *rb;
    ngx_http_core_loc_conf_t  *clcf;

    r->main->count++;
    
    /**
     * 如果不是原始请求、已经标记是丢弃body、request_body不空则调用回调函数
     * request_body不空说明已经读取到完整body
     * 为什么说明这里request_body不空就能说明已经读取到完整body呢?
     * 原因: 此函数在业务处理流程只会被调用一次 即使一次epoll读取事件不能把所有
     * body都读取成功,下一次读取操作不会由该函数处理而是由
     * ngx_http_read_client_request_body_handler处理
     */
    if (r != r->main || r->request_body || r->discard_body) {
        r->request_body_no_buffering = 0;
        post_handler(r); //回调函数必须调用类似ngx_http_finalize_request将count进行自减
        return NGX_OK;
    }

#if (NGX_HTTP_V2)
    if (r->stream) {
        rc = ngx_http_v2_read_request_body(r, post_handler);
        goto done;
    }
#endif

    if (ngx_http_test_expect(r) != NGX_OK) {
        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
        goto done;
    }

说明:

1)函数第二参数post_handler是用户指定的回调函数。当body接收完毕后会主动调用该参数指向的回调函数。 

2)判断body是否有效,如果有效则直接调用post_handler回调函数。注释中已经详细说明。

    /* 表明开始接收body 分配request_body对象 */
    rb = ngx_pcalloc(r->pool, sizeof(ngx_http_request_body_t));
    if (rb == NULL) {
        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
        goto done;
    }

    /*
     * set by ngx_pcalloc():
     *
     *     rb->bufs = NULL;
     *     rb->buf = NULL;
     *     rb->free = NULL;
     *     rb->busy = NULL;
     *     rb->chunked = NULL;
     */

    rb->rest = -1; /* 后续流程会赋值 初始值为content-length */
    rb->post_handler = post_handler;

    r->request_body = rb;
    
    /* 表示body小于0且不是chunked模式 则认为没有接收到body 立即调用回调函数 */
    if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) {
        r->request_body_no_buffering = 0;
        post_handler(r);
        return NGX_OK;
    }

说明:

1)创建用于保存body的request_body对象

2)判断headers_in中content_length是否小于0,如果小于0则说明没有body,则直接调用post_handler回调函数。

    /* 进入此函数表示 HTTP header内容已经处理完毕 剩余的内容就是body */
    preread = r->header_in->last - r->header_in->pos;

    if (preread) {

        /**
         * there is the pre-read part of the request body 
         * 表示已经读取到一部分body
         */

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                       "http client request body preread %uz", preread);

        out.buf = r->header_in;
        out.next = NULL;
        /* 过滤出body */
        rc = ngx_http_request_body_filter(r, &out);

        if (rc != NGX_OK) {
            goto done;
        }

        r->request_length += preread - (r->header_in->last - r->header_in->pos);

        /**
         * 进入下面if分支条件:
         * 1、非chunked模式
         * 2、body还没接收完成,且header_in剩余空间足够接收剩下的body
         * 做的主要工作:
         * 重置读写事件,进行socket读取
         */
        if (!r->headers_in.chunked
            && rb->rest > 0
            && rb->rest <= (off_t) (r->header_in->end - r->header_in->last))
        {
            /**
             * the whole request body may be placed in r->header_in
             * 
             */
            b = ngx_calloc_buf(r->pool);
            if (b == NULL) {
                rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
                goto done;
            }

            b->temporary = 1;
            b->start = r->header_in->pos;
            b->pos = r->header_in->pos;
            b->last = r->header_in->last;
            b->end = r->header_in->end;

            rb->buf = b;
            /* 设置读写事件回调函数 */
            r->read_event_handler = ngx_http_read_client_request_body_handler;
            r->write_event_handler = ngx_http_request_empty_handler;

            rc = ngx_http_do_read_client_request_body(r);
            goto done;
        }

    } else {
        /** 表示当前header_in中没有接收到body 
         * 同时对rb->rest进行赋值
         */
        if (ngx_http_request_body_filter(r, NULL) != NGX_OK) {
            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
            goto done;
        }
    }

说明:

1、preread大于0,说明header_in缓冲区内存在部分body(也可能是完整body),这个是需要剥离body(也可以理解成解析出body数据)存到request_body对象中。

2、 当执行完剥离body之后,会再次判断request_body中rest是否为0,如果不为0表示还有body没有接收完毕。因此需要创建一个buf用接收新的body数据。

3、如果preread为0,表示header_in缓冲区中没有body,这里需要设置request_body对象中rest为content_length。

    if (rb->rest == 0) {/* 表示整个body已经读取完毕 执行回调函数进行处理 */
        /* the whole request body was pre-read */
        r->request_body_no_buffering = 0;
        post_handler(r);
        return NGX_OK;
    }

    if (rb->rest < 0) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                      "negative request body rest");
        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
        goto done;
    }

    /**
     * 以下流程表示 rest > 0 表示需要再次读取body 
     * 主要工作是创建buf、设置回调函数以及读取socket缓冲区     
     */
    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    size = clcf->client_body_buffer_size; //默认1M
    size += size >> 2;// 1M+256K

    /* TODO: honor r->request_body_in_single_buf */

    if (!r->headers_in.chunked && rb->rest < size) {
        size = (ssize_t) rb->rest;

        if (r->request_body_in_single_buf) {
            size += preread;
        }

    } else {
        size = clcf->client_body_buffer_size;
    }
    /* 创建接收缓冲区 */
    rb->buf = ngx_create_temp_buf(r->pool, size);
    if (rb->buf == NULL) {
        rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
        goto done;
    }
    /* 设置读写事件回调函数 */
    r->read_event_handler = ngx_http_read_client_request_body_handler;
    r->write_event_handler = ngx_http_request_empty_handler;
    /* 真正从socket中读取数据 */
    rc = ngx_http_do_read_client_request_body(r);

 说明:这部分代码主要流程,创建一个全新的buffer用于接收body,设置回调函数,执行函数进行socket recv操作。

done:
    
    if (r->request_body_no_buffering
        && (rc == NGX_OK || rc == NGX_AGAIN))
    {
        if (rc == NGX_OK) {
            r->request_body_no_buffering = 0;

        } else {
            /* rc == NGX_AGAIN */
            r->reading_body = 1;
        }

        r->read_event_handler = ngx_http_block_reading;
        post_handler(r);
    }

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        r->main->count--; //如果返回是大于300表示出现错误 需要将count自减
    }

    return rc;
}

这部分代码,理解不是很清楚,但是大部分流程都进入标签done中。

2.3、request_body结构

在上面流程中,涉及到一个非常重要的结构--ngx_http_request_body_t,该结构体对于读取body相关功能,非常重要。其中比较难于理解的就是bufs、buf,在下面的注释已经写得很清楚了,具体定义如下:

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;

三、非首次接收body函数ngx_http_read_client_request_body_handler

我们在上面,看到两处重置读写事件,其中读事件设置的函数为ngx_http_read_client_request_body_handler。表明下次接收并且处理body的函数为ngx_http_read_client_request_body_handler,而不在是ngx_http_read_client_request_body。函数实现比较简单,具体如下所示:

static void
ngx_http_read_client_request_body_handler(ngx_http_request_t *r)
{
    ngx_int_t  rc;

    if (r->connection->read->timedout) {//如果是超时事件 则直接结束http请求
        r->connection->timedout = 1;
        ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT);
        return;
    }

    rc = ngx_http_do_read_client_request_body(r); //真正读取body函数

    if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
        ngx_http_finalize_request(r, rc); //返回错直接结束http请求
    }
}

四、真正接收body函数ngx_http_do_read_client_request_body

/**
 * 真正读取body的函数
 * @param r http请求
 */
static ngx_int_t
ngx_http_do_read_client_request_body(ngx_http_request_t *r)
{
    off_t                      rest;
    size_t                     size;
    ssize_t                    n;
    ngx_int_t                  rc;
    ngx_chain_t                out;
    ngx_connection_t          *c;
    ngx_http_request_body_t   *rb;
    ngx_http_core_loc_conf_t  *clcf;

    c = r->connection;
    rb = r->request_body;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http read client request body");

    for ( ;; ) {
        for ( ;; ) {
            if (rb->buf->last == rb->buf->end) {//空间已满

                if (rb->buf->pos != rb->buf->last) {

                    /**
                     * pass buffer to request body filter chain 
                     * 表示当前buf中报文还没有处理 将当前buf追加到request_body中
                     */

                    out.buf = rb->buf;
                    out.next = NULL;
                    /* 将body保存在request_body对象中 */
                    rc = ngx_http_request_body_filter(r, &out);

                    if (rc != NGX_OK) {
                        return rc;
                    }

                } else {
                    /* update chains */
                    rc = ngx_http_request_body_filter(r, NULL);

                    if (rc != NGX_OK) {
                        return rc;
                    }
                }
                
                /* 表示缓冲区中还有body没有被处理 需要再次触发事件驱动 */
                if (rb->busy != NULL) {
                    if (r->request_body_no_buffering) {//重新注册读事件
                        if (c->read->timer_set) {
                            ngx_del_timer(c->read);
                        }

                        if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                            return NGX_HTTP_INTERNAL_SERVER_ERROR;
                        }

                        return NGX_AGAIN;
                    }

                    return NGX_HTTP_INTERNAL_SERVER_ERROR;
                }
                /** 
                 * 这里为什么可以重置标志位:
                 * Nginx内部实现 当buf缓冲区满时会把缓冲区内存写到临时文件中
                 */
                rb->buf->pos = rb->buf->start;
                rb->buf->last = rb->buf->start;
            }

            size = rb->buf->end - rb->buf->last; //当前buf可用接收数据的空间大小
            rest = rb->rest - (rb->buf->last - rb->buf->pos); //还有多少body没有接收

            if ((off_t) size > rest) {
                size = (size_t) rest;
            }
            /* 真正从socket中 读取报文 */
            n = c->recv(c, rb->buf->last, size);

            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                           "http client request body recv %z", n);

            if (n == NGX_AGAIN) {
                break;
            }

            if (n == 0) {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                              "client prematurely closed connection");
            }

            if (n == 0 || n == NGX_ERROR) {
                c->error = 1;
                return NGX_HTTP_BAD_REQUEST;
            }

            rb->buf->last += n;
            r->request_length += n;

            if (n == rest) {
                /* pass buffer to request body filter chain */

                out.buf = rb->buf;
                out.next = NULL;
                //rb->rest在此函数中会被修改
                rc = ngx_http_request_body_filter(r, &out);

                if (rc != NGX_OK) {
                    return rc;
                }
            }

            if (rb->rest == 0) {//表示body全部内容都已经接收完毕
                break;
            }

            if (rb->buf->last < rb->buf->end) {
                break;
            }
        }//最内层for循环结束

        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                       "http client request body rest %O", rb->rest);

        if (rb->rest == 0) {//表示body全部内容都已经接收完毕
            break;
        }

        if (!c->read->ready) {//如果是失效事件 需要重新注册事件

            if (r->request_body_no_buffering
                && rb->buf->pos != rb->buf->last)
            {
                /* pass buffer to request body filter chain */

                out.buf = rb->buf;
                out.next = NULL;

                rc = ngx_http_request_body_filter(r, &out);

                if (rc != NGX_OK) {
                    return rc;
                }
            }
            //重新注册 读取事件
            clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
            ngx_add_timer(c->read, clcf->client_body_timeout);

            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            return NGX_AGAIN;
        }
    }//外层for循环
    
    /* 注意: 进入下面代码 表示读取body完成 */
    if (c->read->timer_set) {
        ngx_del_timer(c->read);
    }

    if (!r->request_body_no_buffering) {
        r->read_event_handler = ngx_http_block_reading;
        rb->post_handler(r);//调用回调 处理body
    }

    return NGX_OK;
}

虽然该函数内容比较多,单逻辑不是很复杂,在注释中我已经做了标记。 

五、总结

至此,本篇将Nginx是如何接收body大体流程已经介绍完毕,但是仍然没有解决我们心中的疑惑?请看下一篇

发布了107 篇原创文章 · 获赞 33 · 访问量 12万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览