请直接看结论。
一、 代码结构
SendResponseFilter是Zuul的最后一个Filter,负责最终响应结果的渲染输出。
其run方法异常简介,如下:
@Override
public Object run() {
try {
addResponseHeaders();
writeResponse();
}
catch (Exception ex) {
ReflectionUtils.rethrowRuntimeException(ex);
}
return null;
}
只做两件事情:一、添加响应头;二、渲染响应结果到响应体。
addResponseHeaders()方法较简单,大家可自行阅读。
二、 writeResponse
下面对writeResponse()进行分析确认,以便了解zuul内部机制,便于绕开各种小坑。
如下:
private void writeResponse()throws Exception {
RequestContext context = RequestContext.getCurrentContext();
// there is no body to send
// getResponseBody是从上下文取返回文本,这也是推荐做法。
// getResponseDataStream直接读输出流
// 这两个是SendResponseFilter获取输出内容的两种渠道
if (context.getResponseBody() ==null
&& context.getResponseDataStream() ==null) {
return;
}
// 获取输出对象
// context.getResponse() 这个方法普通filter也可以调用,个人认为不够安全,容易增大各filter协调的难度,引入各种bug
HttpServletResponse servletResponse = context.getResponse();
if (servletResponse.getCharacterEncoding() ==null) {// only set if not set
servletResponse.setCharacterEncoding("UTF-8");
}
OutputStream outStream = servletResponse.getOutputStream();
InputStream is =null;
try {
// 首先看上下文中有没有返回文本(getResponseBody),如果有,直接输出
if (RequestContext.getCurrentContext().getResponseBody() !=null) {
String body = RequestContext.getCurrentContext().getResponseBody();
writeResponse(
new ByteArrayInputStream(
body.getBytes(servletResponse.getCharacterEncoding())),
outStream);
// 输出上下文中response为key的文本,完成,收工!
return;
}
boolean isGzipRequested =false;
final String requestEncoding = context.getRequest()
.getHeader(ZuulHeaders.ACCEPT_ENCODING);
if (requestEncoding !=null
&& HTTPRequestUtils.getInstance().isGzipped(requestEncoding)) {
isGzipRequested =true;
}
// getResponseBody中没有值,从getResponseDataStream获取
is = context.getResponseDataStream();
InputStream inputStream = is;
if (is !=null) {
if (context.sendZuulResponse()) {
// if origin response is gzipped, and client has not requested gzip,
// decompress stream
// before sending to client
// else, stream gzip directly to client
// 如果启用压缩,返回流再包装一层
if (context.getResponseGZipped() && !isGzipRequested) {
// If origin tell it's GZipped but the content is ZERO bytes,
// don't try to uncompress
final Long len = context.getOriginContentLength();
if (len ==null || len >0) {
try {
inputStream =new GZIPInputStream(is);
}
catch (java.util.zip.ZipException ex) {
log.debug(
"gzip expected but not "
+"received assuming unencoded response "
+ RequestContext.getCurrentContext()
.getRequest().getRequestURL()
.toString());
inputStream = is;
}
}
else {
// Already done : inputStream = is;
}
}
else if (context.getResponseGZipped() && isGzipRequested) {
servletResponse.setHeader(ZuulHeaders.CONTENT_ENCODING,"gzip");
}
writeResponse(inputStream, outStream);
}
}
}
finally {
try {
if (is !=null) {
is.close();
}
outStream.flush();
// The container will close the stream for us
}
catch (IOException ex) {
}
}
}
三、结论
开发自定义Filter时,如果涉及对输出内容的控制或改造,需要了解Zuul是最终如何处理内容的。
Zuul从上下文中通过getResponseBody或getResponseDataStream读取返回内容,优先采用getResponseBody的内容。
因此,一旦我们采用 RequestContext.getCurrentContext().setResponseBody("haha"); 的方式设置返回内容,responseDataStream中即使有内容也会被忽略。
开发自定义Filter时,如果需要获取返回内容,加工,再放回,策略如下:
1、 如果其他filter已经将内容放到 responseBody 中,取出、处理、再放回即可。
String s = RequestContext.getCurrentContext().getResponseBody();
s +="mycustom";
RequestContext.getCurrentContext().setResponseBody(s);
2、 如果 RequestContext.getCurrentContext().getResponseBody() 获取不到,则返回内容在responseDataStream中。
流中的内容只可以读一次,可以通过wrapper的方式,但操作复杂。
居中的方案是读出来,处理,放入responseBody中。
这时候,可能后面的filter,期待从responseDataStream中读数据,很遗憾,它读不到了, 各fitler之间的协调确实麻烦。
3、 既然了解了Zuul Filter的这个特点,大家可以约定,涉及对response内容修改的情况下:
(1)每个人要加Filter的时候,一定要加在最后(filterOrder最大),这样可以避免影响其他的Filter,如果确实逻辑上有限制,需要确保后续的filter的正常运行。
(2)每个filter在注释中说明白,自己从哪里取的数据,怎么放回去的。
(3)性能允许的情况下,第一个filter把数据从流里读出来,放入responseBody,大家都针对responseBody操作。
(4)需要获取返回内容是,可以通过如下工具方法:
public static String getResponseBody(){
RequestContext ctx = RequestContext.getCurrentContext();
String ret = ctx.getResponseBody();
if(StringUtils.isNotBlank(ret)){
return ret;
}
InputStream is = ctx.getResponseDataStream();
if(is==null){
return null;
}
try {
ByteArrayOutputStream result =new ByteArrayOutputStream();
byte[] buffer =new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
result.write(buffer,0, length);
}
ret = result.toString();
ctx.setResponseBody(ret);
return ret;
}catch (IOException ex) {
// ignore
}finally {
try {
if (is !=null) {
is.close();
}
}
catch (IOException ex) {
}
}
return null;
}
网友评论