我的编程空间,编程开发者的网络收藏夹
学习永远不晚

关于远程调用RestTemplate的使用避坑指南

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

关于远程调用RestTemplate的使用避坑指南

一、前言介绍

RestTemplate是Spring中用于远程接口调用的工具类,它是Apache的HttpClient的模板封装,使用起来非常方便,本文将讲述这两天自己在使用RestTemplate过程中遇到的问题,当然这些问题也是由于自己对RestTemplate工具类了解不够全面不够透彻造成的,希望自己遇到的这些问题能为大家提前避雷或是遇到类似问题时的一个解决参考。

二、 问题记录

1. 慎!【url参数中有json字符串】

在使用RestTemplate进行远程接口调用时,如果url拼接参数中json字符串时一定要小心,使用场景如下:利用restTemplate调用user的查询信息接口,url中的一个参数user为json字符串格式{\"user\":\"xiaoming,\"age\":"12"}


  // JSON参数
        Map<String, String> paramMap = new HashMap<>(8);
        paramMap.put("name","xiaoming");
        paramMap.put("age","12");
        String paramJsonStr = JSONObject.toJSONString(paramMap);
 
        // 实际参数 url = "http://localhost:8080/api/user?user={\"name\":\"xiaoming\",\"age\":\"12\"}&country=china";
        String url = "http://localhost:8080/api/user?user=" + paramJsonStr + "&country=china";
        RestTemplate restTemplate = new RestTemplate();
        // 调用出错
        Object execute = restTemplate.execute(url, HttpMethod.GET, null, null);

此时当我们运行程序时会抛出以下错误:

错误意思大概是没有足够可用的变量值来填充扩展 'name',这是什么鬼意思,别着急让我们跟跟代码看看异常抛出的位置,最终定位如下,在创建URI过程中调用了 UriComponents.expandUriComponent()方法抛出异常:

这段代码的作用其实就是通过NAMES_PATERN规则匹配到相应字符串然后利用 uriVariables.getValue(varibaleName)进行替换,再看看NAMES_PATERN的值就是用来匹配{}中的字符串内容的


private static final Pattern NAMES_PATTERN = Pattern.compile(\\{([^/]+?)\\});

问题分析到这儿相信大家应该也明白了RestTemplate在创建URI时会进行{param}占位替换,这个规则在文本输出时应用比较多,如日志打印和控制台打印中常有使用:


   String value = "test";
   logger.info("占位参数{}",value);

解决办法:

找到原因了,那么我们应当如何解决呢,既然RestTemplate在处理url时会进行{}变量替换,那它理应提供相应的接口调用,查看RestTemplate源码它提供了多个exchange重载方法,其中多个方法都有uriVariables参数,如下所示:


    public <T> T execute(String url, HttpMethod method, RequestCallback requestCallback, ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {
        URI expanded = this.getUriTemplateHandler().expand(url, uriVariables);
        return this.doExecute(expanded, method, requestCallback, responseExtractor);
    }

那么我们将url稍微修改即可解决问题:

2. 慎!【url参数中有经过URLEncode的字符串】

其实在遇到第一个坑时,我并没有采用上面给出的解决方式,而是想着将Json字符串经过URLEncode编码后在拼接到url后面,不就没有{}符号了,不就可以完美解决问题了,心里想着就美滋滋,那让我们来试一把吧:


      // JSON参数
        Map<String, String> paramMap = new HashMap<>(8);
        paramMap.put("name","xiaoming");
        paramMap.put("age","12");
        String paramJsonStr = JSONObject.toJSONString(paramMap);
 
        // 实际参数 url = "http://localhost:8080/api/user?user=%7B%22name%22%3A%22xiaoming%22%2C%22age%22%3A%2212%22%7D&country=china";
        String encode = URLEncoder.encode(paramJsonStr, "utf-8");
        String url = "http://localhost:8080/api/user?user="+encode+"&country=china";
        System.out.println(url);
        RestTemplate restTemplate = new RestTemplate();
        Object execute = restTemplate.execute(url, HttpMethod.GET, null, null,paramJsonStr);

json字符串经过编码后已经没有{}符号了,也能够成功调用接口,但是接口提供方无情的返回了错误:参数无法反序列化。听这口气肯定也是这json串的原因,赶紧用Postman试一试:

神奇的一幕出现了,居然成功了! Postman方式调用和RestTemplate调用有什么不一样,为什么postman行,restTemplate不行?赶紧查看服务器日志看看两种方式接收到的参数有和不一样:

1. Postman方式服务器接收到的url:

"http://localhost:8080/api/user?user=%7B%22name%22%3A%22xiaoming%22%2C%22age%22%3A%2212%22%7D&country=china"

2. RestTemplate方式服务器接收到的url

"http://localhost:8080/api/user?user=%257B%2522name%2522%3A%2522xiaoming%2522%2C%2522age%2522%253A%252212%2522%257D&country=china"

restTemplate居然在每个百分号%后面都擅自加了25这个数字,难怪服务端没法解析,它为什么要这么做?难道是restTemplate的url处理bug?让我们跟一跟代码看个究竟:

详细调用层次就不贴了,简单来说就是RestTemplate在处理url时会对url参数进行再编码,也就是会对url中的特殊字符进行转义,如%号会被转义为%25,所以传给服务端的url就被改变了,具体url特殊字符转义知识请查看这篇文章

既然知道了原因,那么我们应该如何解决呢?

解决方案:

RestTemplate中的URI对象是通过UriTemplateHandler生成的,所以我们只需要利用java.net包中的URI自己构建URI对象传给RestTemplate即可,这样url中的特殊字符就不会被转义了:

3. 慎!【url参数中存在特殊字符】 --- 针对HttpClient

前面两个坑然我对RestTemplate有点望而生畏了,既然RestTemplate这么多坑,那好咋们换回老家伙apache家族的HttpClient,本以为可以一切顺利,没成想一坑接着一坑啊!!!

先看看调用场景:按照出生时间去查询用户信息

调用请求都还没发出就无情报错了:

通过异常信息可以容易知道在创建URL对象时url参数索引位置50处有非法字符,而这个字符刚好就是时间参数中的空格!,跟踪代码异常发生位置发现下面一段注释,大意就是不允许url中有特殊字符存在,看了本文第二个坑的应该已经明白url中特殊字符为什么需要进行转义了,这里就不详细叙述,至此解决方案就呼之欲出了。

解决方案:

需要对参数中的特殊字符进行转义:

1. 直接特殊字符替换

2. 利用google的工具包UrlEscapers(可以处理url、xml、html中的特殊字符)

maven依赖


 <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
    <dependency>
      <groupId>com.google.guava</groupId>
          <artifactId>guava</artifactId>
          <version>28.1-jre</version>
    </dependency>

使用方式:UrlEscapers可以对路径、参数、片段进行处理,提供了 path,parameter,fragment三个部分的Escape实例

分别调用UrlEscapers类的以下方法获取:

  • urlFormParameterEscaper()
  • urlPathSegmentEscaper()
  • urlFragmentEscaper()

总结

本次三个案例本人觉得还是具有典型性,由于平时发起请求大多通过浏览器或者是postman这类的http模拟工具进行,而浏览器和模拟工具在内部会对请求url和参数进行一定处理,例如编码和转义,所以平时对这块关注不多,而当我们在server端自我构建http请求进行远程调用时这类问题就需要我们特别注意,稍有不慎就会掉入坑中,还有一点感悟在使用一个工具类时应当先大致阅读一下工具类提供了哪些方法,做到心中有数使用时就会少走很多弯路。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

关于远程调用RestTemplate的使用避坑指南

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

小程序表单校验uni-forms的正确使用方式以及避坑指南

因微信小程序上没有自带表单验证,为了实现就自己做了个表单验证,下面这篇文章主要给大家介绍了关于小程序表单校验uni-forms的正确使用方式以及避坑指南,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
2022-11-13

基于Python 的进程管理工具supervisor使用指南

Supervisor 是基于 Python 的进程管理工具,只能运行在 Unix-Like 的系统上,也就是无法运行在 Windows 上。Supervisor 官方版目前只能运行在 Python 2.4 以上版本,但是还无法运行在 Pyt
2022-06-04

亚马逊欧美站远程服务器的使用指南

1.了解亚马逊欧美站远程服务器亚马逊欧美站远程服务器是亚马逊公司提供的一种云计算服务,它允许用户租用虚拟服务器来托管应用程序和数据。使用亚马逊欧美站远程服务器,您可以轻松地在云端搭建和管理您的网站、应用程序、数据库等。2.注册亚马逊欧美站远程服务器账户首先,您需要注册一个亚马逊欧美站远程服务器账户。访问亚马逊欧美站远程服务器的官方网站,点击"注册"按钮,按照指示填写必要的信息并创建账户。3.创建亚马逊欧美...
2023-10-27

对 Go 官方指南中关于所有方法使用相同类型接收器的建议感到困惑

php小编百草对于 Go 官方指南中关于所有方法使用相同类型接收器的建议感到困惑。在学习 Go 语言过程中,我们经常会遇到这样的建议,即在方法定义时,接收器的类型应该保持一致。然而,这个建议在实际应用中会带来一些疑问。为了更好地理解这个建议
对 Go 官方指南中关于所有方法使用相同类型接收器的建议感到困惑
2024-02-10

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录