在構建微服務架構時,Spring Cloud Gateway作為一個重要的微服務網關,經常需要在過濾器(Filter)中對POST請求的Body內容進行操作,如日志記錄、簽名驗證和權限驗證等。然而,由于Request的Body只能讀取一次,如果直接在過濾器中讀取而不進行封裝,可能導致后續服務無法獲取數據。
網上搜這個問題的解決方案,大多數文章都是告訴你寫一個Filter將Request的Body緩存起來。這種方法確實可以,只不過需要對代碼經過充分壓力測試,否則很有可能出現如下所示的堆外內存溢出問題。
reactor.NETty.ReactorNetty$InternalNettyException: io.netty.util.internal.OutOfDirectMemoryError:fAIled to allocate
實際上,Spring Cloud Gateway已經內置了AdaptCachedBodyGlobalFilter過濾器,它在Exchange中巧妙地緩存了Request的Body,避免了直接讀取導致的一系列問題。這種方式更為穩妥,避免了潛在的內存溢出風險。
圖片
在需要獲取Body的地方,我們只需要通過以下方法即可:
DataBuffer body = exchange.getAttributeOrDefault("cachedRequestBody", null);
String bodyStr = body.toString(StandardCharsets.UTF_8);
只不過通過源碼可以看出,緩存RequestBody需要路由被標記為需要緩存,也就是this.routesToCache.containsKey(rouceId)方法必須返回true。
AdaptCachedBodyGlobalFilter會監聽EnableBodyCachingEvent事件,當發布該事件時就將RouteId放入routesToCache中。為了方便使用,我們可以編寫一個配置類,在初始化時發布EnableBodyCachingEvent事件,將所有路由都啟用緩存功能。
@Configuration(proxyBeanMethods = false)
@Slf4j
public class EnableCachedBodyConfiguration {
@Resource
private ApplicationEventPublisher publisher;
@Resource
private GatewayProperties gatewayProperties;
@PostConstruct
public void init() {
gatewayProperties.getRoutes().forEach(routeDefinition -> {
// 對 spring cloud gateway 路由配置中的每個路由都啟用 AdaptCachedBodyGlobalFilter
EnableBodyCachingEvent enableBodyCachingEvent = new EnableBodyCachingEvent(new Object(), routeDefinition.getId());
publisher.publishEvent(enableBodyCachingEvent);
});
}
}
通過這種方式,我們可以更加方便地處理POST請求的Body內容,避免了一些常見的問題。在實際應用中,考慮到穩定性和性能,這種解決方案提供了一種更為可靠的選擇。