整个错位来自这样一个事实,即如何将空间编码/解码为"+" 的非标准做法。
可以说空间可以(正在)编码为"+" 或"%20"。
例如 Google 对搜索字符串这样做:
https://www.google.com/search?q=test+my+space+delimited+entry
rfc1866, section-8.2.2 声明 GET 请求的查询部分应编码为 'application/x-www-form-urlencoded'。
所有表单的默认编码是`application/x-www-form-
urlencoded'。表单数据集在此媒体类型中表示为
如下:
- 表单字段名称和值被转义:空格
字符被替换为“+”。
另一方面,rfc3986 声明 URL 中的空格必须使用 "%20" 进行编码。
这基本上意味着对空格进行编码有不同的标准,具体取决于它们在 URI syntax components 中的位置。
foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
| _____________________|__
/ \ / \
urn:example:animal:ferret:nose
基于这些评论,我们可以说明在 URI 中的 GET http 调用中:
-
"?" 之前的空格需要编码为"%20"
- 查询参数中
"?"后面的空格需要编码为"+"
- 这意味着
"+"符号需要在查询参数中编码为"%2B"
Spring 实现遵循 rfc 规范,这就是为什么当您在查询参数中发送 "+412386789" 时,"+" 符号被解释为空白字符,并以“412386789”。
看着:
final URI uri = UriComponentsBuilder.fromHttpUrl("http://localhost")
.port(port)
.path("/events")
.queryParams(params)
.build()
.toUri();
你会发现:
"foo#bar@quizz+foo-bazz//quir." 编码为"foo%23bar@quizz+foo-bazz//quir."
符合规范 (rfc3986)。
因此,如果您希望查询参数中的 "+" 字符不被解释为空格,则需要将其编码为 "%2B"。
您发送到后端的参数应如下所示:
params.add("id", id);
params.add("device", device);
params.add("phoneNumber", "%2B225697845");
params.add("timestamp", "2019-03-25T15%3A09%3A44.703088%2B02%3A00");
params.add("value", "foo%23bar%40quizz%2Bfoo-bazz%2F%2Fquir.");
为此,您可以在将参数传递给地图时使用UrlEncoder。当心 UriComponentsBuilder 双重编码你的东西!
您可以通过以下方式获得正确的 URL:
final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("id", id);
params.add("device", device);
String uft8Charset = StandardCharsets.UTF_8.toString();
params.add("phoneNumber", URLEncoder.encode(phoneNumber, uft8Charset));
params.add("timestamp", URLEncoder.encode(timestamp.toString(), uft8Charset));
params.add("value", URLEncoder.encode(value, uft8Charset));
final URI uri = UriComponentsBuilder.fromHttpUrl("http://localhost")
.port(port)
.path("/events")
.queryParams(params)
.build(true)
.toUri();
请注意,将“true”传递给build() 方法会关闭编码,因此这意味着来自URI 部分的方案、主机等不会被UriComponentsBuilder 正确编码。