整个错位来自这样一个事实,即如何将空间编码/解码为"+"
.
可以说,空间可以(正在)编码为"+"
or "%20"
。
例如,谷歌对搜索字符串这样做:
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语法组件中的位置。
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
)。
因此,如果您希望"+"
查询参数中的 char 不被解释为空格,则需要将其编码为"%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
.