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


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

          

    跟踪日志:

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

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

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

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

    1. /**
    2. * 获取到解码方的response,验签--->重新封装--->加签
    3. * 通过 DataBufferFactory 解决响应体分段传输问题。
    4. */
    5. private ServerHttpResponseDecorator verifyRePackageSignatureResponse(ServerWebExchange exchange, String jmf_decode_url, String route_privateKey, String jmf_publicKey) {
    6. ServerHttpResponse response = exchange.getResponse();
    7. log.debug("R:给响应结果response设置编码格式----START----");
    8. response.getHeaders().add("Content-Type","application/json;charset=UTF-8");
    9. log.debug("R:给响应结果response设置编码格式-----END-----");
    10. DataBufferFactory bufferFactory = response.bufferFactory();
    11. return new ServerHttpResponseDecorator(response) {
    12. @Override
    13. public Mono writeWith(Publisher body) {
    14. if (body instanceof Flux) {
    15. // 获取响应类型,如果是 json 就打印
    16. String originalResponseContentType = exchange.getAttribute(ServerWebExchangeUtils.ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
    17. log.debug("响应类型为originalResponseContentType:{}",originalResponseContentType);
    18. if (RequestResponseUtil.isJson(originalResponseContentType)) {
    19. Fluxextends DataBuffer> fluxBody = Flux.from(body);
    20. return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
    21. // 合并多个流集合,解决返回体分段传输
    22. DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
    23. DataBuffer join = dataBufferFactory.join(dataBuffers);
    24. byte[] content = new byte[join.readableByteCount()];
    25. join.read(content);
    26. // 释放掉内存
    27. DataBufferUtils.release(join);
    28. // 正常返回的数据
    29. String rootData = new String(content, StandardCharsets.UTF_8);
    30. log.debug("R:正常返回的数据rootData为:{}", rootData);
    31. //使用枚举 + 工厂 + 策略模式(第二版)
    32. String newQqtBodyJson = null;
    33. try {
    34. newQqtBodyJson = DecoderSignStrategyContext.zwmVerifySignResponse(rootData, jmf_decode_url, route_privateKey, jmf_publicKey);
    35. log.debug("R:【码路由服务】经具体策略处理,得到结果newQqtBodyJson为:{}", newQqtBodyJson);
    36. } catch (Exception e) {
    37. log.error("R:【码路由服务】解码方返回结果验签异常:{}", e);
    38. throw new VerifySignException(StaticVar.FAIL_10020015, "R:【码路由服务】解码方返回结果验签异常");
    39. }
    40. byte[] respData = newQqtBodyJson.getBytes();
    41. //byte[] respData = newQqtBodyJson.getBytes(StandardCharsets.UTF_8);
    42. byte[] uppedContent = new String(respData, Charset.forName("UTF-8")).getBytes();
    43. return bufferFactory.wrap(uppedContent);
    44. }));
    45. } else {
    46. log.error("响应结果异常");
    47. throw new ProcessHandleException(StaticVar.FAIL_10020015, "R:【码路由服务】解码方返回结果异常,非法JSON");
    48. }
    49. }
    50. // if body is not a flux. never got there.
    51. return super.writeWith(body);
    52. }
    53. };
    54. }

    因此不是代码的问题。又找到了一篇文章,解决了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属性置为空字符串,并添加注释如下:

    1. // 定义新的消息头
    2. HttpHeaders headers = new HttpHeaders();
    3. //System.out.println("headers = " + headers); //查看重新定义消息头的内容
    4. headers.putAll(exchange.getRequest().getHeaders());
    5. // 由于修改了传递参数,需要重新设置CONTENT_LENGTH,长度是字节长度,不是字符串长度
    6. int length = bodyStr.getBytes().length;
    7. log.debug("bodyStr长度为:{}",length);
    8. headers.remove(HttpHeaders.CONTENT_LENGTH);
    9. // 设置CONTENT_TYPE
    10. if (StringUtils.isNotBlank(contentType)) {
    11. headers.set(HttpHeaders.CONTENT_TYPE, contentType);
    12. }
    13. String jmf_decode_url = request.getURI().toString();
    14. String qqtBodyJsonStr = JSONUtils.toString(bizPackage);
    15. //request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(bodyStr.length())); //报错JSON parse error JsonEOFException,长度必须为字节的长度
    16. request.mutate().header(HttpHeaders.CONTENT_LENGTH, Integer.toString(length));//成功,解决JSON parse error JsonEOFException
    17. /**
    18. * 乱码现象:请求响应结果乱码
    19. * 解决过程分为PostMan请求和手机端App扫码请求:
    20. * (1)、PostMan请求响应乱码解决:
    21. * PostMan请求的headers中默认会有"Accept-Encoding"属性,值为"gzip, deflate, br",导致响应结果乱码
    22. * 去掉Accept-Encoding后请求正常。
    23. *
    24. * (2)、手机端App扫码请求乱码解决:
    25. * 在HttpRequestSignForwardGatewayFilter中定义新的消息头,headers中默认会有"accept-encoding"属性,
    26. * 值为"gzip, deflate, br", 添加代码"request.mutate().header("accept-encoding",""); "解决乱码
    27. *
    28. * 注意: 创建headers对象默认会生成“accept-encoding=‘gzip, deflate, br‘ ”属性,此处必须将accept-encoding
    29. * 置为空字符串(置为null会报错),否则使用默认值会导致响应结果乱码。
    30. * 说明: 测试开发阶段未发生此问题,第三方检测时演示出现此问题,这个可能是gateway内部的问题,尚未可知。
    31. *
    32. * 经测试,这种方式已解决了:手机端APP扫码和PostMan响应结果乱码的问题(PostMan请求时可以不用刻意去掉Accept-Encoding,
    33. * 也可请求成功)
    34. *
    35. */
    36. request.mutate().header("accept-encoding","");

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

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

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

    参考文章:

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

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

    在此感谢文章作者!

  • 相关阅读:
    自定义注解以及注解在反射中的应用
    CTF--Web安全--SQL注入之Post-Union注入
    yarn,webpack笔记
    MySQL数据存在更新不存在新增数据
    华为云新用户:定义,优惠券及专享活动
    uniapp nvue 踩坑记录
    Redis主从复制
    秋招失利,拿到这份“Java 高分指南(25 专题)”,金三银四翻盘有望
    java计算机毕业设计社区生鲜电商平台源码+系统+数据库+lw文档+mybatis+运行部署
    LVGL库入门教程 - 颜色和图像
  • 原文地址:https://blog.csdn.net/u013506626/article/details/134487673