java.net.URI和查询参数值中的百分数

7
System.out.println(
    new URI("http", "example.com", "/servlet", "a=x%20y", null));

结果是http://example.com/servlet?a=x%2520y,其中查询参数值与提供的不同。奇怪的是,这确实遵循了Javadoc的规定:
“这些构造函数始终将百分比字符('%')引用。”
我们可以传递解码后的字符串a=x y,然后我们会得到一个合理的结果a=x%20y
但是,如果查询参数值包含“&”字符怎么办?例如,如果该值本身是具有查询参数的URL,则会发生这种情况。看看这个(错误的)查询字符串:a=b&c。这里必须转义和号(a=b%26c),否则这可能被视为查询参数a=b和一些垃圾(c)。如果我将其传递给URI构造函数,则会对其进行编码,并返回错误的URL:...?a=b%2526c 这个问题似乎使java.util.URI无用。我在这里错过了什么吗?
答案摘要
java.net.URI确实知道URI的查询部分的存在,但它不理解查询部分的内部结构,这可能因每种方案而异。例如,java.net.URI不理解HTTP查询部分的内部结构。如果java.net.URI将查询视为不透明字符串并且不更改它,则这不会成为问题。但它尝试应用一些通用的百分比编码算法,这会破坏HTTP URL。
因此,尽管有构造函数,但我无法使用URI类可靠地组装URL的各个部分。我还要提到,从Java 7开始,相对路径操作的实现非常有限,仅在一个URL是另一个URL的前缀时才起作用。这两个功能(以及它们的更精简的接口)是我对java.net.URI感兴趣的原因,但它们都不适用于我。
最后,我使用java.net.URL进行解析,并编写了代码来从部分组装URL并使两个URL相对。我还检查了Apache HttpClient URIBuilder类,尽管它确实理解HTTP查询字符串的内部结构,但截至4.3,在处理整个查询部分时与java.net.URI一样存在编码问题。
4个回答

1
我发现唯一的解决办法是使用单参数构造函数和方法。请注意,您必须使用URI#getRawQuery()来避免解码%26。例如:
URI uri = new URI("http://a/?b=c%26d&e");
// uri.getRawQuery() equals "b=c%26d&e"

uri = new URI(new URI(uri.getScheme(), uri.getAuthority(),
        uri.getPath(), null, null) + "?f=g%26h&i");
// uri.getRawQuery() equals "f=g%26h&i"

uri = uri.resolve("?j=k%26l&m");
// uri.getRawQuery() equals "j=k%26l&m"
// uri.toString() equals "http://a/?j=k%26l&m"

1
查询字符串
a=b&c

在URI中使用“不正确”是可以的。URI通用语法的RFC文件指出了这一点。

The query component is a string of information to be interpreted by the resource.

  query         = *uric

Within a query component, the characters ";", "/", "?", ":", "@",
"&", "=", "+", ",", and "$" are reserved.

在查询字符串中,字符&是完全有效的(uric代表保留、标记和字母数字字符)。RFC还规定:
许多URI包括由特定特殊字符组成或分隔的组件。这些字符被称为“保留字符”,因为它们在URI组件中的使用仅限于其保留目的。如果URI组件的数据与保留目的冲突,则必须在形成URI之前对冲突数据进行转义。
因为&是有效但保留的,所以由用户确定是否将其编码。
您称之为“查询参数”的内容不是URI的功能,因此URI类没有理由(也不应该)支持它。
相关:

是的,a=b&c在语法上是有效的,但它并不意味着显然想要的查询参数a的值为b&c。必须转义 "&" 符号,但 URI 在 toString() 中返回混乱的 URL。让我们看一个更现实的例子,在 return 参数中传递相对 URL mypage?hello=world。完整有效的 URL 是:http://example.com/some?return=mypage%3Fhello%3Dworld。 我应该在 java.net.URI 多参数构造函数中传递什么来获得这个完整的 URL? - Hontvári Levente
@HontváriJózsefLevente 查询参数在HTTP上下文中是相关的。但URI不仅在HTTP上下文中是相关的。查询参数由HTTP服务器解释。在URI中,它们毫无意义,因此您将无法使用URI类进行任何特殊格式化。 - Sotirios Delimanolis
java.net.URI 不需要理解查询部分的内部结构。例如,如果它的多参数构造函数不改变我传递给它们的完全有效的查询字符串,那么这就足够了。 - Hontvári Levente
@HontváriJózsefLevente 你传递了哪个完全有效的查询字符串并且它改变了它?a=x%20y不是一个有效的查询字符串。请注意,RFC规定:“在正常情况下,仅在从其组成部分生成URI的过程中对URI中的八位组进行百分比编码”。因此,a=x%20y变成了a=x%2520y。 javadoc指出,除了一些小偏差外,java.net.URI实例表示URI引用。 - Sotirios Delimanolis
1
& 可以作为查询组件内的分隔符和数据字符。在后一种情况下,它必须进行百分号编码。因为 URI 不理解查询组件的内部结构,所以它无法确定 & 是分隔符还是数据字符。因此,正如您所写的那样,由用户即我的代码来决定。现在,如果我对不是分隔符而是数据字符的 & 进行百分号编码,则 URI.toString() 将返回错误的字符串。我仍然不知道应该传递什么参数给 URI 多参数构造函数才能获得我上面写的示例 URL。 - Hontvári Levente
@HontváriJózsefLevente 它只能在 HTTP 请求的上下文中进行编码。URI 类并不知道您想要将其用于哪种上下文,因此它不会对其进行编码,因为这不是它的工作。您不能使用 URI 构造函数来实现您想要的功能。 - Sotirios Delimanolis

0

我知道的单一解决方案是反射(请参见https://blog.stackhunter.com/2014/03/31/encode-special-characters-java-net-uri/

URI uri = new URI("http", null, "example.com", -1, "/accounts", null, null);
Field field = URI.class.getDeclaredField("query");
field.setAccessible(true);
field.set(uri, encodedQueryString);
//clear cached string representation
field = URI.class.getDeclaredField("string");
field.setAccessible(true);
field.set(uri, null);

-1

使用URLEncoder.encode()方法,例如:

URLEncoder.encode("a=x%20y", "ISO-8859-1");

我会使用一些java.net.URI特定的功能:具有查询和权限参数的构造函数以及相对化操作。 - Hontvári Levente

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