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

目录
  • 一、前言介绍
  • 二、 问题记录
    • 1. 慎!【url参数中有json字符串】
    • 2. 慎!【url参数中有经过URLEncode的字符串】
    • 3. 慎!【url参数中存在特殊字符】 --- 针对HttpClient
  • 总结

一、前言介绍

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请求进行远程调用时这类问题就需要我们特别注意,稍有不慎就会掉入坑中,还有一点感悟在使用一个工具类时应当先大致阅读一下工具类提供了哪些方法,做到心中有数使用时就会少走很多弯路。

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

(0)

相关推荐

  • 基于RestTemplate的使用方法(详解)

    1.postForObject :传入一个业务对象,返回是一个String 调用方: BaseUser baseUser=new BaseUser(); baseUser.setUserid(userid); baseUser.setPass(pass); String postForObject = restTemplate.postForObject(this.getURL()+"/user/login", baseUser, String.class); return postF

  • RestTemplate未使用线程池问题的解决方法

    一.问题描述 现场出现springboot服务卡死,无法打开页面现象. 初步分析为服务中使用RestTemplate通信框架,但未使用连接池,如果通信抛出异常(连接失败),连续运行一定时间,导致线程飙升,资源耗尽,服务程序宕机. 二.问题再现 模拟无法通信的微服务地址,修改连接2s/次,启动三个微服务demo进行通信,连续测试2小时,现象可再现: 详细如下图: 启动时线程数: 连接异常提示: 线程飙升: 大量未关闭线程: 线程dump信息: "http-nio-8081-exec-120&quo

  • Spring远程调用HttpClient/RestTemplate的方法

    一.HttpClient 两个系统间如何互相访问?两个tomcat上的项目如何互相访问? 采用HttpClient实现跨系统的接口调用. 介绍: 官网:http://hc.apache.org/index.html 现在也叫:HttpComponents HttpClient可以发送get.post.put.delete....等请求 使用: 导入坐标 <dependency> <groupId>org.apache.httpcomponents</groupId> &

  • spring boot RestTemplate 发送get请求的踩坑及解决

    spring boot RestTemplate 发送get请求踩坑 闲话少说,代码说话 RestTemplate 实例 手动实例化,这个我基本不用 RestTemplate restTemplate = new RestTemplate(); 依赖注入,通常情况下我使用 java.net 包下的类构建的 SimpleClientHttpRequestFactory @Configuration public class RestConfiguration { @Bean @Conditiona

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

    目录 一.前言介绍 二. 问题记录 1. 慎![url参数中有json字符串] 2. 慎![url参数中有经过URLEncode的字符串] 3. 慎![url参数中存在特殊字符] --- 针对HttpClient 总结 一.前言介绍 RestTemplate是Spring中用于远程接口调用的工具类,它是Apache的HttpClient的模板封装,使用起来非常方便,本文将讲述这两天自己在使用RestTemplate过程中遇到的问题,当然这些问题也是由于自己对RestTemplate工具类了解不够

  • Java多线程基本概念以及避坑指南

    目录 前言 1. 多线程基本概念 1.1 轻量级进程 1.2 JMM 1.3 Java中常见的线程同步方式 2. 避坑指南 2.1. 线程池打爆机器 2.2. 锁要关闭 2.3. wait要包两层 2.4. 不要覆盖锁对象 2.5. 处理循环中的异常 2.6. HashMap正确用法 2.7. 线程安全的保护范围 2.8. volatile作用有限 2.9. 日期处理要小心 2.10. 不要在构造函数中启动线程 End 前言 多核的机器,现在已经非常常见了.即使是一块手机,也都配备了强劲的多核处

  • ant-design-vue 快速避坑指南(推荐)

    ant-design-vue是蚂蚁金服 Ant Design 官方唯一推荐的Vue版UI组件库,它其实是Ant Design的Vue实现,组件的风格与Ant Design保持同步,组件的html结构和css样式也保持一致. 用下来发现它的确称得上为数不多的完整的VUE组件库与开发方案集成项目. 本文主要目的是总结一些开发过程中比较耗时间去查找,文档中没有具体说明的常见问题,同时希望能给新上手此框架的同学提供一些参考作用. 1.Table对接后台返回数据 针对Table数据格式与后他接口返回数据格

  • .Net Core 2.2升级3.1的避坑指南(小结)

    写在前面 微软在更新.Net Core版本的时候,动作往往很大,使得每次更新版本的时候都得小心翼翼,坑实在是太多.往往是悄咪咪的移除了某项功能或者组件,或者不在支持XX方法,这就很花时间去找回需要的东西了,下面是个人在迁移.Net Core WebApi项目过程中遇到的问题汇总: 开始迁移 1. 修改*.csproj项目文件 <TargetFramework>netcoreapp2.2</TargetFramework> 修改为 <TargetFramework>net

  • .NET+PostgreSQL实践与避坑指南(推荐)

    简介 .NET+PostgreSQL(简称PG)这个组合我已经用了蛮长的一段时间,感觉还是挺不错的.不过大多数人说起.NET平台,还是会想起跟它"原汁原味"配套的Microsoft SQL Server(简称MSSQL),其实没有MSSQL也没有任何问题,甚至没有Windows Server都没问题,谁说用.NET就一定要上微软全家桶?这都什么年代了-- PG和MSSQL的具体比较我就不详细展开了,自行搜一下,这种比较分析文章很多.应该说两个RDBMS各有特色,MSSQL工具集庞大(大

  • python函数默认参数使用避坑指南

    目录 引言 verify 炸弹 测试接口的数据 原因 改进方案 引言 阿刁是一个自动化测试用例,从一出生他就被赋予终生使命,去测试一个叫登录的过程是否合理.他一直就被关在一个小黑屋里面,从来也没有出去过,小黑屋里还被关着其他的同胞,他们身上都捆着两个小袋子. 小黑屋里很难受,他们都想跑出去,可怎么也跑不出去.Python 是他们的总司令,有一次,python 告诉他们,你们就不要想着跑出去了,你们已经够幸运了,只有 8 个人用这个屋子,别的屋子都挤着 30 多个人呢! “这里还有其他的屋子?”

  • Vxe-Table开发中的各种坑以及避坑指南

    目录 背景: 开发阶段遇到的各种问题 全局size的问题 按钮的问题 合并单元格的问题 reload和load的问题 总结 背景: 由于公司要开发erp,采用了element-plus做为UI基础框架,但是回想往事点点滴滴,element-ui表格的种种表现令人痛心,于是跟leader商量之后决定使用Vxe-Table做表格插件,虽然element-plus在表格上也在大力优化,但就目前来看可用度确实不高,刚出了一个虚拟滚动,但看上去确实让人有点心急... 开发阶段遇到的各种问题 全局size的

  • Linux下安装Python3.6及避坑指南

    Python3的安装 1.安装依赖环境 Python3在安装的过程中可能会用到各种依赖库,所以在正式安装Python3之前,需要将这些依赖库先行安装好. yum -y install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel 2. 下载Python3源代码 下载Python3的

  • Python学习之异常处理的避坑指南

    目录 finally与return的执行顺序 else与return的执行顺序 总结 最终想了想,还是把这个章节单独拎出来,虽然字数不多. 在代码中,存在return也应当执行finally: 存在return时,else是不执行的: 无return时,else正常执行: 如果发生异常,则else也不执行 finally 与 return 的执行顺序 示例代码如下: class Test(object):     def division(self, num1, num2):         t

  • GoFrame框架使用避坑指南和实践干货

    目录 gf gen dao 设置参数可不传 model作为结构体类型 使用with关联取值而不是join 不使用结构体批量添加数据 主程序如下: gomeGoods.MainImgs的定义: 插入数据 gf gen dao 生成dao层的脚手架工具很好用,我遇到的坑是这样的: 生成的dao文件和同事们的不一致,生成文件成功,但是对应的Columns是空的,虽然有这个方法,但是方法内没有值.我的版本比同事们的略高,我一直以为是这个原因,各种降级和同事保持一致的版本后还是不行. 最终发现:是配置文件

随机推荐