spring-cloud-gateway获取post请求body参数以及响应数据
@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等统一校验处理
学习时的痛苦是暂时的 未学到的痛苦是终生的