springcloud gateway 获取post请求体Json分段导致不全的解决方案
开发版本
springboot 2.0.8.RELEASE + springcloud Finchley.SR2 + spring cloud gateway
前端客户端采用post发送请求,content_type: application/json,
spring cloud gateway需要从request的中取出body进行网关的鉴权处理,然后把处理之后的数据重新封装到body中转发给下游业务系统,
最之前的方式采用的是以下方式读取请求体:
AtomicReference<String> bodyRef = new AtomicReference<>();//缓存读取的request body信息
Flux<DataBuffer> fluxBody = exchange.getRequest().getBody();
fluxBody.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
String bodyStr = bodyRef.get();
偶尔前端提出验签失败,检查上送请求体报文时发现错误:网关获取的body不完整。
尝试了网上的写法来获取body,于是改成了以下的写法来亲自验证,偶尔还是发现网关获取的body体不完整,说明此方法还是不行,有坑。
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest){
//获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
StringBuilder sb = new StringBuilder();
body.subscribe(buffer -> {
List<String> list = Lists.newArrayList();
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
String bodyString = new String(bytes, StandardCharsets.UTF_8);
sb.append(bodyString);
});
return sb.toString();
}
继续查询相关资料,有几篇文件介绍了要解决获取body不完整的情况得代用code路由的方式来配置,而不能采用yml,于是继续尝试。
@Bean
public RouteLocator tpauditRoutes(RouteLocatorBuilder builder) {
return builder.routes()
.route(r ->r
.readBody(String.class, requestBody -> {
//log.info("requestBody is {}", requestBody);
// 这里 r.readBody做了一个前置语言,这样就可以在filter中通过exchange.getAttribute("cachedRequestBodyObject"); 获取body体
return true;
}).and()
.path("/gw/xxxdmin/**")
.filters(f -> f.stripPrefix(1)
.hystrix(h -> h.setName("Hystrix").setFallbackUri("forward:/timeoutfallback")))
.uri("lb://ADMIN/")
)
.route(r ->r
.readBody(String.class, requestBody -> {
//log.info("requestBody is {}", requestBody);
// 这里 r.readBody做了一个前置语言,这样就可以在filter中通过exchange.getAttribute("cachedRequestBodyObject"); 获取body体
return true;
}).and()
.path("/gw/xxxapp/**")
.filters(f -> f.stripPrefix(1)
.hystrix(h -> h.setName("Hystrix").setFallbackUri("forward:/timeoutfallback")))
.uri("lb://APP/")
)
.build();
}
然后移除yml中配置的路由:
routes:
- id: xxxAPP
uri: lb://xxxAPP
predicates:
- Path=${server.servlet.context-path}/xxxapp/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 #允许用户每秒处理多少个请求
redis-rate-limiter.burstCapacity: 1 #令牌桶的容量,允许在一秒钟内完成的最大请求数
key-resolver: "#{@addressKeyResolver}" #使用SpEL按名称引用bean
# 降级配置
- name: Hystrix
args:
name: default
fallbackUri: forward:/timeoutfallback
业务处理过滤器中通过String bodyStr = exchange.getAttribute("cachedRequestBodyObject");方法获取请求体,
最终问题得以解决。
但本人喜欢采用yml配置文件的形式来做路由,所以对于yml路由的配置,此坑还需要进一步的排查解决。