菜鸟学习nginx之发送HTTP响应

上一篇介绍了Nginx接收HTTP body流程,今天介绍Nginx发送HTTP响应流程。

一、发送HTTP Response header

发送响应需要设置两部分内容,Response header和Response body(若有)。这里介绍一下发送的header函数。

1.1、存储结构

发送HTTP响应行、HTTP响应头保存的数据结构在,ngx_http_request_t中headers_out结构中,该结构具体定义如下:

/**
 * 保存HTTP响应行以及响应头
 */
typedef struct {
    ngx_list_t                        headers; /* 响应头、响应头以链表方式存储 */
    /**
     * 以下所有字段是Nginx为了提升访问速度,把常用字段独立定义出,
     * 与请求headers_in一样
     */
    ngx_uint_t                        status;
    ngx_str_t                         status_line;

    ngx_table_elt_t                  *server;
    ngx_table_elt_t                  *date;
    ngx_table_elt_t                  *content_length;
    ngx_table_elt_t                  *content_encoding;
    ngx_table_elt_t                  *location;
    ngx_table_elt_t                  *refresh;
    ngx_table_elt_t                  *last_modified;
    ngx_table_elt_t                  *content_range;
    ngx_table_elt_t                  *accept_ranges;
    ngx_table_elt_t                  *www_authenticate;
    ngx_table_elt_t                  *expires;
    ngx_table_elt_t                  *etag;

    ngx_str_t                        *override_charset;

    size_t                            content_type_len;
    ngx_str_t                         content_type;
    ngx_str_t                         charset;
    u_char                           *content_type_lowcase;
    ngx_uint_t                        content_type_hash;

    ngx_array_t                       cache_control;

    off_t                             content_length_n;
    off_t                             content_offset;
    time_t                            date_time;
    time_t                            last_modified_time;
} ngx_http_headers_out_t;

1.2、发送header入口函数

ngx_int_t
ngx_http_send_header(ngx_http_request_t *r)
{
    if (r->post_action)
    {
        return NGX_OK;
    }

    if (r->header_sent)
    {//为1 代码header已经发送出去了
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                      "header already sent");
        return NGX_ERROR;
    }

    if (r->err_status)
    {//设置响应码
        r->headers_out.status = r->err_status;
        r->headers_out.status_line.len = 0;
    }

    return ngx_http_top_header_filter(r); //ngx_http_header_filter
}

 由此可知,ngx_http_header_filter才是真正发送header函数

1.3、ngx_http_header_filter发送header

主要讲解一下该函数主要做了三件事:

1)统计要发送的响应行、响应头所占内存长度。

2)根据1)计算出的长度,申请内存,并对其进行赋值

3)调用ngx_http_write_filter函数发送给客户端。

由于该函数比较长,但是代码逻辑并不是很复杂,为了节约篇幅,这里只罗列一部分代码,具体如下:

/**
 * 发送响应行、响应头
 */
static ngx_int_t
ngx_http_header_filter(ngx_http_request_t *r)
{
    u_char                    *p;
    size_t                     len;
    ngx_str_t                  host, *status_line;
    ngx_buf_t                 *b;
    ngx_uint_t                 status, i, port;
    ngx_chain_t                out;
    ngx_list_part_t           *part;
    ngx_table_elt_t           *header;
    ngx_connection_t          *c;
    ngx_http_core_loc_conf_t  *clcf;
    ngx_http_core_srv_conf_t  *cscf;
    u_char                     addr[NGX_SOCKADDR_STRLEN];

    if (r->header_sent) {
        return NGX_OK;
    }

    r->header_sent = 1;

    if (r != r->main) {//如果不是原始请求 则直接返回
        return NGX_OK;
    }

    if (r->http_version < NGX_HTTP_VERSION_10) {
        return NGX_OK;
    }

    if (r->method == NGX_HTTP_HEAD) {
        r->header_only = 1;
    }
    /* 修改时间设置 主要用于浏览器cookie设置 */
    if (r->headers_out.last_modified_time != -1) {
        if (r->headers_out.status != NGX_HTTP_OK
            && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT
            && r->headers_out.status != NGX_HTTP_NOT_MODIFIED)
        {
            r->headers_out.last_modified_time = -1;
            r->headers_out.last_modified = NULL;
        }
    }

    /* 计算响应行、响应头所需要的内存长度 */
    len = sizeof("HTTP/1.x ") - 1 + sizeof(CRLF) - 1
          /* the end of the header */
          + sizeof(CRLF) - 1;

    ...

    /* 至此 长度统计完毕 分配内存 */
    b = ngx_create_temp_buf(r->pool, len);
    if (b == NULL) {
        return NGX_ERROR;
    }

    /* 赋值 */
    /* "HTTP/1.x " */
    b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);

    /* status line */
    if (status_line) {
        b->last = ngx_copy(b->last, status_line->data, status_line->len);

    } else {
        b->last = ngx_sprintf(b->last, "%03ui ", status);
    }
    *b->last++ = CR; *b->last++ = LF;

    ...

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "%*s", (size_t) (b->last - b->pos), b->pos);

    /* the end of HTTP header */
    *b->last++ = CR; *b->last++ = LF;
    /* 赋值完毕 统计实际要发送长度 */
    r->header_size = b->last - b->pos;
    /* 如果只发送header 此处需要对last_buf标志位设置为1 */
    if (r->header_only) {
        b->last_buf = 1;
    }

    out.buf = b;
    out.next = NULL;
    /* 发送header */
    return ngx_http_write_filter(r, &out);
}

对于ngx_http_write_filter后续发送body也会使用到,后面小节会详细说明。 

二、发送HTTP Response body

发送body使用的函数是ngx_http_output_filter,该函数比较简单,代码如下:

/**
 * 发送body
 * @param r http请求
 * @param in 存储body的缓冲区
 */
ngx_int_t
ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    ngx_int_t rc;
    ngx_connection_t *c;

    c = r->connection;

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http output filter \"%V?%V\"", &r->uri, &r->args);

    rc = ngx_http_top_body_filter(r, in); //ngx_http_write_filter

    if (rc == NGX_ERROR)
    {
        /* NGX_ERROR may be returned by any filter */
        c->error = 1;
    }

    return rc;
}

 通过上面代码,可知最后调用到ngx_http_write_filter函数。

三、ngx_http_write_filter

无论发送header还是body,最终都会调用到ngx_http_write_filter函数。该函数篇幅比较多,这里分片显示,具体如下:

/**
 * 发送报文
 * @param r http请求
 * @param in 待发送缓冲区
 */
ngx_int_t
ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
{
    off_t                      size, sent, nsent, limit;
    ngx_uint_t                 last, flush, sync;
    ngx_msec_t                 delay;
    ngx_chain_t               *cl, *ln, **ll, *chain;
    ngx_connection_t          *c;
    ngx_http_core_loc_conf_t  *clcf;

    c = r->connection;

    if (c->error) {
        return NGX_ERROR;
    }

    size = 0;
    flush = 0;
    sync = 0;
    last = 0;
    ll = &r->out; /* 一次socket send操作可能发送不完,剩下的内容保存在这里 */

    /**
     * find the size, the flush point and the last link of the saved chain 
     * out保存上次没有发送报文,in是本次需要发送的报文
     * 计算上次未发送报文的长度
     */
    for (cl = r->out; cl; cl = cl->next) {
        ll = &cl->next;

        size += ngx_buf_size(cl->buf); //统计大小

        if (cl->buf->flush || cl->buf->recycled) {
            flush = 1;
        }

        if (cl->buf->sync) {
            sync = 1;
        }

        if (cl->buf->last_buf) {
            last = 1;
        }
    }

    /**
     * add the new chain to the existent one 
     * 将本次要发送in缓冲区 追加到out末尾
     */
    for (ln = in; ln; ln = ln->next) {
        cl = ngx_alloc_chain_link(r->pool);
        if (cl == NULL) {
            return NGX_ERROR;
        }
        /* 插入到out末尾处 */
        cl->buf = ln->buf;
        *ll = cl;
        ll = &cl->next;

        size += ngx_buf_size(cl->buf);//统计大小

        if (cl->buf->flush || cl->buf->recycled) {
            flush = 1;
        }

        if (cl->buf->sync) {
            sync = 1;
        }

        if (cl->buf->last_buf) {
            last = 1;
        }
    }
    /* 至此 size大小=上次未发送报文大小+本次待发送大小 */
    *ll = NULL;

    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http write filter: l:%ui f:%ui s:%O", last, flush, size);

说明:

1、这里需要明确ngx_http_request_t结构体中out代表的含义。一次socket send操作,可能无法把所有数据发到对端,那么就需要下次发送,那么如果保存未发送的数据呢,就是利用out变量保存。

2、这部分代码主要是把待发送的缓冲区,插入到out中。

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    /*
     * avoid the output if there are no last buf, no flush point,
     * there are the incoming bufs and the size of all bufs
     * is smaller than "postpone_output" directive
     * 这段代码 Nginx考虑的出发点是 减少小报文发送,尽量等到大报文时,才会执行
     * 发送动作
     * 只要下面有一个条件不满足,就立刻发送报文到对端
     * last==0,表示没有收到 完整要发送缓冲区
     * flush==0,表示不是刷新操作
     * in != NULL 表是要发送缓冲区
     * 当前size 小于默认发送大小
     */
    if (!last && !flush && in && size < (off_t) clcf->postpone_output) {
        return NGX_OK;
    }

虽然这里只有一行代码,但是蕴藏的逻辑还是比较多的,在上面的注释中我已经有详细说明。下面这部分代码比较枯燥,不是很好理解,但是对于理解流程影响不大。

    if (c->write->delayed) {
        c->buffered |= NGX_HTTP_WRITE_BUFFERED;
        return NGX_AGAIN;
    }

    if (size == 0
        && !(c->buffered & NGX_LOWLEVEL_BUFFERED)
        && !(last && c->need_last_buf))
    {
        if (last || flush || sync) {
            for (cl = r->out; cl; /* void */) {
                ln = cl;
                cl = cl->next;
                ngx_free_chain(r->pool, ln);
            }

            r->out = NULL;
            c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;

            return NGX_OK;
        }

        ngx_log_error(NGX_LOG_ALERT, c->log, 0,
                      "the http output chain is empty");

        ngx_debug_point();

        return NGX_ERROR;
    }
    //这部分是针对 限速问题Nginx做的优化
    if (r->limit_rate) {
        if (r->limit_rate_after == 0) {
            r->limit_rate_after = clcf->limit_rate_after;
        }

        limit = (off_t) r->limit_rate * (ngx_time() - r->start_sec + 1)
                - (c->sent - r->limit_rate_after);

        if (limit <= 0) {
            c->write->delayed = 1;
            delay = (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1);
            ngx_add_timer(c->write, delay);

            c->buffered |= NGX_HTTP_WRITE_BUFFERED;

            return NGX_AGAIN;
        }

        if (clcf->sendfile_max_chunk
            && (off_t) clcf->sendfile_max_chunk < limit)
        {
            limit = clcf->sendfile_max_chunk;
        }

    } else {
        limit = clcf->sendfile_max_chunk;
    }

下面开始,Nginx将进行发送报文到对端,代码如下:


    sent = c->sent;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http write filter limit %O", limit);

    chain = c->send_chain(c, r->out, limit);

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                   "http write filter %p", chain);

    if (chain == NGX_CHAIN_ERROR) {
        c->error = 1;
        return NGX_ERROR;
    }
if (r->limit_rate) {

        nsent = c->sent;

        if (r->limit_rate_after) {

            sent -= r->limit_rate_after;
            if (sent < 0) {
                sent = 0;
            }

            nsent -= r->limit_rate_after;
            if (nsent < 0) {
                nsent = 0;
            }
        }

        delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate);

        if (delay > 0) {
            limit = 0;
            c->write->delayed = 1;
            ngx_add_timer(c->write, delay);
        }
    }

上面这部分代码还是针对限速进行设置。最后这一部分主要是对一些标志位设置以及内存释放回收。

    if (limit
        && c->write->ready
        && c->sent - sent >= limit - (off_t) (2 * ngx_pagesize))
    {
        c->write->delayed = 1;
        ngx_add_timer(c->write, 1);
    }

    for (cl = r->out; cl && cl != chain; /* void */) {
        ln = cl;
        cl = cl->next;
        ngx_free_chain(r->pool, ln);
    }

    r->out = chain;

    if (chain) {
        c->buffered |= NGX_HTTP_WRITE_BUFFERED;
        return NGX_AGAIN;
    }

    c->buffered &= ~NGX_HTTP_WRITE_BUFFERED;

    if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) {
        return NGX_AGAIN;
    }

    return NGX_OK;
}

其实对ngx_http_write_filter理解真的不是很清楚,若以后工作中能对这部分代码有更深刻的理解,会再次编辑本页面。

四、总结

至此Nginx就会把响应行、响应头以及body都会发送到客户端。整个发送流程到这里就算介绍完毕了。但是还有一个疑问,难道一次发送就能把数据都发送给客户端吗?显然是不行的,那么Nginx是如何解决响应数据发送多次呢?在发送流程中没有见到具体设置啊。请看一下篇,结束http请求。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值