springcloud2.3.7+Hoxton.SR6 gateway获取post请求body
网上找了很多解决办法除了使用RouteLocator路由定位,都无法获取,但是项目路由比较多,使用RouteLocator不是很理想。其他的解决办法,根本获取不到内容。例:
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Flux<DataBuffer> body= exchange.getRequest().getBody();
byte[] b=resolveBody(body);
//省略
return chain.filter(exchange);
}
private byte[] resolveBody(Flux<DataBuffer> body) {
AtomicReference<byte[]> referenceBody = new AtomicReference<>();
if (body == null) {
return new byte[0];
}
//此处获取为空 不会进入 ,但是2.0.4.RELEASE+Finchley.SR1 中是可以获取到请求体
body.subscribe(buffer -> {
try {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
referenceBody.set(bytes);
} catch (Exception e) {
log.debug("解析请求体异常", e);
} finally {
if (buffer != null) {
DataBufferUtils.release(buffer);
log.debug("release buffer resolveBody");
}
}
});
return referenceBody.get();
}
经历种种,终于找到解决方案:需要增加全局过滤器来处理。
全局过滤器的目的就是把原有的request请求中的body内容读出来,并且使用ServerHttpRequestDecorator这个请求装饰器对request进行包装,重写getBody方法,并把包装后的请求放到过滤器链中传递下去。
代码如下:
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 解决无法通过Flux<DataBuffer> body 获取请求体
*
* @author luozy
* Created time 2021/8/24
*/
@Component
public class CacheBodyGlobalFilter implements Ordered, GlobalFilter {
@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;
}
};
//重新封装求情 否则会出现Only one connection receive subscriber allowed错误
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
这样在之后过滤器代码里面就可以获取到body了;
private byte[] resolveBody(Flux<DataBuffer> body) {
AtomicReference<byte[]> referenceBody = new AtomicReference<>();
if (body == null) {
return new byte[0];
}
body.subscribe(buffer -> {
try {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
referenceBody.set(bytes);
} catch (Exception e) {
log.debug("解析请求体异常", e);
}
});
return referenceBody.get();
}
其他的读取方式
public static String resolveBodyFromRequest( Flux body){
AtomicReference 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();
}
一定要注意将CacheBodyGlobalFilter类顺序放在最高。
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
参考:http://blog.itblood.com/856.html
这里我将使用路由的方式代码贴一下,有需求使用这种方式的可以借鉴使用
@EnableAutoConfiguration
@Configuration
@Slf4j
public class MyRoutesTest {
@Autowired
private RequestFilter requestFilter;
private static final String SERVICE = "/v2/**";
private static final String URI = "http://127.0.0.1:8080";
@Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
//x-www-form-urlencoded 和 json 处理不同所以写2个post路由
RouteLocatorBuilder.Builder routes = builder.routes();
RouteLocatorBuilder.Builder serviceProvider = routes
.route("route1",
r -> r
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.and()
.method(HttpMethod.POST)
.and()
.readBody(String.class, readBody -> {
log.info("application/x-www-form-urlencoded,body:{}", readBody);
return true;
})
.and()
.path(SERVICE)
.filters(f -> {
f.filter(requestFilter);
return f;
})
.uri(URI))
.route("route2",
r -> r
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.and()
.method(HttpMethod.POST)
.and()
.readBody(Object.class, readBody -> {
log.info("application/json,body:{}", readBody);
return true;
})
.and()
.path(SERVICE)
.filters(f -> {
f.filter(requestFilter);
return f;
})
.uri(URI));
// get的如下
/**
.route("routeGet",
r -> r
.method(HttpMethod.GET)
.and()
.path(SERVICE)
.filters(f -> {
f.filter(requestFilter);
return f;
})
.uri(URI))*/
RouteLocator routeLocator = serviceProvider.build();
return routeLocator;
}
}
后续获取body代码
@Log4j2
@Component
public class GatewayOauthFilter implements GatewayFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Object body= exchange.getAttribute("cachedRequestBodyObject");
log.info("body:{}", body);
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
}));
}
@Override
public int getOrder() {
return -5;
}
}