spring-cloud-gateway獲取post請求body引數以及響應資料
阿新 • • 發佈:2020-11-24
@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等統一校驗處理