SpringCloud Gateway读取Request Body方式
Gateway读取Request Body
我们使用SpringCloud Gateway做微服务网关的时候,经常需要在过滤器Filter中读取到Post请求中的Body内容进行日志记录、签名验证、权限验证等操作。我们知道,Request的Body是只能读取一次的,如果直接通过在Filter中读取,而不封装回去回导致后面的服务无法读取数据。
SpringCloud Gateway内部提供了一个断言工厂类ReadBodyPredicateFactory,这个类实现了读取Request的Body内容并放入缓存,我们可以通过从缓存中获取body内容来实现我们的目的。
分析ReadBodyPredicateFactory
public AsyncPredicate<ServerWebExchange> applyAsync(ReadBodyPredicateFactory.Config config) {
return (exchange) -> {
Class inClass = config.getInClass();
Object cachedBody = exchange.getAttribute("cachedRequestBodyObject");
if (cachedBody != null) {
try {
boolean test = config.predicate.test(cachedBody);
exchange.getAttributes().put("read_body_predicate_test_attribute", test);
return Mono.just(test);
} catch (ClassCastException var7) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Predicate test failed because class in predicate does not match the cached body object", var7);
}
return Mono.just(false);
}
} else {
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap((dataBuffer) -> {
DataBufferUtils.retain(dataBuffer);
final Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
return Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()));
});
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
return ServerRequest.create(exchange.mutate().request(mutatedRequest).build(), messageReaders).bodyToMono(inClass).doOnNext((objectValue) -> {
exchange.getAttributes().put("cachedRequestBodyObject", objectValue);
exchange.getAttributes().put("cachedRequestBody", cachedFlux);
}).map((objectValue) -> {
return config.predicate.test(objectValue);
});
});
}
};
}
通过查看ReadBodyPredicateFactory内部实现,我们可以看到,该工厂类将request body内容读取后存放在 exchange的cachedRequestBodyObject中。
那么我们可以通过代码:
exchange.getAttribute(“cachedRequestBodyObject”); //将body内容取出来
知道如何取body内容后,我们只需将该工厂类注册到yml配置文件中的predicates,然后从Filter中获取即可。
配置ReadBodyPredicateFactory
查看ReadBodyPredicateFactory关于配置的代码:
public <T> ReadBodyPredicateFactory.Config setPredicate(Class<T> inClass, Predicate<T> predicate) {
this.setInClass(inClass);
this.predicate = predicate;
return this;
}
配置该工厂类需要两个参数:
inClass
:接收body内容的对象Class,我们用字符串接收,配置String即可。Predicate
:Predicate的接口实现类,我们自定义一个Predicate的实现类即可。
自定义Predicate实现,并注册Bean。
@Bean
public Predicate bodyPredicate(){
return new Predicate() {
@Override
public boolean test(Object o) {
return true;
}
};
}
两个参数都有了,直接在yml中配置:
predicates:
- Path=/card/api
@Bean
public ReadBodyGatewayFilterFactory readBodyGatewayFilterFactory() {
return new ReadBodyGatewayFilterFactory();
}
到此,我们的Filter类也可以在yml配置文件中直接配置使用了。
完整的yml配置
- id: body_route #读取post中的body路由
order: 5
uri: lb://API-CARD
filters:
- ReadBody=true #使用自定义的过滤器工厂类,读取request body内容
predicates:
- Path=/card/api
@Slf4j
@Component
public class CacheBodyGlobalFilter implements Ordered, GlobalFilter {
// public static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (exchange.getRequest().getHeaders().getContentType() == null) {
return chain.filter(exchange);
} else {
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> {
DataBufferUtils.retain(dataBuffer);
Flux<DataBuffer> cachedFlux = Flux
.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
//exchange.getAttributes().put(CACHE_REQUEST_BODY_OBJECT_KEY, cachedFlux);
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
CacheBodyGlobalFilter这个全局过滤器的目的就是把原有的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.
at reactor.ipc.netty.channel.FluxReceive.startReceiver(FluxReceive.java:279)
at reactor.ipc.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:129)
at
所以,必须把CacheBodyGlobalFilter的优先级设到最高。
在自定义的过滤器中尝试获取body中的数据
package com.cloudpath.iam.gateway.customerfilter;
import com.cloudpath.iam.gateway.utils.FilterRequestResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import java.util.Arrays;
import java.util.List;
@Component
@Slf4j
public class TestGatewayFilterFactory extends AbstractGatewayFilterFactory<TestGatewayFilterFactory.Config> {
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("enabled");
}
public TestGatewayFilterFactory() {
super(Config.class);
log.info("Loaded TestGatewayFilterFactory");
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
if (!config.isEnabled()) {
return chain.filter(exchange);
}
if (null != exchange) {
ServerHttpRequest httpRequest = exchange.getRequest();
try {
Flux<DataBuffer> dataBufferFlux = httpRequest.getBody();
//获取body中的数据
String body = FilterRequestResponseUtil.resolveBodyFromRequest(dataBufferFlux);
log.info("body:{}",body);
} catch (Exception e) {
log.error("异常:",e);
return chain.filter(exchange);
}
}
return chain.filter(exchange);
};
}
public static class Config {
private boolean enabled;
public Config() {
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
}
解析body的工具类
package com.cloudpath.iam.gateway.utils;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import reactor.core.publisher.Flux;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class FilterRequestResponseUtil {
public static String resolveBodyFromRequest( Flux<DataBuffer> body){
AtomicReference<String> bodyRef = new AtomicReference<>();
// 缓存读取的request body信息
body.subscribe(dataBuffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());
DataBufferUtils.release(dataBuffer);
bodyRef.set(charBuffer.toString());
});
//获取request body
return bodyRef.get();
}
public static String resolveBodyFromRequest2( Flux<DataBuffer> body){
StringBuilder sb = new StringBuilder();
body.subscribe(buffer -> {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
sb.append(bodyString);
});
return formatStr(sb.toString());
}
private static String formatStr(String str){
if (str != null && str.length() > 0) {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(str);
return m.replaceAll("");
}
return str;
}
}
解析body的内容,网上普遍是上面的两种方式,亲测resolveBodyFromRequest方法解析body中的数据,没有1024字节的限制。
ps:我传的参数有1万多字节。。。。。。。
大家可以按需所选。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341