基于restTemplate遇到的编码问题及解决

 更新时间:2021年10月28日 10:44:28   作者:zhuxingKevin  
这篇文章主要介绍了restTemplate遇到的编码问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

背景

之前用restTemplate做网络间的请求,没遇到过问题。今天先是出现了中文乱码的问题,而后又出现了特殊字符丢失的问题,于是查找资料及翻看源码,将问题解决也顺便记录下。

问题一:中文乱码

描述

在创建课件时,使用GET方法传递类型和标题两个参数到服务器,服务器返回一个课件编号。类型是固定数字1,不存在问题,而标题则是用户输入字符串,也就是任意字符串。发现输入汉字的时候,结果网络传输后在服务器端出现了乱码。输入标题为:开发测试001,结果在服务器上接收到的为:%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001。

分析

出现编码问题,那肯定是对涉及到这个标题的编码的位置出现了问题,于是查找涉及到的代码位置:

public String createPptSlide(String title) {
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(host + PPT_SLIDE_INSERT);
        builder.queryParam("businessLineId", PPT_BUSINESS_LINE_ID);
        builder.queryParam("slideTitle", title);
        String url = builder.toUriString();
        restTemplate.getForEntity(url, JSONObject.class);
}

此处的String url已经是编码过了的http://xxx.com/slide/insertEmptySlide?businessLineId=1&slideTitle=%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001

于是继续追踪getForEntity方法,在执行doExecute方法前,构造URI时又进行了一次编码,如下:

@Override
    public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> urlVariables)
            throws RestClientException {
        RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
        ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
        return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables);
    }
public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback,
            ResponseExtractor<T> responseExtractor, Map<String, ?> urlVariables) throws RestClientException {
        URI expanded = new UriTemplate(url).expand(urlVariables);
        return doExecute(expanded, method, requestCallback, responseExtractor);
}
public URI expand(Map<String, ?> uriVariables) {
        UriComponents expandedComponents = this.uriComponents.expand(uriVariables);
        UriComponents encodedComponents = expandedComponents.encode();
        return encodedComponents.toUri();
}

结论

在程序构建url时,程序代码已经对title进行了一次编码,,传入restTemplate后,restTemplate框架本身又会对其做一次编码,最后服务器接收到的参数其实是做了两次编码,导致解码后还是乱码。

第一次编码:%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001

第二次编码:%25E5%25BC%2580%25E5%258F%2591%25E6%25B5%258B%25E8%25AF%2595001

解码后:%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001

侧面论证:

这里的编码是URLencode,这个编码简单来讲就是:将非字母数字字符都将被替换成百分号(%)后跟两位十六进制数。可以看到这一串的乱码%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001很像urlencode后的结果,解码后发现果真是这样。

image.png

方案

传入url前不对title进行编码,直接拼接原始的参数,然后传入到restTemplate,让restTemplate框架来进行编码。这样解决了中文乱码的问题,如下:

UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(host + PPT_SLIDE_INSERT);
        builder.queryParam("businessLineId", PPT_BUSINESS_LINE_ID);
        String url = builder.toUriString();
        // 不能 encode 参数
        if (StringUtils.isNotBlank(title)) {
            url = url + "&slideTitle=" + title;
        }
        restTemplate.getForEntity(url, JSONObject.class);
}

总结

项目中使用restTemplate的地方,在传给restTemplate框架url的时候都进行了一次编码,于是自己也照搬过来,殊不知restTemplate框架本身就会对url进行编码。

其实从一个框架设计者的角度上将,urlencode是每个请求都会用到的,很顺利的可以想到框架会包含这个urlencode功能,不需要编程人员将参数urlencode。

问题二:特殊字符串丢失

描述

在使用restTemplate传递课件标题的时候,发现有一些特殊字符串有数据丢失问题,例如传递课件标题:开发测试001&aaa,接收方接到的是:开发测试001,后面跟的&aaa字符丢失了。

分析

再一次怀疑是restTemplate里面自带的编码导致的问题,于是对比了直接使用urlencode与使用restTemplate编码的结果,如下表:

urlencode

%e5%bc%80%e5%8f%91%e6%b5%8b%e8%af%95001%26aaa

restTemplate

%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001&aaa

明显可以发现restTemplate对特殊字符“&”没有进行url编码,导致最后构建成的url是这样的:

http://xxx.com/slide/insertEmptySlide?businessLineId=1&slideTitle=%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001&aaa

这样就会把&aaa中的aaa当成get请求中的一个参数来看待,从而得到的title只为:%E5%BC%80%E5%8F%91%E6%B5%8B%E8%AF%95001,导致丢失了&aaa字符,接收方解析也只能得到标题为:开发测试001。

结论

restTemplate在进行url编码的时候,不会对某些特殊字符的编码,例如&。

方案

既然restTemplate会忽略掉某些特殊字符的url编码,那么我们就索性不用restTemplate编码,直接自己编码好,跳过restTemplate的编码,实现方案为:

public String createPptSlide(String title) {
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(host + PPT_SLIDE_INSERT);
        builder.queryParam("businessLineId", PPT_BUSINESS_LINE_ID);
        builder.queryParam("slideTitle", title);
        URI uri = builder.build().encode().toUri();
        restTemplate.getForEntity(uri, JSONObject.class);
}

(ps:传String的url,restTemplate都会再一次进行编码,而直接传URI可以跳过restTemplate的编码)

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 关于Mybatis实体别名支持通配符扫描问题小结

    关于Mybatis实体别名支持通配符扫描问题小结

    MyBatis可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录,这篇文章主要介绍了Mybatis实体别名支持通配符扫描的问题,需要的朋友可以参考下
    2022-01-01
  • String转JSONObject的两种方式

    String转JSONObject的两种方式

    这篇文章主要介绍了String转JSONObject,本文通过实例代码给大家介绍两种方式转换,结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • RabbitMQ 延迟队列实现订单支付结果异步阶梯性通知(实例代码)

    RabbitMQ 延迟队列实现订单支付结果异步阶梯性通知(实例代码)

    这篇文章主要介绍了RabbitMQ 延迟队列实现订单支付结果异步阶梯性通知,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-02-02
  • SpringBoot+Vue+Element-ui实现前后端分离

    SpringBoot+Vue+Element-ui实现前后端分离

    使用前后端分离的方式,可以减少代码耦合,本文主要介绍了SpringBoot+Vue+Element-ui实现前后端分离,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • java 内部类的实例详解

    java 内部类的实例详解

    这篇文章主要介绍了java 内部类的实例详解的相关资料,希望通过本文大家能够理解掌握java内部类的使用,需要的朋友可以参考下
    2017-09-09
  • java中的抽象类和接口定义与用法详解

    java中的抽象类和接口定义与用法详解

    在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
    2021-10-10
  • IntelliJ IDEA多屏后窗口不显示问题解决方案

    IntelliJ IDEA多屏后窗口不显示问题解决方案

    这篇文章主要介绍了IntelliJ IDEA多屏后窗口不显示问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-09-09
  • Spring学习笔记之RestTemplate使用小结

    Spring学习笔记之RestTemplate使用小结

    这篇文章主要给大家介绍了关于Spring学习笔记之RestTemplate使用的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-08-08
  • SpringCloud Zuul过滤器和谷歌Gauva实现限流

    SpringCloud Zuul过滤器和谷歌Gauva实现限流

    这篇文章主要介绍了SpringCloud Zuul过滤器和谷歌Gauva实现限流,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • spring security数据库表结构实例代码

    spring security数据库表结构实例代码

    这篇文章主要介绍了spring security数据库表结构实例代码,需要的朋友可以参考下
    2017-09-09

最新评论