菜鸟学习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_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;

上一篇已经介绍了该结构体,这里纯粹是为了方便下面阅读。

二、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_t                     size;
    ngx_int_t                  rc;
    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相关内容。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值