上一篇介绍了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请求。