#前言
之前寫
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
,有什麼建議歡迎下方評論。