一、前言
最近项目中用到SpringCloud Gateway进行网关的开发,我使用的版本是:SpringBoot的2.2.11.RELEASE+SpringCloud的Hoxton.SR9,在自定义过滤器时需要获取ServerHttpRequest中body的数据,发现一直获取不到,经过一番查找和调试,终于解决了这个问题,以下是常见问题和完整解决方案。
二、问题
1、目前局部过滤器获取post方法的body有两种(get获取参数此处不做赘述)
(1)、
Flux<DataBuffer> body = exchange.getRequest().getBody();
body.subscribe(buffer -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
try {
String bodyString = new String(bytes, "utf-8");
System.out.println(bodyString);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
});
(2)、
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
String bodyStr = bodyRef.get();
System.out.println(bodyStr);
但是weflux是非阻塞线程,即body.subscribe内还没有执行完,你已经使用了bodyString.get,导致bodyString获取为null
三、解决办法
1、使用全局过滤器,接收body参数,重写Flux<DataBuffer> getBody()方法,话不多说,直接贴代码:
@Component @Slf4j public class RequestParamGlobalFilter implements GlobalFilter, Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { /** * save request path and serviceId into gateway context */ ServerHttpRequest request = exchange.getRequest(); HttpHeaders headers = request.getHeaders(); // 处理参数 MediaType contentType = headers.getContentType(); long contentLength = headers.getContentLength(); if (contentLength > 0) { if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) { return readBody(exchange, chain); } } return chain.filter(exchange); } /** * default HttpMessageReader */ private static final List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); /** * ReadJsonBody * * @param exchange * @param chain * @return */ private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain) { /** * join the body */ return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bytes); DataBufferUtils.release(dataBuffer); Flux<DataBuffer> cachedFlux = Flux.defer(() -> { DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes); DataBufferUtils.retain(buffer); return Mono.just(buffer); }); /** * repackage ServerHttpRequest */ ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return cachedFlux; } }; /** * mutate exchage with new ServerHttpRequest */ ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build(); /** * read body string with default messageReaders */ return ServerRequest.create(mutatedExchange, messageReaders).bodyToMono(String.class) .doOnNext(objectValue -> { log.debug("[GatewayContext]Read JsonBody:{}", objectValue); }).then(chain.filter(mutatedExchange)); }); } @Override public int getOrder() { return HIGHEST_PRECEDENCE; } }
RequestParamGlobalFilter 这个全局过滤器的目的就是把原有的request请求中的body内容读出来,并且使用ServerHttpRequestDecorator这个请求装饰器对request进行包装,重写getBody方法,并把包装后的请求放到过滤器链中传递下去。这样后面的过滤器中再使用exchange.getRequest().getBody()来获取body时,实际上就是调用的重载后的getBody方法,获取的最先已经缓存了的body数据。这样就能够实现body的多次读取了。
值得一提的是,这个过滤器的order设置的是Ordered.HIGHEST_PRECEDENCE,即最高优先级的过滤器。优先级设置这么高的原因是某些系统内置的过滤器可能也会去读body,这样就会导致我们自定义过滤器中获取body的时候报body只能读取一次这样的错误:
- java.lang.IllegalStateException: Only one connection receive subscriber allowed.
所以,必须把CacheBodyGlobalFilter的优先级设到最高。
2、此时再使用局部过滤器获取参数进行统一鉴权就可以了
return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest(); if (request.getMethod() == HttpMethod.POST) { AtomicReference<String> requestBody = new AtomicReference<>(""); RecorderServerHttpRequestDecorator requestDecorator = new RecorderServerHttpRequestDecorator(request); Flux<DataBuffer> body = requestDecorator.getBody(); body.subscribe(buffer -> { CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer()); requestBody.set(charBuffer.toString()); }); //获取body参数 JSONObject requestParams = JSONObject.parseObject(requestBody.get()); //TODO 拿到参数之后做你自己的校验 } return chain.filter(exchange); };
public class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator { private final List<DataBuffer> dataBuffers = new ArrayList<>(); public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) { super(delegate); super.getBody().map(dataBuffer -> { dataBuffers.add(dataBuffer); return dataBuffer; }).subscribe(); } @Override public Flux<DataBuffer> getBody() { return copy(); } private Flux<DataBuffer> copy() { return Flux.fromIterable(dataBuffers) .map(buf -> buf.factory().wrap(buf.asByteBuffer())); } }
Springcloud Gateway 搭建统一网关,获取不到body参数的问题就完美解决了~有问题欢迎评论,感谢观看