Spring Cloud Gateway 攔截響應問題分析(數(shù)據(jù)截斷問題)
Spring Cloud Gateway是Spring 官方基于Spring 5.0、Spring Boot 2.0和Project Reactor等技術(shù)開發(fā)的網(wǎng)關(guān),Spring Cloud Gateway旨在為微服務架構(gòu)提供一種簡單有效的、統(tǒng)一的 API 路由管理方式。
Spring Cloud Gateway 作為 Spring Cloud 生態(tài)系中的網(wǎng)關(guān),其目標是替代 Netflix Zuul,它不僅提供統(tǒng)一的路由方式,并且基于Filter鏈的方式提供了網(wǎng)關(guān)基本的功能,如:安全、監(jiān)控/埋點和限流等。
Spring Cloud Gateway依賴Spring Boot和Spring WebFlux,基于Netty 運行。不能在傳統(tǒng)的 servlet 容器中工作也不能構(gòu)建成war包。
關(guān)于Spring Cloud Gateway 核心概念
1、Route
Route 是網(wǎng)關(guān)的基礎(chǔ)元素,由 ID、目標 URI、斷言、過濾器組成。當請求到達網(wǎng)關(guān)時,由 Gateway HandlerMapping 通過斷言進行路由匹配(Mapping),斷言為真時匹配到路由。
2、Predicate
Predicate 是 Java 8 中提供的一個函數(shù)。輸入類型是 Spring Framework ServerWebExchange。它允許開發(fā)人員匹配來自 HTTP 的請求,例如請求頭或者請求參數(shù)。簡單來說它就是匹配條件。
3、Filter
Filter是Gateway 中的過濾器,可以在請求發(fā)出前后進行一些業(yè)務上的處理。
Spring Cloud Gateway 攔截響應
最近因為上鏈路追蹤后發(fā)現(xiàn)如果在服務層將異常攔截掉,在鏈路追蹤界面上就不會顯示異常鏈路信息,除了服務異常意外,系統(tǒng)的異常不會觸發(fā)鏈路error,所以對服務層做了個變更,將所有未處理異常直接捕獲后統(tǒng)一封裝完拋出,這個時候就需要在網(wǎng)關(guān)層統(tǒng)一處理那么網(wǎng)關(guān)需要對響應數(shù)據(jù)進行攔截如果是 9999錯誤碼,則封裝后返回,如果是其它響應碼或者其它數(shù)據(jù)直接返回。
起初寫法測試后發(fā)現(xiàn)數(shù)據(jù)過長時被截斷。
return super.writeWith(fluxBody.map(dataBuffer -> { byte[] content = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(content); // 釋放掉內(nèi)存 DataBufferUtils.release(dataBuffer); String str = new String(content, Charset.forName("UTF-8")); originalResponse.getHeaders().setContentLength(str.getBytes().length); log.error("gateway catch service exception error:"+ str); JsonResult result = new JsonResult(); result.setCode(ErrorCode.SYS_EXCEPTION.getCode()); result.setMessage(ErrorCode.SYS_EXCEPTION.getMsg()); return bufferFactory.wrap(str.getBytes()); }));
查詢api后發(fā)現(xiàn)存在一個DataBufferFactory可以一次性join完所有數(shù)據(jù)后拼接就不會產(chǎn)生截斷問題。
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
于是修改代碼如下
package com.server.gateway.filters; import java.nio.charset.Charset; import java.util.concurrent.atomic.AtomicReference; import org.apache.http.protocol.HTTP; import org.reactivestreams.Publisher; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferFactory; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.core.io.buffer.DefaultDataBufferFactory; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponseDecorator; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import com.framework.common.enums.ErrorCode; import com.framework.common.web.JsonResult; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @Component @Slf4j public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered{ @Override public int getOrder() { // -1 is response write filter, must be called before that return -2; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpResponse originalResponse = exchange.getResponse(); DataBufferFactory bufferFactory = originalResponse.bufferFactory(); ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { AtomicReference<String> bodyRef = new AtomicReference<>(); if (body instanceof Flux) { Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body; return super.writeWith(fluxBody.buffer().map(dataBuffers -> { DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory(); DataBuffer join = dataBufferFactory.join(dataBuffers); byte[] content = new byte[join.readableByteCount()]; join.read(content); // 釋放掉內(nèi)存 DataBufferUtils.release(join); String str = new String(content, Charset.forName("UTF-8")); originalResponse.getHeaders().setContentLength(str.getBytes().length); log.error("gateway catch service exception error:"+ str); JsonResult result = new JsonResult(); result.setCode(ErrorCode.SYS_EXCEPTION.getCode()); result.setMessage(ErrorCode.SYS_EXCEPTION.getMsg()); return bufferFactory.wrap(str.getBytes()); })); } // if body is not a flux. never got there. return super.writeWith(body); } }; // replace response with decorator return chain.filter(exchange.mutate().response(decoratedResponse).build()); } }
經(jīng)過如上修改后鏈路追蹤可以實現(xiàn)哪個服務出錯就立馬出現(xiàn)鏈路異常馬上可以定位到哪個服務出現(xiàn)了未處理異常
到此這篇關(guān)于Spring Cloud Gateway 攔截響應(數(shù)據(jù)截斷問題)的文章就介紹到這了,更多相關(guān)Spring Cloud Gateway 攔截響應內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決spirngboot連接redis報錯:READONLY?You?can‘t?write?against?
docker部署的redis,springboot基本每天來連redis都報錯:READONLY?You?can't?write?against?a?read?only?replica,重啟redis后,可以正常連接。但是每天都重啟redis,不現(xiàn)實,也很麻煩,今天給大家分享解決方式,感興趣的朋友一起看看吧2023-06-06已有的springcloud+mybatis項目升級為mybatis-plus的方法
這篇文章主要介紹了已有的springcloud+mybatis項目升級為mybatis-plus,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-03-03ShardingSphere數(shù)據(jù)庫讀寫分離算法及測試示例詳解
這篇文章主要為大家介紹了ShardingSphere數(shù)據(jù)庫讀寫分離算法及測試示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03java.lang.FileNotFoundException 異常的正確解決方法(親測有效)
java.io.FileNotFoundException是一個在文件操作過程中常見的異常,它屬于IOException的一個子類,這篇文章主要介紹了java.lang.FileNotFoundException 異常的正確解決方法(親測有效),需要的朋友可以參考下2024-01-01反射機制:getDeclaredField和getField的區(qū)別說明
這篇文章主要介紹了反射機制:getDeclaredField和getField的區(qū)別說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06Spring的Aware接口實現(xiàn)及執(zhí)行順序詳解
這篇文章主要為大家介紹了Spring的Aware接口實現(xiàn)及執(zhí)行順序詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12