在解析完 http 请求报文后, 需要发出响应报文, 那么ngx 框架 提供了那些通用接口呢?如果自己设计将所用的模块的响应接口合并起来 你会怎么设计呢??
响应头过滤函数主要的用处就是处理HTTP响应的头,可以根据实际情况对于响应头进行修改或者添加删除。
响应头过滤函数先于响应体过滤函数,而且只调用一次,所以一般可作过滤模块的初始化工作。
响应头过滤函数的入口只有一个:该函数向客户端发送回复的时候调用,然后按前一节所述的执行顺序。该函数的返回值一般是NGX_OK,NGX_ERROR和NGX_AGAIN,分别表示处理成功,失败和未完成。
//调用ngx_http_output_filter方法即可向客户端发送HTTP响应包体,ngx_http_send_header发送响应行和响应头部 ngx_int_t ngx_http_send_header(ngx_http_request_t *r) { if (r->post_action) { return NGX_OK; } ngx_log_debugall(r->connection->log, 0, "ngx http send header"); if (r->header_sent) { 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_send_header发送HTTP头部时,就从ngx_http_top_header_filter指针开始遍历所有的HTTP头部过滤模块, 而在执行ngx_http_output_filter发送HTTP包体时,就从ngx_http_top_body_filter指针开始遍历所有的HTTP包体过滤模块 */ //包体通过ngx_http_output_filter循环发送 /* 注意对于HTTP过滤模块来说,在ngx_modules数组中的位置越靠后,在实陈执行请求时就越优先执行。因为在初始化HTTP过滤模块时,每一个http 过滤模块都是将自己插入到整个单链表的首部的。 */ //ngx_http_header_filter_module是最后一个header filter模块(ngx_http_top_header_filter = ngx_http_header_filter;他是最后发送头部的地方), //ngx_http_write_filter_module是最后一个包体writer模块(ngx_http_top_body_filter = ngx_http_write_filter;),他是最后放包体的地方 //调用ngx_http_output_filter方法即可向客户端发送HTTP响应包体,ngx_http_send_header发送响应行和响应头部 ngx_http_output_header_filter_pt ngx_http_top_header_filter;//所有的HTTP头部过滤模块都添加到该指针上 ngx_http_send_header中调用链表中所有处理方法 //该函数中的所有filter通过ngx_http_output_filter开始执行 ngx_http_output_body_filter_pt ngx_http_top_body_filter;//所有的HTTP包体部分都是添加到该指针中,在ngx_http_output_filter中一次调用链表中的各个函数
ngx_http_top_header_filter是一个全局变量。当编译进一个filter模块的时候,就被赋值为当前filter模块的处理函数。而ngx_http_next_header_filter是一个局部全局变量,它保存了编译前上一个filter模块的处理函数。所以整体看来,就像用全局变量组成的一条单向链表。
每个模块想执行下一个过滤函数,只要调用一下ngx_http_next_header_filter这个局部变量。而整个过滤模块链的入口,需要调用ngx_http_top_header_filter这个全局变量。ngx_http_top_body_filter的行为与header fitler类似。
响应头和响应体过滤函数的执行顺序如下所示:
在向headers链表中添加自定义的HTTP头部时,可以参考ngx_list_push的使用方法。这里有一个简单的例子,如下所示。
ngx_table_elt_t* h = ngx_list_push(&r->headers_out.headers);
if (h == NULL) {
return NGX_ERROR;
}
h->hash = 1;
h->key.len = sizeof("TestHead") - 1;
h->key.data = (u_char *) "TestHead";
h->value.len = sizeof("TestValue") - 1;
h->value.data = (u_char *) "TestValue";
这样将会在响应中新增一行HTTP头部:
TestHead: TestValud
响应体过滤函数
响应体过滤函数是过滤响应主体的函数。ngx_http_top_body_filter这个函数每个请求可能会被执行多次,它的入口函数是ngx_http_output_filter,
当ngx_http_output_filter方法返回时,可能由于TCP连接上的缓冲区还不可写,所以导致ngx_buf_t缓冲区指向的内存还没有发送,可这时方法返回已把控制权交给Nginx了,又会导致栈里的内存被释放,最后就会造成内存越界错误。因此,在发送响应包体时,尽量将ngx_buf_t中的pos指针指向从内存池里分配的内存。
//r是request请求,in是输入的chain
//调用ngx_http_output_filter方法即可向客户端发送HTTP响应包体,ngx_http_send_header发送响应行和响应头部
Nginx还封装了一个生成ngx_buf_t的简便方法,它完全等价于上面的6行语句,如下所示。
ngx_buf_t *b = ngx_create_temp_buf(r->pool, 128);
分配完内存后,可以向这段内存写入数据。当写完数据后,要让b->last指针指向数据的末尾,如果b->last与b->pos相等,那么HTTP框架是不会发送一个字节的包体的。
最后,把上面的ngx_buf_t *b用ngx_chain_t传给ngx_http_output_filter方法就可以发送HTTP响应的包体内容了。例如: ngx_chain_t out; out.buf = b; out.next = NULL; return ngx_http_output_filter(r, &out);
ngx_int_t ngx_http_output_filter(ngx_http_request_t *r, ngx_chain_t *in) {//如果内容被保存到了临时文件中,则会在ngx_http_copy_filter->ngx_output_chain->ngx_output_chain_copy_buf->ngx_read_file中读取文件内容,然后发送 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); //filter上面的最后一个钩子是ngx_http_write_filter if (rc == NGX_ERROR) { /* NGX_ERROR may be returned by any filter */ c->error = 1; } return rc; }
ngx_http_output_filter可以被一般的静态处理模块调用,也有可能是在upstream模块里面被调用,对于整个请求的处理阶段来说,他们处于的用处都是一样的,就是把响应内容过滤,然后发给客户端。