美文网首页
Zuul SendResponseFilter 代码阅读笔记

Zuul SendResponseFilter 代码阅读笔记

作者: liuhailong | 来源:发表于2018-06-12 16:17 被阅读0次

请直接看结论。

一、 代码结构

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;

}

相关文章

网友评论

      本文标题:Zuul SendResponseFilter 代码阅读笔记

      本文链接:https://www.haomeiwen.com/subject/hlhreftx.html