SpringCloud Alibaba | 网关(三) : SpringCloudGateway 过滤器获取application/json中body数据
SpringCloudGateway 过滤器获取application/json中body数据
- 一、前言
- 二、通过cachedRequestBodyObject缓存获取
- 三、ServerHttpRequest getBody方法获取
- 四、(* ̄︶ ̄)
一、前言
项目接口需要加解密,就在网关层进行解密操作。那么问题来了怎么在gateway 的filter 中获取 body(application/json)中的数据呢? 经过一顿百度一顿验证发现了两种方式
- 一种是通过cachedRequestBodyObject缓存获取request body信息
- 一种是通过ServerHttpRequest.getBody方法获取body信息
二、通过cachedRequestBodyObject缓存获取
通过cachedRequestBodyObject 获取的话 路由配置需要使用java方式配置
ReadBodyPredicateFactory里面缓存了request body的信息,于是在自定义router中配置了ReadBodyPredicateFactory,然后在filter中通过cachedRequestBodyObject缓存字段获取request body信息。
路由配置
/**
* 路由配置
*
*/
@EnableAutoConfiguration
@Configuration
public class GatewayRouteConfig {
@Resource
private RequestParamDecryptFilter2 paramDecryptFilter;
private static final String PATH_URL = "/UserCenter/**";
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
return builder.routes()
//拦截请求类型为POST Content-Type application/json application/json;charset=UTF-8
.route(r -> r
.header(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_JSON_VALUE + MediaType.APPLICATION_JSON_UTF8_VALUE)
.and()
.method(HttpMethod.POST)
.and()
//TODO 这里是核心,获取缓存中的请求体
.readBody(Object.class, readBody -> {
return true;
})
.and()
.path(PATH_URL)
//把请求体传递给拦截器reqTraceFilter
.filters(f -> {
f.filter(paramDecryptFilter);
return f;
})
//这里换成 nacos 的配置地址
.uri("http://127.0.0.1:8083")).build();
}
}
自定义filter
/**
* 解密操作
*
* @description: 过滤器,
* @modified:
*/
@Slf4j
@Component
public class RequestParamDecryptFilter2 implements GatewayFilter, Ordered {
@Override
public int getOrder() {
return 0;
}
private static final String CACHE_REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Object cachedBody = exchange.getAttribute(CACHE_REQUEST_BODY_OBJECT_KEY);
if (null != cachedBody) {
String bodyStr = cachedBody.toString();
//TODO 这里进行 业务逻辑操作
}
return chain.filter(exchange);
}
}
三、ServerHttpRequest getBody方法获取
通过gateBody()方法获取 需要两个filter
- 第一个filter 进行ServerHttpRequest getBody方法的重写
- 第二个filter 解析body
ServerHttpRequest getBody 重写filter
/**
* ServerHttpRequest getBody 包装Filter
*
* @description: 过滤器,
* @modified:
*/
@Slf4j
@Component
public class ServerHttpReqFilter implements GlobalFilter, Ordered {
/**
* 设置最高优先级 保证在 获取body 之前执行
*
* @return
*/
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
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, HandlerStrategies.withDefaults().messageReaders()).bodyToMono(String.class)
.doOnNext(objectValue -> {
}).then(chain.filter(mutatedExchange));
});
}
}
真正获取body 数据进行 业务逻辑处理的filter
/**
* 解密操作
*
* @description: 过滤器,
* @modified:
*/
@Slf4j
@Component
public class RequestParamDecryptFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return 0;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
log.info("request path :{}", request.getPath());
//TODO 进行Content-type 与 method 判断
StringBuffer paramBuffer = new StringBuffer();
Flux<DataBuffer> body = exchange.getRequest().getBody();
body.subscribe(buffer -> {
//解析body 数据
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
log.info("request charBuffer:{}", charBuffer);
//在这里解密 并 重新塞进去
paramBuffer.append(charBuffer);
});
//获取到最后的body 中的数据
String param = paramBuffer.toString();
log.info("request param :{}", paramBuffer.toString());
//TODO 这里进行业务操作
//......
//重新封装参数 向下传递
DataBuffer bodyDataBuffer = stringBuffer(param);
Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
request = new ServerHttpRequestDecorator(request) {
@Override
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};//封装我们的request
return chain.filter(exchange.mutate().request(request).build());
}
protected DataBuffer stringBuffer(String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}
}
那么为什么要用两个filter?
StringBuffer paramBuffer = new StringBuffer();
Flux<DataBuffer> body = exchange.getRequest().getBody();
body.subscribe(buffer -> {
//解析body 数据
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
log.info("request charBuffer:{}", charBuffer);
//在这里解密 并 重新塞进去
paramBuffer.append(charBuffer);
});
//获取到最后的body 中的数据
String param = paramBuffer.toString();
在以上代码中,当你直接通过body.subscribe 解析数据的时候,你会发现他在filter执行完才会执行body.subscribe()方法内的内容(debug可以试试 在filter执行完才会debug到里边)。
SpringCloudGateWay 是底层是 Spring webflux 是非阻塞线程的,所以当你直接通过body.subscribe() 来解析的话 他还没执行 你就直接用 paramBuffer 来 这个时候获取的是null 所以需要重写 getBody方法。
四、(* ̄︶ ̄)
如果还有其他好的获取方法, 请评论告知一下,共同学习,共同进步。谢谢啦
如果对你有帮助,加个关注把~