@Component
@Slf4j
@AllArgsConstructor
public class HttpPostBodyFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String method = request.getMethodValue(); String contentType = request.getHeaders().getFirst("Content-Type"); if ("POST".equals(method) && contentType.startsWith("multipart/form-data")){ return DataBufferUtils.join(exchange.getRequest().getBody()) .flatMap(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); try { String bodyString = new String(bytes, "utf-8"); log.info(bodyString); exchange.getAttributes().put("POST_BODY",bodyString); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } DataBufferUtils.release(dataBuffer); Flux<DataBuffer> cachedFlux = Flux.defer(() -> { DataBuffer buffer = exchange.getResponse().bufferFactory() .wrap(bytes); return Mono.just(buffer); }); ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return cachedFlux; } }; return chain.filter(exchange.mutate().request(mutatedRequest) .build()); }); } return chain.filter(exchange); } @Override public int getOrder() { return -200; } }
主要思路就是在優先級最高的過濾器里面,CacheBodyGlobalFilter這個全局過濾器的目的就是把原有的request請求中的body內容讀出來,並且使用ServerHttpRequestDecorator這個請求裝飾器對request進行包裝,重寫getBody方法,並把包裝后的請求放到過濾器鏈中傳遞下去。這樣后面的過濾器中再使用exchange.getRequest().getBody()來獲取body時,實際上就是調用的重載后的getBody方法,獲取的最先已經緩存了的body數據。這樣就能夠實現body的多次讀取了。
過濾器優先級不一定是最高,但是要在要獲取body之前執行,然后后面在身份鑒定的等過濾器里面,獲取到body
@Component @Slf4j public class WrapperResponseGlobalFilter implements GlobalFilter, Ordered { @Override public int getOrder() { return -2; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest serverHttpRequest = exchange.getRequest(); ServerHttpResponse originalResponse = exchange.getResponse(); //如果是post請求,將請求體取出來,再寫入 HttpMethod method = serverHttpRequest.getMethod(); //請求參數,post從請求里獲取請求體 String requestBodyStr = HttpMethod.POST.equals(method) ? resolveBodyFromRequest(serverHttpRequest) : null; DataBufferFactory bufferFactory = originalResponse.bufferFactory(); ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) { @Override public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) { if (body instanceof Flux) { Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body; return super.writeWith(fluxBody.buffer().map(dataBuffers -> {//解決返回體分段傳輸 StringBuffer stringBuffer = new StringBuffer(); dataBuffers.forEach(dataBuffer -> { byte[] content = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(content); DataBufferUtils.release(dataBuffer); try { stringBuffer.append(new String(content, "utf-8")); } catch (Exception e) { log.error("--list.add--error", e); } }); String result = stringBuffer.toString(); //TODO,result就是response的值,想修改、查看就隨意而為了 String url = serverHttpRequest.getPath().toString(); String urlParams = UrlUtil.getParamsByMap(serverHttpRequest.getQueryParams().toSingleValueMap()); JSONObject jsonObject = JSONObject.parseObject(result); log.info("請求長度:" + StringUtils.length(requestBodyStr) + ",返回data長度:" + StringUtils.length(jsonObject.getString("data"))); log.info("請求地址:【{}】請求參數:GET【{}】|POST:【\n{}\n】,響應數據:【\n{}\n】", url, urlParams, requestBodyStr, result); byte[] uppedContent = new String(result.getBytes(), Charset.forName("UTF-8")).getBytes(); originalResponse.getHeaders().setContentLength(uppedContent.length); return bufferFactory.wrap(uppedContent); })); } // 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()); } /** * 從Flux<DataBuffer>中獲取字符串的方法 * * @return 請求體 */ private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) { //獲取請求體 Flux<DataBuffer> body = serverHttpRequest.getBody(); AtomicReference<String> bodyRef = new AtomicReference<>(); body.subscribe(buffer -> { CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); DataBufferUtils.release(buffer); bodyRef.set(charBuffer.toString()); }); //獲取request body return bodyRef.get(); } }
這里可以把請求地址、參數、body、響應數據一起打印出來,測試的post請求
請求參數有56.97kb,后端order-center的接收打印,把整個數據完全接收並返回出去
網關的打印請求參數數據
再來一個get請求的
其他的put、delete等請求均試過正常請求
用postman的壓測結果和對比,數據都完全正常,並且能通過json格式化,說明數據格式也保持了一致
代碼還有很多可以優化改進的地方,根據自己的也無需求來,比如簽名、token等統一校驗處理