spring cloud gateway - 日志
spring cloud gateway - 日志
实现日志
前提:spring cloud gateway是基于webflux的项目,所以不能像使用spring mvc一样直接获取request body。
参考博客:
下面会讲述两种获取request body的方式(对应不同的版本)
第一种:重新构造再转发
如果我们在spring cloud gateway 封装之前读取了一次request body,比如打印request body日志,在下游获取数据的时候会出现错误:[spring cloud] [error] java.lang.IllegalStateException: Only one connection receive subscriber allowed. 因为request body只能读取一次,它是属于消费类型的。
实现思路为:
首先读取原请求的数据,然后构造一个新的请求,将原请求的数据封装到新的请求中,然后再转发出去。
这种方法在spring-boot-starter-parent 2.0.6.RELEASE + Spring Cloud Finchley.SR2 body 中生效
但是在spring-boot-starter-parent 2.1.0.RELEASE + Spring Cloud Greenwich.M3 body 中不生效,总是为空
重写的拦截方法filter
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
Route gatewayUrl = exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
URI uri = gatewayUrl.getUri();
ServerHttpRequest request = (ServerHttpRequest)exchange.getRequest();
String URIPath = request.getURI().toString();
String path = request.getPath().value();
String method = request.getMethodValue();
String instance = uri.getAuthority();
HttpHeaders header = request.getHeaders();
//缓存读取的request body信息
AtomicReference<String> bodyRef = new AtomicReference<>();
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();
//获取request body
log.info("请求体参数:{}",bodyStr);
//封装新的request
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
request = new ServerHttpRequestDecorator(request){
@Override
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};
return chain.filter(exchange.mutate().request(request).build());
}
缓存body的方法
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;
}
第二种:获取缓存里面的body
这种方式就是解决在spring-boot-starter-parent 2.1.0.RELEASE + Spring Cloud Greenwich.M3 body 中不生效,总是为空的问题
实现思路:
在filter中通过cachedRequestBodyObject缓存字段获取request body信息,这种解决,一不会带来重复读取问题,二不会带来requestbody取不全问题。三在低版本的Spring Cloud Finchley.SR2也可以运行。
路由配置:
在配置中添加Predicate方法readBody(Object.class,requestBody -> true)
@Bean
public RouteLocator routeLocatorPermission(RouteLocatorBuilder builder) {
return builder.routes()
.route(PERMISSION_ROUTE_ID,
r -> r.path(PERMISSION_ROUTE_PATH).and()
.readBody(Object.class,requestBody -> true)
.filters(f -> f
.stripPrefix(3)
.hystrix(config -> config.setName("hystrixName").setFallbackUri("forward:/fallback"))
.requestRateLimiter(config -> config.setKeyResolver(ipKeyResolver).setRateLimiter(myRedisRateLimiter))
.filter(oauthFilter)
)
.uri("lb://BOSS-BES-PERMISSION")).build();
}
配置日志拦截
可以通过Object requestBody = exchange.getAttribute("cachedRequestBodyObject")
直接获取body
@Component
public class GatewayRequestLogFilter implements GlobalFilter, Ordered {
private static Logger logger = LoggerFactory.getLogger(GatewayRequestLogFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
logger.info("path: " + request.getPath());
logger.info("address: " + request.getRemoteAddress());
logger.info("method: " + request.getMethodValue());
logger.info("URI: " + request.getURI());
logger.info("Headers: " + request.getHeaders());
Object requestBody = exchange.getAttribute("cachedRequestBodyObject");
logger.info("body: "+ requestBody);
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
注意事项:
如果是get请求,get请求使用readBody会报错
如果是post请求,Content-Type是application/x-www-form-urlencoded,readbody为 String.class
如果是post请求,Content-Type是application/json,readbody为 Object.class
最后附上响应拦截记录日志代码:
@Component
public class GatewayResponseLogFilter implements GlobalFilter, Ordered {
private static Logger logger = LoggerFactory.getLogger(GatewayResponseLogFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpResponse originalResponse = exchange.getResponse();
DataBufferFactory bufferFactory = originalResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
@Override
@SuppressWarnings("unchecked")
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.map(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
DataBufferUtils.release(dataBuffer);
String result = new String(content, Charset.forName("UTF-8"));
logger.info("response body: " + result);
byte[] uppedContent = new String(content, Charset.forName("UTF-8")).getBytes();
return bufferFactory.wrap(uppedContent);
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
}
@Override
public int getOrder() {
return -2;
}
}
上一篇: win7桌面图标变成或者带有黑色方块,用这个方法轻松搞定
下一篇: sql电商常用表基本字段
推荐阅读
-
Spring Cloud GateWay 路由转发规则介绍详解
-
Spring Cloud Ribbon实现客户端负载均衡的方法
-
利用Spring Cloud Config结合Bus实现分布式配置中心的步骤
-
详解Spring Cloud Hystrix断路器实现容错和降级
-
spring-cloud入门之eureka-server(服务发现)
-
spring-cloud入门之spring-cloud-config(配置中心)
-
spring-cloud入门之eureka-client(服务注册)
-
spring cloud实现前端跨域问题的解决方案
-
详解Spring Boot配置使用Logback进行日志记录的实战
-
Spring Cloud Gateway网关XSS过滤Filter