1

前言

最近团队的网关日志发现有不少响应结果记录,出现形如下的乱码

       �V*.I,IU�JK�)N�Q�M-.NL�^�m�?��(�钍/�,}�����]O7L|���ŲƧ�MϦnP�Q*K�)*�+���QJ-*�/r�O���{�@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 压缩算法进行压缩。
br(Brotli): 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来对服务端响应的数据进行解压缩


linyb极客之路
336 声望193 粉丝