Zuul SendResponseFilter 代碼閱讀筆記

請直接看結(jié)論。

一、 代碼結(jié)構(gòu)

SendResponseFilter是Zuul的最后一個Filter,負責(zé)最終響應(yīng)結(jié)果的渲染輸出。

其run方法異常簡介,如下:

@Override

public Object run() {

try {

addResponseHeaders();

writeResponse();

}

catch (Exception ex) {

ReflectionUtils.rethrowRuntimeException(ex);

}

return null;

}

只做兩件事情:一、添加響應(yīng)頭;二、渲染響應(yīng)結(jié)果到響應(yīng)體。

addResponseHeaders()方法較簡單,大家可自行閱讀。

二、 writeResponse

下面對writeResponse()進行分析確認,以便了解zuul內(nèi)部機制,便于繞開各種小坑。

如下:

private void writeResponse()throws Exception {

RequestContext context = RequestContext.getCurrentContext();

// there is no body to send

// getResponseBody是從上下文取返回文本,這也是推薦做法。

// getResponseDataStream直接讀輸出流

// 這兩個是SendResponseFilter獲取輸出內(nèi)容的兩種渠道

? if (context.getResponseBody() ==null

? ? ? ? && context.getResponseDataStream() ==null) {

return;

}

// 獲取輸出對象

// context.getResponse() 這個方法普通filter也可以調(diào)用,個人認為不夠安全,容易增大各filter協(xié)調(diào)的難度,引入各種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) {

}

}

}

三、結(jié)論

開發(fā)自定義Filter時,如果涉及對輸出內(nèi)容的控制或改造,需要了解Zuul是最終如何處理內(nèi)容的。

Zuul從上下文中通過getResponseBody或getResponseDataStream讀取返回內(nèi)容,優(yōu)先采用getResponseBody的內(nèi)容。

因此,一旦我們采用 RequestContext.getCurrentContext().setResponseBody("haha"); 的方式設(shè)置返回內(nèi)容,responseDataStream中即使有內(nèi)容也會被忽略。

開發(fā)自定義Filter時,如果需要獲取返回內(nèi)容,加工,再放回,策略如下:

1、 如果其他filter已經(jīng)將內(nèi)容放到 responseBody 中,取出、處理、再放回即可。

String s = RequestContext.getCurrentContext().getResponseBody();

s +="mycustom";

RequestContext.getCurrentContext().setResponseBody(s);

2、 如果 RequestContext.getCurrentContext().getResponseBody() 獲取不到,則返回內(nèi)容在responseDataStream中。

流中的內(nèi)容只可以讀一次,可以通過wrapper的方式,但操作復(fù)雜。

居中的方案是讀出來,處理,放入responseBody中。

這時候,可能后面的filter,期待從responseDataStream中讀數(shù)據(jù),很遺憾,它讀不到了, 各fitler之間的協(xié)調(diào)確實麻煩。

3、 既然了解了Zuul Filter的這個特點,大家可以約定,涉及對response內(nèi)容修改的情況下:

(1)每個人要加Filter的時候,一定要加在最后(filterOrder最大),這樣可以避免影響其他的Filter,如果確實邏輯上有限制,需要確保后續(xù)的filter的正常運行。

(2)每個filter在注釋中說明白,自己從哪里取的數(shù)據(jù),怎么放回去的。

(3)性能允許的情況下,第一個filter把數(shù)據(jù)從流里讀出來,放入responseBody,大家都針對responseBody操作。

(4)需要獲取返回內(nèi)容是,可以通過如下工具方法:

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;

}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,553評論 19 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong閱讀 22,940評論 1 92
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,679評論 18 399
  • 好久沒有早起了,記得這學(xué)期剛開始的時候,立志要六點半就起床,結(jié)果沒堅持幾天,就被早晨越來越低的氣氛“逼”回了被窩兒...
    星酉閱讀 805評論 0 0
  • 日更第三天,加油! 不知道你一般怎么度過周末,或者周末讓你感到最放松的事情是什么呢? 有人熬夜刷劇, 有人約好友敘...
    青衣雨翼_shape閱讀 528評論 0 0

友情鏈接更多精彩內(nèi)容