#前言
之前写
springcloud gateway
收集日志,由于之前没有调研全面,导致了一个小坑,无法记录
post
方法获取
requestBody
。
踩坑示范
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(response) {
@Override
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 -> {
// probably should reuse buffers
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
String responseResult = new String(content, Charset.forName("UTF-8"));
normalMsg.append("status=").append(this.getStatusCode());
normalMsg.append(";header=").append(this.getHeaders());
normalMsg.append(";responseResult=").append(responseResult);
normalMsg.append(RESPONSE_TAIL);
log.info(normalMsg.toString());
return bufferFactory.wrap(content);
}));
}
// if body is not a flux. never got there.
return super.writeWith(body);
}
};
public class RecorderServerHttpRequestDecorator extends ServerHttpRequestDecorator {
private final List<DataBuffer> dataBuffers = new ArrayList<>();
public RecorderServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
super.getBody().map(dataBuffer -> {
dataBuffers.add(dataBuffer);
return dataBuffer;
}).subscribe();
}
@Override
public Flux<DataBuffer> getBody() {
return copy();
}
private Flux<DataBuffer> copy() {
return Flux.fromIterable(dataBuffers)
.map(buf -> buf.factory().wrap(buf.asByteBuffer()));
}
}
- 之前的思路是,直接
Flux<DataBuffer> body = serverHttpRequest.getBody();
,这种方式获取。 - 获取到
body
然后再将其包装成新的request
传递下去(因为requestBody
只能获取一次,其他filte
就会获取不到)。
这里面会产生两个问题:
- 在封装的时候这个
subscribe
是异步的,可以没有把body的内容放进去,就直接放行了。 - 还有就是如果能放进去,最大也只能获取了
1024b
。
爬坑案例
我定义两个filter
,一个用做缓存,一个取出,什么意思呢?
- 先让请求走缓存
filter
,缓存放到exchange
里面,作为它的一个变量。 - 从日志的
filter
取出来这个变量。
CacheRequestBodyFilter
@Slf4j
@Component
public class CacheRequestBodyFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 将 request body 中的内容 copy 一份,记录到 exchange 的一个自定义属性中
Object cachedRequestBodyObject = exchange.getAttributeOrDefault(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY, null);
// 如果已经缓存过,略过
if (cachedRequestBodyObject != null) {
return chain.filter(exchange);
}
// 如果没有缓存过,获取字节数组存入 exchange 的自定义属性中
return DataBufferUtils.join(exchange.getRequest().getBody())
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
}).defaultIfEmpty(new byte[0])
.doOnNext(bytes -> exchange.getAttributes().put(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY, bytes))
.then(chain.filter(exchange));
}
@Override
public int getOrder() {
return -100;
}
}
LoggerFilter
给出核心代码,详细见github:
Object cachedRequestBodyObject = exchange.getAttributes().get(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY);
if (cachedRequestBodyObject != null) {
byte[] body = (byte[]) cachedRequestBodyObject;
String string = new String(body);
log.info("request body:");
log.info(string);
}
可以给个post请求下网关,看是否能获取到requestBoy,如果可以的话,请给代码一个star
,有什么建议欢迎下方评论。