奇怪的URL编码问题

10
我在使用Spring的UriComponentBuilder生成URL时遇到了一个奇怪的问题,即将加号+作为查询参数进行url编码。该API的文档说明如下:

日期必须采用W3C格式,例如'2016-10-24T13:33:23+02:00'。

目前为止还好,所以我使用以下代码(最小化)来生成URL:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX");
ZonedDateTime dateTime = ZonedDateTime.now().minusDays(1);
String formated = dateTime.format(formatter);

UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(baseUrl);
uriComponentsBuilder.queryParam("update", formated);
uriComponentsBuilder.build();
String url = uriComponentsBuilder.toUriString();

未编码的查询将如下所示:
https://example.com?update=2017-01-05T12:40:44+01

编码后的字符串结果为:
https://example.com?update=2017-01-05T12:40:44%2B01

在我的看法中,这是一个正确编码的查询字符串。请注意,在查询字符串末尾,+01 中的 + 被替换为 %2B

然而,当我使用编码后的 URL 向 API 发送请求时,我收到一个错误,说请求无法处理。

但是,如果在发送请求之前将 %2B 替换为 +,则可以正常工作:

url.replaceAll("%2B", "+");

据我理解,+符号是用来替代空格的。所以经过解码后服务器真正接收到的URL应该是:
https://example.com?update=2017-01-05T12:40:44 01
  • 我的假设是否正确?

  • 除了联系API所有者以使用正确编码的查询(而不是奇怪的非标准字符串替换),还有其他我可以做的事情吗?

更新:

根据规范RFC 3986(第3.4节),查询参数中的+符号不需要编码。

3.4. Query

The query component contains non-hierarchical data that, along with data in the path component (Section 3.3), serves to identify a
resource within the scope of the URI's scheme and naming authority
(if any). The query component is indicated by the first question
mark ("?") character and terminated by a number sign ("#") character
or by the end of the URI.

Berners-Lee, et al. Standards Track [Page 23] RFC 3986 URI Generic Syntax
January 2005

  query       = *( pchar / "/" / "?" )

The characters slash ("/") and question mark ("?") may represent data within the query component. Beware that some older, erroneous implementations may not handle such data correctly when it is used as the base URI for relative references (Section 5.1), apparently
because they fail to distinguish query data from path data when
looking for hierarchical separators. However, as query components
are often used to carry identifying information in the form of
"key=value" pairs and one frequently used value is a reference to
another URI, it is sometimes better for usability to avoid percent-
encoding those characters.

根据stackoverflow上的这个答案,Spring的UriComponentBuilder使用了这个规范,但实际上并没有完全遵循。那么一个新的问题就出现了,如何让UriComponentBuilder遵循规范?

2
@Prabu,您提供的参考资料并没有回答问题。 OP想知道为什么服务只接受未编码的加号,而不是如何编码或不编码,或者要使用哪些C#实用程序。 对编码规则的快速检查表明,必须编码保留字符的情况已经发生了轻微变化,但我还没有看到任何迹象表明应该对作为格式化日期字符串一部分使用的加号进行编码。 - arcy
这正是我所认为的,@arcy。 - baao
不,我已经记录了几次了。@arcy我确定它们和显示的完全一样。 - baao
urlBuilder.queryParam("redirect_uri", URLEncoder.encode(redirectURI,"UTF-8" )); 尝试指定字符集为UTF-8。 - Praburaj
在你编辑这篇文章之前,我已经发表了很多评论。你在9分钟前进行了编辑,而我在10分钟前发布了我的评论。 - Praburaj
显示剩余3条评论
4个回答

1
你可以使用 builder.build().toUriString()
这对我有用。
谢谢。

1
看起来,Spring 的 UriComponentBuilder 对整个 URL 进行编码,将 build() 方法中的编码标志设置为 false 没有任何效果,因为 toUriString() 方法总是对 URL 进行编码,因为它在 build() 之后显式调用了 encode()
/**
 * Build a URI String. This is a shortcut method which combines calls
 * to {@link #build()}, then {@link UriComponents#encode()} and finally
 * {@link UriComponents#toUriString()}.
 * @since 4.1
 * @see UriComponents#toUriString()
 */
public String toUriString() {
    return build(false).encode().toUriString();
}

目前对我来说解决方案是手动编码真正需要编码的内容。另一个解决方案可能是(可能也需要编码)获取URI并在进一步处理时使用它。

String url = uriComponentsBuilder.build().toUri().toString(); // returns the unencoded url as a string

当使用UriComponentsBuilder.fromUriString(baseUrl)并且该baseUrl已经包含编码查询参数时,此方法无法正常工作。在这种情况下,uriComponentsBuilder.build().toUri().toString();会再次对已编码的字符进行编码。特别是如果您使用类似于ServletUriComponentsBuilder.fromCurrentRequest().build().toString()的东西。 - Marcel Overdijk
现在看起来他们已经改变了行为,先调用encode(),然后是build()。如果您的查询字符串中有一个值如"{{{{",这将导致java.lang.IllegalArgumentException: Mismatched open and close braces。 https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java#L445 - rougou

0

编码 2017-01-05T12:40:44+01

会给你 2017-01-05T12%3A40%3A44%2B01

而不是像你建议的那样 2017-01-05T12:40:44%2B01

也许这就是服务器无法处理您的请求的原因,它是一个半编码日期。


2
不,那不是问题。 - baao

0

在org/springframework/web/util/HierarchicalUriComponents.java文件中

QUERY_PARAM {
        @Override
        public boolean isAllowed(int c) {
            if ('=' == c || '+' == c || '&' == c) {
                return false;
            }
            else {
                return isPchar(c) || '/' == c || '?' == c;
            }
        }
    }

不允许使用'+'字符,因此它将被编码


你可以使用 builder.build().toUriString()。 - Sanjay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接