Spring Cloud Gateway 攔截響應問題分析(數據截斷問題)

Spring Cloud Gateway是Spring 官方基於Spring 5.0、Spring Boot 2.0和Project Reactor等技術開發的網關,Spring Cloud Gateway旨在為微服務架構提供一種簡單有效的、統一的 API 路由管理方式。

Spring Cloud Gateway 作為 Spring Cloud 生態系中的網關,其目標是替代 Netflix Zuul,它不僅提供統一的路由方式,並且基於Filter鏈的方式提供瞭網關基本的功能,如:安全、監控/埋點和限流等。

Spring Cloud Gateway依賴Spring Boot和Spring WebFlux,基於Netty 運行。不能在傳統的 servlet 容器中工作也不能構建成war包。

關於Spring Cloud Gateway 核心概念

1、Route

Route 是網關的基礎元素,由 ID、目標 URI、斷言、過濾器組成。當請求到達網關時,由 Gateway HandlerMapping 通過斷言進行路由匹配(Mapping),斷言為真時匹配到路由。

2、Predicate

Predicate 是 Java 8 中提供的一個函數。輸入類型是 Spring Framework ServerWebExchange。它允許開發人員匹配來自 HTTP 的請求,例如請求頭或者請求參數。簡單來說它就是匹配條件。

3、Filter

Filter是Gateway 中的過濾器,可以在請求發出前後進行一些業務上的處理。

Spring Cloud Gateway 攔截響應

最近因為上鏈路追蹤後發現如果在服務層將異常攔截掉,在鏈路追蹤界面上就不會顯示異常鏈路信息,除瞭服務異常意外,系統的異常不會觸發鏈路error,所以對服務層做瞭個變更,將所有未處理異常直接捕獲後統一封裝完拋出,這個時候就需要在網關層統一處理那麼網關需要對響應數據進行攔截如果是 9999錯誤碼,則封裝後返回,如果是其它響應碼或者其它數據直接返回。

起初寫法測試後發現數據過長時被截斷。

 return super.writeWith(fluxBody.map(dataBuffer -> {
	
 
	byte[] content = new byte[dataBuffer.readableByteCount()];
	
	dataBuffer.read(content);
	// 釋放掉內存
	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後發現存在一個DataBufferFactory可以一次性join完所有數據後拼接就不會產生截斷問題。

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);
						// 釋放掉內存
						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());
	    }
 
}

經過如上修改後鏈路追蹤可以實現哪個服務出錯就立馬出現鏈路異常馬上可以定位到哪個服務出現瞭未處理異常

到此這篇關於Spring Cloud Gateway 攔截響應(數據截斷問題)的文章就介紹到這瞭,更多相關Spring Cloud Gateway 攔截響應內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: