记一次线上bug排查-----SpringCloud Gateway组件 请求头accept-encoding导致响应结果乱码

       基于公司的业务需求,在SpringCloud Gateway组件的基础上,写了一个转发服务,测试开发阶段运行正常,并实现初步使用。但三个月后,PostMan请求接口,返回异常,经排查,从日志中获取到转发响应的结果为乱码:

      

跟踪日志:

转发到目标接口,响应结果已乱码。一般排查的思路是,查看请求方和响应方的编码格式是否一致,打印请求方的编码格式为UTF-8,响应服务的编码格式也是UTF-8。

以上说明编码格式没有问题。上网去找“gateway响应结果乱码”的相关文章,大多数会提供解决方案:

DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
DataBuffer join = dataBufferFactory.join(dataBuffers);
byte[] content = new byte[join.readableByteCount()];
join.read(content);
// 释放掉内存
DataBufferUtils.release(join);
String str = new String(content, Charset.forName("UTF-8"));
originalResponse.getHeaders().setContentLength(str.getBytes().length);
System.out.println(str);
return bufferFactory.wrap(str.getBytes());

这段关键代码,在我的响应结果包装过滤器是有的,如下:

    /**
     * 获取到解码方的response,验签--->重新封装--->加签
     * 通过 DataBufferFactory 解决响应体分段传输问题。
     */
    private ServerHttpResponseDecorator verifyRePackageSignatureResponse(ServerWebExchange exchange, String jmf_decode_url, String route_privateKey, String jmf_publicKey) {
        ServerHttpResponse response = exchange.getResponse();
        log.debug("R:给响应结果response设置编码格式----START----");
        response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
        log.debug("R:给响应结果response设置编码格式-----END-----");
        DataBufferFactory bufferFactory = response.bufferFactory();

        return new ServerHttpResponseDecorator(response) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {

                    // 获取响应类型,如果是 json 就打印
                    String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    log.debug("响应类型为originalResponseContentType:{}",originalResponseContentType);
                    if (RequestResponseUtil.isJson(originalResponseContentType)) {
                        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);

                            // 释放掉内存
                            DataBufferUtils.release(join);

                            // 正常返回的数据
                            String rootData = new String(content, StandardCharsets.UTF_8);
                            log.debug("R:正常返回的数据rootData为:{}", rootData);


                            //使用枚举 + 工厂 + 策略模式(第二版)
                            String newQqtBodyJson = null;
                            try {
                                newQqtBodyJson = DecoderSignStrategyContext.zwmVerifySignResponse(rootData, jmf_decode_url, route_privateKey, jmf_publicKey);
                                log.debug("R:【码路由服务】经具体策略处理,得到结果newQqtBodyJson为:{}", newQqtBodyJson);
                            } catch (Exception e) {
                                log.error("R:【码路由服务】解码方返回结果验签异常:{}", e);
                                throw new VerifySignException(StaticVar.FAIL_10020015, "R:【码路由服务】解码方返回结果验签异常");
                            }
                            byte[] respData = newQqtBodyJson.getBytes();
                            //byte[] respData = newQqtBodyJson.getBytes(StandardCharsets.UTF_8);
                            byte[] uppedContent = new String(respData, Charset.forName("UTF-8")).getBytes();
                            return bufferFactory.wrap(uppedContent);

                        }));

                    } else {
                        log.error("响应结果异常");
                        throw new ProcessHandleException(StaticVar.FAIL_10020015, "R:【码路由服务】解码方返回结果异常,非法JSON");
                    }
                }

                // if body is not a flux. never got there.
                return super.writeWith(body);
            }
        };
    }

因此不是代码的问题。又找到了一篇文章,解决了PostMan请求的问题。

https://bbs.csdn.net/topics/399102026/close

如上所述,在PostMan请求的headers中去掉Accept-Encoding,请求成功:

       至此,PostMan请求乱码的问题已解决。但事情似乎没有那么简单,接下来,使用手机模拟扫码,请求码路由服务,又出现了乱码,扫码结果页面,返回结果:

 查看日志,乱码如下:

      排查问题进入瓶颈期,有些烦躁了,必须冷静下来,重新捋一下代码,同时也在想,既然和PostMan请求存在同样的问题,是不是在请求头中默认会有一个Accept-Encoding属性,从而导致了乱码,根据这个思路,在自定义的请求转发过滤器中,发现了以下代码:

由于在请求转发的逻辑中,重新构建了一个request请求的同时,重新创建了一个请求头headers,它在重新构建的时候默认会有一个accept-encoding属性,看到这一行代码时,正印证了以上我的猜想,问题已经找到了,将其置为空字符串即可(注意:不能使其为null,也不能remove这个属性,会报错)。

  将headers的accept-encoding属性置为空字符串,并添加注释如下:

// 定义新的消息头
        HttpHeaders headers = new HttpHeaders();
        //System.out.println("headers = " + headers); //查看重新定义消息头的内容
        headers.putAll(exchange.getRequest().getHeaders());

        // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
        int length = bodyStr.getBytes().length;
        log.debug("bodyStr长度为:{}",length);
        headers.remove(HttpHeaders.CONTENT_LENGTH);

        // 设置CONTENT_TYPE
        if (StringUtils.isNotBlank(contentType)) {
            headers.set(HttpHeaders.CONTENT_TYPE, contentType);
        }

        String jmf_decode_url = request.getURI().toString();
        String qqtBodyJsonStr = JSONUtils.toString(bizPackage);

        //request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(bodyStr.length())); //报错JSON parse error JsonEOFException,长度必须为字节的长度
        request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(length));//成功,解决JSON parse error JsonEOFException

        /**
         * 乱码现象:请求响应结果乱码
         * 解决过程分为PostMan请求和手机端App扫码请求:
         *   (1)、PostMan请求响应乱码解决:
         *             PostMan请求的headers中默认会有"Accept-Encoding"属性,值为"gzip, deflate, br",导致响应结果乱码
         *         去掉Accept-Encoding后请求正常。
         *
         *   (2)、手机端App扫码请求乱码解决:
         *             在HttpRequestSignForwardGatewayFilter中定义新的消息头,headers中默认会有"accept-encoding"属性,
         *         值为"gzip, deflate, br", 添加代码"request.mutate().header("accept-encoding",""); "解决乱码
         *
         *       注意: 创建headers对象默认会生成“accept-encoding=‘gzip, deflate, br‘ ”属性,此处必须将accept-encoding
         *       置为空字符串(置为null会报错),否则使用默认值会导致响应结果乱码。
         *       说明: 测试开发阶段未发生此问题,第三方检测时演示出现此问题,这个可能是gateway内部的问题,尚未可知。
         *
         *          经测试,这种方式已解决了:手机端APP扫码和PostMan响应结果乱码的问题(PostMan请求时可以不用刻意去掉Accept-Encoding,
         *       也可请求成功)
         *
         */
        request.mutate().header("accept-encoding","");

重启后,部署后问题得到解决。这个问题在测试开发阶段没有暴露出来,按理说应该早暴露了,但现实情况就是这么诡异,这可能是Gateway内部的bug,尚未可知。

     希望此文对遇到同样问题的小伙伴有所帮助和启发,望了解gateway内部原理机制的大神,参与讨论。

    乱码问题已排查并处理结束,完结,撒花!

参考文章:

RestTemplate请求头accept-encoding导致乱码_resttemplate 乱码_AE86Jag的博客-CSDN博客

https://bbs.csdn.net/topics/399102026/close

在此感谢文章作者!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/166989.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Docker/K8S/Helm部署项目流程

假设项目已经开发完成&#xff0c;部署流程如下&#xff1a; 一、制作镜像&#xff1a; 1、创建nginx配置文件default.conf server {listen 80;server_name localhost; # 修改为docker服务宿主机的iplocation / {root /usr/share/nginx/html;index index.html ind…

element-ui中怎样使用iconfont的图标

1 登录 https://www.iconfont.cn/ 2 搜索合适的图 这里可以找到这个图所在的图库。这样就可以一次查找到对应的所有同款图标 3 选择同款加入购物车 4 将购物车的icon加入项目&#xff0c;注意是新建项目&#xff0c;除非你是确定需要前面已经加过的icon 5 下载icon 选择fon…

日期相关整理

3214. 节日 有一类节日的日期并不是固定的&#xff0c;而是以“a 月的第 b 个星期 c ”的形式定下来的&#xff0c;比如说母亲节就定为每年的五月的第二个星期日。 现在&#xff0c;给你 a,b,c 和 y1,y2&#xff0c;希望你输出从公元 y1 年到公元 y2 年间的每年的 a 月的第 b 个…

为什么越来越多人选择学习Python?

今天我要和大家聊聊一个很热门的话题&#xff1a;为什么那么多人学习Python&#xff1f; 最近小编发现一个有趣的现象&#xff0c;高中生们居然在学校课程里学Python&#xff0c;这不仅给我们这些已经毕业多年的人当头一棒&#xff0c;更是彻底颠覆了传统观念。现在的高中生竟…

Embedding技术与应用(4): Embedding应用工程探析

编者按&#xff1a;随着互联网内容数量的急剧增长&#xff0c;个性化推荐已成为各大科技公司的核心竞争力之一。那么&#xff0c;如何构建一个可靠、高效的基于嵌入技术的推荐系统&#xff0c;使其能够在实际生产环境中正常运行呢&#xff1f;这是所有从业者都关心的问题。 本文…

(一)RISC-V 指令集及寄存器介绍

1. RISC-V指令集介绍 RISC-V 念作 “risk-five”&#xff0c;代表着 Berkeley 所研发的第五代精简指令集。 该项目 2010 年始于加州大学伯克利&#xff08;Berkeley&#xff09;分校&#xff0c;希望选择一款 ISA用于科研和教学。经过前期多年的研究和选型&#xff0c;最终决定…

UE TransformVector 学习笔记

假如算现在枪的位置&#xff0c;那么就是先拿人的位置再拿枪在本地的相对位置相加&#xff0c;就是枪的位置&#xff0c;也就是枪在场景中的位置&#xff0c;那么这里还可以写成Actor的变化和枪的相对位置连在TransformVector上&#xff0c;返回的就是枪的场景位置 这里做反算&…

统一身份认证平台之SSO建设

前言 上篇说道Passwordless无密码技术&#xff0c;也提到了数字时代密码管理的难度&#xff0c;其实在日常的生活中&#xff0c;很多用户也会因为忘记某些网站的登录密码而烦恼。为了方便记忆&#xff0c;很多人都在不同的站点使用相同的用户名和密码&#xff0c;虽然也可以减少…

PS太难学,这款软件更简单!——Lightroom

今天&#xff0c;我们来谈谈Adobe Photoshop Lightroom软件&#xff0c;它是当今数字拍摄工作流程中不可或缺的一部分。现在您可以快速导入、处理、管理和展示图像 — 从一张照片到所有照片。增强的校正工具、强大的组织功能以及灵活的打印选项可以帮助您加快速度。 Adobe Pho…

redis集群(Cluster)

文章目录 前言一、资源准备二、redis安装二、启动redis三、构建集群 前言 redis 集群三种方式&#xff1a;主从复制&#xff0c;哨兵模式&#xff0c;Cluster集群。 本文只介绍Cluster集群部署方案。 一、资源准备 服务器1台&#xff08;正常应该是3台,每台2个节点&#xff…

源启容器平台KubeGien 打造云原生转型的破浪之舰

云原生是应用上云的标准路径&#xff0c;也是未来发展大的趋势。如何将业务平滑过渡到云上&#xff1f;怎样应对上云期间的各项挑战呢&#xff1f;中电金信基于金融级数字底座“源启”打造了一款非常稳定可靠、多云异构、安全可控、开放灵活的容器平台产品——源启容器平台Kube…

Threejs_03 全屏+响应式画布实现

咋控制全屏呢&#xff1f; 1.做一个用来点击的按钮 var btn document.createElement("button"); btn.innerHTML "点击全屏"; btn.style.position "absolute"; btn.style.top "10px"; btn.style.left "10px"; btn.sty…

纯JS,RSA,AES,公钥,私钥生成及加解密

通过网络找的JS源文件&#xff0c;修改后使用&#xff0c;包含RSA 密匙对生成 及AES 加解密 涉及的JS源文件 下载 GitHub - cgrlancer/RSA-AES: 纯js,RSA,AES前端加解密 前端引用 import {generateRsaKeyWithPKCS8,encryptByRSA,decryptByRSA,encrypt,decrypt,testRsa} fr…

【Java程序员面试专栏 专业技能篇】Java SE核心面试指引(一):基础知识考察

关于Java SE部分的核心知识进行一网打尽,包括四部分:基础知识考察、面向对象思想、核心机制策略、Java新特性,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 本篇Blog为第一部分:基础知识考察,子节点表示追问或同级提问 基本概念 …

ICCV 2023|小红书 4 篇入选论文亮点解读,「开集视频目标分割」获得 Oral

近日&#xff0c;ICCV 正式揭晓 2023 年论文接收结果&#xff0c;小红书技术团队共有 4 篇论文入选&#xff0c;其中 1 篇为 Oral 论文&#xff08;Oral 接收率仅为 1.88%&#xff09;&#xff0c;最新科研成果涵盖了视频目标分割、3D 数字人重建、人体运动预测、视频分析等领域…

国产化区块链平台-FISCO BCOS 区块链

目录 FISCO BCOS 版本信息 系统概述 关键特性 组件服务 开发运维工具 FISCO BCOS作为一种企业级区块链平台&#xff0c;为企业和组织提供了高性能、隐私保护和可定制的区块链解决方案。其强大的架构和丰富的功能使得企业能够在安全可信的环境中开展区块链应用&#xff0…

Excel筛选怎么用?6种基本用法帮你提高效率!

“我在使用Excel进行数据处理时&#xff0c;需要对某些数据进行筛选&#xff0c;但是不知道应该如何使用筛选功能&#xff0c;有没有比较简单的使用方法呀&#xff1f;” Excel中的筛选功能是数据处理和分析中的重要工具&#xff0c;能够帮助用户快速找到所需的数据。但是有很多…

YOLOv8改进 | EIoU、SIoU、WIoU、DIoU、FocusIoU等二十余种损失函数

一、本文介绍 这篇文章介绍了YOLOv8的重大改进&#xff0c;特别是在损失函数方面的创新。它不仅包括了多种IoU损失函数的改进和变体&#xff0c;如SIoU、WIoU、GIoU、DIoU、EIOU、CIoU&#xff0c;还融合了“Focus”思想&#xff0c;创造了一系列新的损失函数。这些组合形式的…

Taro安装及使用

安装及使用 安装​ Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>12.0.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了…

[解决] 问题:ImportError: cannot import name ‘Callable‘ from ‘collections‘

问题 我在运行yolov8的代码时&#xff0c;出现了ImportError: cannot import name Callable from collections的错误 原因 版本问题:以下collections的方法都在Python3.10版本后被取消了 ["Awaitable", "Coroutine", "AsyncIterable", "A…