RestTemplate中的URLEncode

Springboot使用RestTemplate发送Post请求postForEntity (application/x-www-form-urlencoded) Springboot使用RestTemplate发送Post请求postForEntity[application/x-www-form-urlencoded] 背景思路正文ServiceRestTemplate 使用 postForEntity 请求接口基于xml系列注解解析XML返回值XML的一些基本常识实体类与xml文件对应实体代码参考博客 背景 对接请求为(application/x-www-form-urlencoded),返回XML格式接口。 思路 ServiceRestTemplate 使用 阅读详情

跟进过程与结果在评论中

0. 起因

正常情况下 url 只会出现英文字母、数字和标点符号,特殊字符会在请求前进行 encode 操作,转化成合法的 url。 例如我们用浏览器在百度上搜索 += 时,浏览器实际上访问的是 https://www.baidu.com/s?wd=%2B%3D。
encode 操作其实是将需要转码的字符转为 16 进制,然后从右到左,取 4 位(不足 4 位直接处理),每 2 位做一位,前面加上 % ,编码成 %XY 格式。
常见特殊字符及编码后值如下:

字符编码
!%21
#%23
$%24
%%25
+%2B
@%40
:%3A
=%3D
?%3F

1. 经过

在最近的开发中恰好用到了 HTTP 的 GET 方式请求,并且参数中涉及到特殊字符。
在通信过程中发现,第三方服务收到的数据,与我发出的不一致。例如我发出的数据是 a+b= ,第三方收到的却是 a b= ,这就变得有意思了。因为 ‘+’ 和 ‘=’ 都是特殊字符,为什么一个可以正常收到,另一个却不行。
刚开始我们怀疑是日志打印的问题,可是在我开发环境上也能重现这个问题。然后我们开始怀疑是我发出去的请求没有进行 UrlEncode 处理,可是 ‘=’ 却可以被正常接收和处理,经过猜测和跟代码,最后我们终于找到了问题的原因。
找问题的过程如下:
因为我们使用了 Spring 的 RestTemplate 作为 http 的 client ,所以从 RestTemplate 入手。
具体的跟踪思路和源码如下:

  1. RestTemplate 中会有一个 uriTemplateHandler 来处理 uri。
private UriTemplateHandler uriTemplateHandler = new DefaultUriBuilderFactory();
@Override
@Nullable
public <T> T execute(String url, HttpMethod method, @Nullable RequestCallback requestCallback,
        @Nullable ResponseExtractor<T> responseExtractor, Object... uriVariables) throws RestClientException {

    URI expanded = getUriTemplateHandler().expand(url, uriVariables);
    return doExecute(expanded, method, requestCallback, responseExtractor);
}
  1. DefaultUriBuilderFactory 会使用 UriComponentsBuilder 来实例化自己。也就是说
/**
 * Default constructor without a base URI.
 * <p>The target address must be specified on each UriBuilder.
 */
public DefaultUriBuilderFactory() {
    this(UriComponentsBuilder.newInstance());
}
/**
 * Variant of {@link #DefaultUriBuilderFactory(String)} with a
 * {@code UriComponentsBuilder}.
 */
public DefaultUriBuilderFactory(UriComponentsBuilder baseUri) {
    Assert.notNull(baseUri, "'baseUri' is required");
    this.baseUri = baseUri;
}
  1. 而 UriComponentsBuilder 的 encode 处理默认会通过 HierarchicalUriComponents 完成
/**
 * Encode all URI components using their specific encoding rules and return
 * the result as a new {@code UriComponents} instance.
 * @param charset the encoding of the values
 * @return the encoded URI components
 */
@Override
public HierarchicalUriComponents encode(Charset charset) {
    if (this.encoded) {
        return this;
    }
    String scheme = getScheme();
    String fragment = getFragment();
    String schemeTo = (scheme != null ? encodeUriComponent(scheme, charset, Type.SCHEME) : null);
    String fragmentTo = (fragment != null ? encodeUriComponent(fragment, charset, Type.FRAGMENT) : null);
    String userInfoTo = (this.userInfo != null ? encodeUriComponent(this.userInfo, charset, Type.USER_INFO) : null);
    String hostTo = (this.host != null ? encodeUriComponent(this.host, charset, getHostType()) : null);
    PathComponent pathTo = this.path.encode(charset);
    MultiValueMap<String, String> paramsTo = encodeQueryParams(charset);
    return new HierarchicalUriComponents(
            schemeTo, fragmentTo, userInfoTo, hostTo, this.port, pathTo, paramsTo, true, false);
}

private MultiValueMap<String, String> encodeQueryParams(Charset charset) {
    int size = this.queryParams.size();
    MultiValueMap<String, String> result = new LinkedMultiValueMap<>(size);
    this.queryParams.forEach((key, values) -> {
        String name = encodeUriComponent(key, charset, Type.QUERY_PARAM);
        List<String> encodedValues = new ArrayList<>(values.size());
        for (String value : values) {
            encodedValues.add(encodeUriComponent(value, charset, Type.QUERY_PARAM));
        }
        result.put(name, encodedValues);
    });
    return result;
}

再往下跟踪 encodeUriComponent 方法,就找到了进行 UrlEncode 的地方

/**
 * Encode the given source into an encoded String using the rules specified
 * by the given component and with the given options.
 * @param source the source String
 * @param charset the encoding of the source String
 * @param type the URI component for the source
 * @return the encoded URI
 * @throws IllegalArgumentException when the given value is not a valid URI component
 */
static String encodeUriComponent(String source, Charset charset, Type type) {
    if (!StringUtils.hasLength(source)) {
        return source;
    }
    Assert.notNull(charset, "Charset must not be null");
    Assert.notNull(type, "Type must not be null");

    byte[] bytes = source.getBytes(charset);
    ByteArrayOutputStream bos = new ByteArrayOutputStream(bytes.length);
    boolean changed = false;
    for (byte b : bytes) {
        if (b < 0) {
            b += 256;
        }
        if (type.isAllowed(b)) {
            bos.write(b);
        }
        else {
            bos.write('%');
            char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
            char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
            bos.write(hex1);
            bos.write(hex2);
            changed = true;
        }
    }
    return (changed ? new String(bos.toByteArray(), charset) : source);
}

进行 encode 处理的就是下面的代码与刚才描述的一致,没有问题,那么问题很可能会出在判断是否需要进行 encode 操作的代码上。

bos.write('%');
char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
bos.write(hex1);
bos.write(hex2);

type.isAllowed(b) 就是进行判断操作,按照语义上来理解,返回值表示是否是符合规范的字符,也就是说如果不符合规范,则会被执行下面的 encode 操作。
这样看起来还没有问题,在跟下这个方法。发现有个枚举实现了这个方法,我们是处理的参数,正常情况下会调用到 QUERY_PARAM.isAllowed 方法。
看源码可以了解到,如果是 = 或者 & 符号直接会返回 false 表示不符合规范,如果是 / 或者 ? 符号直接返回 true 表示符合规范,其它情况由 isPchar 方法判断是否符合规范。

/**
 * Enumeration used to identify the allowed characters per URI component.
 * <p>Contains methods to indicate whether a given character is valid in a specific URI component.
 * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>
 */
enum Type {
    SCHEME {
        @Override
        public boolean isAllowed(int c) {
            return isAlpha(c) || isDigit(c) || '+' == c || '-' == c || '.' == c;
        }
    }
    ...
    QUERY_PARAM {
        @Override
        public boolean isAllowed(int c) {
            if ('=' == c || '&' == c) {
                return false;
            }
            else {
                return isPchar(c) || '/' == c || '?' == c;
            }
        }
    },
    FRAGMENT {
        @Override
        public boolean isAllowed(int c) {
            return isPchar(c) || '/' == c || '?' == c;
        }
    },
    URI {
        @Override
        public boolean isAllowed(int c) {
            return isUnreserved(c);
        }
    };

但我们再跟下 isPchar 方法就会发现,这个方法并不是判断字符是否符合 url 的规范。恰恰相反,它是判断字符是否是规定的特殊字符。

/**
 * Indicates whether the given character is in the {@code sub-delims} set.
 * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
 */
protected boolean isSubDelimiter(int c) {
    return ('!' == c || '$' == c || '&' == c || '\'' == c || '(' == c || ')' == c || '*' == c || '+' == c ||
            ',' == c || ';' == c || '=' == c);
}
...
/**
 * Indicates whether the given character is in the {@code unreserved} set.
 * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
 */
protected boolean isUnreserved(int c) {
    return (isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c);
}

/**
 * Indicates whether the given character is in the {@code pchar} set.
 * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986, appendix A</a>
 */
protected boolean isPchar(int c) {
    return (isUnreserved(c) || isSubDelimiter(c) || ':' == c || '@' == c);
}

2. 结论

也就是说,除了 ‘=’ ‘&’ 符号之外的其它特殊字符,在参数中出现时都不会被执行 encode。不知道这个地方是开发者的纰漏还是我理解的问题,已经尝试在联系开发者中。
暂时的解决方案是使用 java.url 包下面的 URLEncoder 来进行 encode 操作,示例代码如下:

RestTemplate restTemplate = new RestTemplate();
// 准备参数
String url = "http://a.com";
Map<String, String> params = Collections.singletonMap("p1", "+=");
// 构造uri
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(url);
for (Map.Entry<String, String> entry : params.entrySet()) {
    uriComponentsBuilder.queryParam(entry.getKey(), entry.getValue());
}
URI uri = uriComponentsBuilder.build().encode().toUri();
// 执行请求
ResponseEntity<String> response = restTemplate.getForEntity(uri, String.class);
远程调用RestTemplate使用避坑指南 一、前言介绍 RestTemplate是Spring中用于远程接口调用的工具类,它是Apache的HttpClient的模板封装,使用起来非常方便,本文将讲述这两天自己在使用RestTemplate过程中遇到的问题,当然这些问题也是由于自己对RestTemplate工具类了解不够全面不够透彻造成的,希望自己遇到的这些问题能为大家提前避雷或是遇到类似问题时的一个解决参考。 ... 阅读详情

相关推荐

STM8库函数开发手册(3) //UART1、内部EEPROM、FLASH操作库、IIC(硬、软)

STM8S 微控制器家族的通用同步异步收发器(UART1,UART2 或者UART3)提供了一种灵活的方法与使用工业标准NRZ 异步串行数据格式的外部设备之间进行全双工数据交换。STM8 的UART 提供范围的波特率选择,并且支持多处理器通讯。UART 也支持LIN(局部互联网)协议版本1.3,2.0,和2.1 以及在主模式下的J2602。 UART1 和UAR...

finedayforu的博客 2520

【开发心得】RestTemplate 自动 URLEncode导致特殊符号被转码问题

前言: 最近在做抖音视频上传对接,场景是后端方式进行远程调用抖音开放能力,涉及到一个小问题,即分片上传时,upload_id需要encoder。ok,那么,手动给这个参数进行encode,然后问题来了,笔者使用的是resttemplate的postForObject()方法,第一个参数使用String方式传递的参数,执行该方法的时候,发现%符号被二次encode了,好家伙。(一开始没注意到,后来debug=true开启之后,看到了详细请求信息,注意到了这个问题) 参考,使用Ur...

清风唱诗人的博客 1871

均匀分布产生高斯分布

均匀分布产生高斯分布 文章目录均匀分布产生高斯分布简介方法和证明代码效果引用 简介 方法和证明 代码 效果 引用

gy_Rick的博客 5317

RestTemplate的Get请求参数urlencode编码加密后请求异常的解决方案

项目场景: 项目中对外请求的一个接口,里面的参数需要进行urlencode编码加密后进行发送,这时候出现一个莫名其妙的问题,同样的url和同样的参数,使用postman请求返回正常,使用RestTemplate却一直报错,无法得到正确结果。 如果接口是我们自己的,进去服务端看下日志,问题就能直接定位,但由于接口是外部的,对于我们来说是黑盒子,只能摸石头过河,从而找到解决方案。 问题描述: 出现问题的关键点在于,get参数的里包含了需要urlencode编码的+、%等特殊符号,导致RestTemplate

xs_challenge的博客 8366

基于微信小程序的答题系统

学习视频:《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!5812)]《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

2401_83330193的博客 499

从零开始:H20服务器上DeepSeek R1 671B大模型部署与压力测试全攻略

最近,我有幸在工作中接触到了DeepSeek R1 671B模型,这是目前中文开源领域参数量最大的高质量模型之一。DeepSeek团队在2024年推出的这款模型,以其惊人的6710亿参数量和出色的推理性能,引起了业界广泛关注。作为一名AI基础设施工程师,我有机会在H20服务器上部署这个庞然大物,并对其进行了全面的压力测试。这篇文章将详细记录我的部署过程和性能测试方法,希望能为大家提供一些参考。💡为什么选择DeepSeek R1?超大规模参数量(671B)优秀的中英文理解能力开源可商用的许可证。

wangli的博客 1万+

[Java] 使用restTemplate发送请求会自动对参数进行编码

背景 在一个项目中我使用restTemplate进行get请求,并在url中携带参数 String urlEncodedText = URLEncoder.encode(text, "UTF-8"); String url = "http://xxx?&word=" + urlEncodedText; ResponseEntity<byte[]> response = restTemplate.exchange(url, HttpMethod.GET, null, byte[].clas

petrel2015的博客 1832

VisualStudio2022启动报错

把Users\...\AppData\Local\Microsoft\VisualStudio\11.0\ComponentModelCache中的Microsoft.VisualStudio.Default.cache这个文件移除,再重启vs,各项功能都能正常使用了。第一次使用方法1有效果,不知道什么原因后面又报错再删除缓存文件无效了,最后使用方法2启动VS成功。vs2022启动时,报错弹框,在ActivityLog查看具体报错信息,报错原因未知。网上搜索到两种解决方法。方法1:删除缓存文件。

面朝大海,春暖花开 3938

Spring-RestTemplate之urlencode参数解析异常全程分析

对接外部的一个接口时,发现一个鬼畜的问题,一直提示缺少某个参数,同样的url,通过curl命令访问ok,但是改成RestTemplate请求就不行;因为提供接口的是外部的,所以也无法从服务端着手定位问题,特此记录下这个问题的定位以及解决过程 I. 问题复现 首先我们是通过get请求访问服务端,参数直接拼接在url中;与我们常规的get请求有点不一样的是其中一个参数要求url编码之后传过去。 因为...

weixin_34320724的博客 1125

Close-By Site Assignment,门店近邻关系在 SAP Retail 物料搜索里的真实价值

SAP零售模块中的"Close-By Site Assignment"功能常被低估,但它在门店现场业务中极为关键。该功能允许预先定义各门店间的近邻关系,当导购或店长查询商品时,系统会自动展示附近门店的库存、价格和促销信息。与单纯的地理距离不同,这种近邻关系可基于调拨频率、业务关联度等业务逻辑定义。维护入口在事务码WSD_CBP中,支持按应用场景(如销售、采购)分别设置不同近邻关系。该功能不同于组织结构,专注于增强搜索体验,特别适合多门店运营场景,能有效提升跨店查询效率。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 4851

uniapp - 实现扫码功能自定义扫描二维码/条码界面+手电筒轻触照亮+扫码屏幕添加手动输入框,手机扫描二维码并跳转过程,调用手机原生摄像头进行扫码且自动识别执行事件(uniApp安卓苹果App端)

uniapp,app端,安卓app,苹果ios,uniapp扫码识别,二维码展示和扫码,uniapp如何实现扫码功能,uni-app实现扫码功能,二维码和一维码/条码扫描,UniApp实现扫描二维码,uniapp开发原生app页面的扫码功能,自定义扫码界面,uniapp怎么调用扫一扫功能,app自定义扫码界面,uniapp 自定义扫一扫页面,扫码手电筒功能,自定义扫描框,扫码报错怎么办,扫码黑屏,扫一扫摄像头黑屏,uniapp扫码原生插件,最新扫码组件,连续扫码,uniapp APP扫码,扫码后如何处理跳转

✅ 精选推荐文章 1万+

Spring RestTemplate发送请求时 自动对参数进行urlencode的问题

我们用Java开发项目时,发送请求都是用的RestTemplate。最近和其他部门合作时,我们需要请求他们的一个http接口。两边协议都确定好后,发现联调不通。后来发现是我们这边发出的请求,到达对方那边时,他们接收到的是经过了urlencode后的结果,通过wireshark抓包也看到确实发出的请求是被urlencode的。 我们这边的进程,并没有显式调用urlencode相关的方法,因此猜测是RestTemplate自动给我们进行了urlencode。网上查找资料,发现这两篇文章讲得很详细: h.

wangooo的博客 6546

Python爬虫:抓取专利数据并分析专利趋势

本文展示了如何使用 Python 爬虫技术抓取中国专利数据,并对数据进行了清洗、分析和趋势预测。通过分析专利数据,企业和科研机构可以了解行业的技术发展趋势,进行技术创新战略规划。未来,我们可以进一步扩展爬虫的功能,抓取更多专利数据,增加多维度的数据分析,如技术领域、地域分布等。同时,可以尝试使用深度学习模型(如 LSTM)进行时间序列预测,以获得更准确的专利趋势预测。

2201_76125261的博客 1787

spring restTemplate会对String类型的url中的特殊字符进行转义

/ 通过UriComponentsBuilder创建URI对象,这样RestTemplate不会自动进行urlencode。通过讲String类型的url转为URL来避免restTemplate的二次特殊符号转义。1、通过抓包实际获取到的https 的路径为。

tonysh_zds的博客 287

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

一个奇怪的urlencode转码问题

问题现象 问题源自于使用tiger进行图查询时,要用到一个get请求,参数是复杂的json字符串,含有空格,逻辑运算符等若干字符。 当服务器端接收时,总是无法正常解析。 神奇的是使用httpclient原生的方式没有这个问题,使用postman也没问题。但是使用spring的restTemplate就偏偏有这问题。 反复调试也没有完全搞清楚。困扰了两三天。 具体问题 下...

dianfuwo9488的博客 481

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

【bugfix】密文传输+UrlEncode的坑

背景现象 服务之间的报文传输需要进行加密改造,发现上游发送过来表单请求(application/x-www-form-urlencoded)中的SM4密文解密不了 原因 客户端和服务端打印密文如下: 客户端打印密文: U000AAEAAAAyAAAAAgAAAEAAAADYnadFTDMUfEBTv/STdyFPyUwhDRbdI+syV3Z9yvFh/hCNCugs5FUoCs4++I...

opi 914

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

【无标题】

restTemplate进行服务间调用,发现签证中的+号总是变为了空格

qq_40867461的博客 206

关于java发送http请求时中文乱码的一种解决办法

https://my.oschina.net/angerbaby/blog/207774摘要: 在jsp中常见的乱码解决办法无外乎是关于get和post两种方式的,但只有切实地在实践中使用时才会注意或者说注重到其他方式。例如,在http请求头中传送中文参数,出现乱码,如何解决?

qq_27361945的博客 1万+

restTemplate遇到的编码问题

背景:之前用restTemplate做网络间的请求,没遇到过问题。今天先是出现了中文乱码的问题,而后又出现了特殊字符丢失的问题,于是查找资料及翻看源码,将问题解决也顺便记录下。 问题一:中文乱码 描述: 在创建课件时,使用GET方法传递类型和标题两个参数到服务器,服务器返回一个课件编号。类型是固定数字1,不存在问题,而标题则是用户输入字符串,也就是任意字符串。发现输入汉字的时候,结果网...

zhuxingKevin的博客 4132

spring RestTemplate请求异常,curl正常原因分析

转自:https://my.oschina.net/u/566591/blog/3028537 对接外部的一个接口时,发现一个鬼畜的问题,一直提示缺少某个参数,同样的url,通过curl命令访问ok,但是改成RestTemplate请求就不行;因为提供接口的是外部的,所以也无法从服务端着手定位问题,特此记录下这个问题的定位以及解决过程 I. 问题复现 首先我们是通过get请求访问服务端,参数...

duanqiaocanyue2012的专栏 5064

java http请求 乱码问题_关于java发送http请求时中文乱码的一种解决办法

在jsp中常见的乱码解决办法无外乎是关于get和post两种方式的,但只有切实地在实践中使用时才会注意或者说注重到其他方式。例如,在http请求头中传送中文参数,出现乱码,如何解决?实际场景:使用Spring提供的RestTemplate向WebService发送put请求,使用HttpHeader类装载需要传递的参数(包括中文)。请求端系统使用的是utf-8编码,而服务端使用的是gbk编码,使用...

weixin_39826080的博客 2436

RestTemplate使用过程中你可能需要注意的点

RestTemplate在get请求设置参数时,url中后跟的参数一定不要是encode之后的param值,因为他会再次encode 一个url可以多次encode,每次结果不一 附上:原生jack变fastjson RestTemplate restTemplate = new RestTemplate(); List&lt;HttpMessageCon...

BoomMan 7558

RestTemplate转码bug

发现一个关于HTTP的Get请求的罕见bug。 转码问题的背景 需要向tigergraph服务端发送一个复杂的get请求,参数只有一个,但是参数的值是一个复杂json 服务端收到的值始终是不正常的值。观察发现,不正常地方在于服务端本应解析为空格的地方都变成了加号(+)。 以为是代码写得有问题,然后使用HTTPclient的原生的方式发起请求: public stati...

dianfuwo9488的博客 545
上一篇: authorize(基于注解的权限认证框架)
sunbufu
博客等级 码龄11年 17粉丝 36原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值