记一次springcloud gateway记录日志响应结果乱码问题
前言
最近团队的网关日志发现有不少响应结果记录出现形如下的乱码
V*.I,IUJK)NQM-.NL^m?(钍/,}]O7L|ŲƧMϦnPQ*K)*+QJ-*/rO{@8
一开始感觉是不是中文乱码但是后面发现有些日志不是中文也是乱码而有些记录的日志又能正常显示。于是搜索了一圈在https://blog.csdn.net/u013506626/article/details/134487673
在这篇文章找到的思路以及解决答案。
如何解决
根据上面博文介绍是因为请求的headers中加了有"Accept-Encoding"属性值为"gzip, deflate, br",导致响应结果乱码。解决思路就是将Accept-Encoding置为空“”就可以解决按他的思路我就写了一个过滤器
@RequiredArgsConstructor
public class RemoveGzipHeaderGlobalFilter implements Ordered, GlobalFilter {
private final GwCommonProperty gwCommonProperty;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (isSkipRemoveGzipHeaderEnabled(exchange)) {
return chain.filter(exchange);
} else {
ServerHttpRequest request = exchange.getRequest().mutate()
.header(HttpHeaders.ACCEPT_ENCODING, "").build();
return chain.filter(exchange.mutate().request(request).build());
}
}
private boolean isSkipRemoveGzipHeaderEnabled(ServerWebExchange exchange){
if(!gwCommonProperty.isRemoveGzipHeaderEnabled()){
return true;
}
String gzipHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.ACCEPT_ENCODING);
if(!StringUtils.hasText(gzipHeader)){
return true;
}
return !gzipHeader.contains("gzip");
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
后面果然不再出现乱码。既然是Accept-Encoding引起的乱码问题我们就来聊下Accept-Encoding
Accept-Encoding
Accept-Encoding 是 HTTP 协议中的一个头部字段其主要作用在于告知服务器客户端能够理解的内容编码方式。这个字段对于网络传输效率的优化非常重要因为它允许服务器根据客户端的能力来压缩响应数据从而减少传输的数据量加快网页加载速度。
1、常见编码方式
gzip 使用 Lempel-Ziv 编码LZ77和 Huffman 编码进行压缩的算法。
deflate 使用 zlib 库和 deflate 压缩算法进行压缩。
brBrotli Google 开发的一种新的数据压缩算法旨在提供比 gzip 和 deflate 更高的压缩率。
2、字段格式
Accept-Encoding 字段的值是一个由逗号分隔的列表其中包含了客户端支持的内容编码方式。例如Accept-Encoding: gzip, deflate, br
3、工作流程
客户端在发送 HTTP 请求时会在请求头部中包含 Accept-Encoding 字段列出它支持的内容编码方式。
服务器在收到请求后会检查 Accept-Encoding 字段并根据客户端支持的内容编码方式来选择合适的压缩算法来压缩响应数据。
如果服务器选择了一种内容编码方式它会在响应头部的 Content-Encoding 字段中指定所使用的编码方式。
网关日志记录响应结果乱码原因
介绍完Accept-Encoding我们继续探讨一下为啥Accept-Encoding会引起网关日志响应结果乱码因为设置了Accept-Encoding: gzip,deflate所以服务器就会根据客户端支持的内容编码方式来选择合适的压缩算法来压缩响应而网关层数据没对数据进行解压缩因此就乱码
因此解决乱码的思路理论上会有2种一种是上述博文介绍的去掉Accept-Encoding: gzip,deflate这个头信息。去掉这个头信息就是告诉服务器客户端不支持压缩要求不压缩直接返回数据
另外一种思路是如果服务器选择了一种内容编码方式它会在响应头部的 Content-Encoding 字段中指定所使用的编码方式。因此我们就可以根据Content-Encoding来判断是否要对数据进行解压缩
网关日志记录过滤器核心改造的示例如下
/**
* 记录响应日志
* 通过 DataBufferFactory 解决响应体分段传输问题。
*/
private ServerHttpResponseDecorator recordResponseLog(ServerWebExchange exchange, AccessLog accessLog) {
ServerHttpResponse response = exchange.getResponse();
DataBufferFactory bufferFactory = response.bufferFactory();
return new ServerHttpResponseDecorator(response) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (body instanceof Flux) {
LogUtils.setReponse(accessLog,exchange);
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
// 合并多个流集合解决返回体分段传输
DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
content = isGzip(response) ? gzipMessageBodyResolver.decode(content) : content;
// 释放掉内存
DataBufferUtils.release(join);
String responseResult = new String(content, StandardCharsets.UTF_8);
accessLog.setResponseData(responseResult);
return bufferFactory.wrap(content);
}));
}
// if body is not a flux. never got there.
return super.writeWith(body);
}
};
}
public boolean isGzip(ServerHttpResponse serverHttpResponse) {
HttpHeaders headers = serverHttpResponse.getHeaders();
if (headers.containsKey(HttpHeaders.CONTENT_ENCODING)) {
List<String> encodingList = headers.get(HttpHeaders.CONTENT_ENCODING);
return CollectionUtil.isNotEmpty(encodingList) && encodingList.contains(GZIP);
}
return false;
}
注 特别提醒因为要获取服务端header响应Content-Encoding用的是ServerHttpResponse而不是ServerHttpRequest
总结
综上解决因Accept-Encoding引起的乱码方式有2种一种是直接移除Accept-Encoding告诉服务端不要对响应数据进行压缩直接返回未压缩数据。另外一种如果不移除Accept-Encoding就得根据Content-Encoding来对服务端响应的数据进行解压缩
共同学习,写下你的评论
评论加载中...
作者其他优质文章