8

使用 Java,我想剥离片段标识符并对一组不同的 URI 进行一些简单的规范化(例如,小写方案、主机)。输入和输出 URI 在一般 HTTP 意义上应该是等效的。

通常,这应该是直截了当的。但是,对于像 URI 这样http://blah.org/A_%28Secret%29.xml#blah的百分比编码的 URI (Secret), 的行为java.util.URI让生活变得困难。

归一化方法应该http://blah.org/A_%28Secret%29.xml从 URI中返回,http://blah.org/A_%28Secret%29.xml并且http://blah.org/A_(Secret).xml在解释中不等效 [§2.2; RFC3968 ]

所以我们有以下两种归一化方法:

URI u = new URI("http://blah.org/A_%28Secret%29.xml#blah");
System.out.println(u);
        // prints "http://blah.org/A_%28Secret%29.xml#blah"

String path1 = u.getPath();      //gives "A_(Secret).xml"
String path2 = u.getRawPath();   //gives "A_%28Secret%29.xml"


//NORMALISE METHOD 1
URI norm1 = new URI(u.getScheme().toLowerCase(), u.getUserInfo(), 
                      u.getHost().toLowerCase(), u.getPort(), path1, 
                      u.getQuery(), null);
System.out.println(norm1);
// prints "http://blah.org/A_(Secret).xml"

//NORMALISE METHOD 2
URI norm2 = new URI(u.getScheme().toLowerCase(), u.getUserInfo(),
                      u.getHost().toLowerCase(), u.getPort(), path2, 
                      u.getQuery(), null);
System.out.println(norm2);
// prints "http://blah.org/A_%2528Secret%2529.xml"

正如我们所见,URI 在没有片段标识符的情况下被解析和重建。

但是,对于方法 1,u.getPath()返回一个未编码的 URI,它会更改最终的 URI。

对于方法 2,u.getRawPath()返回原始路径,但是当传递给URI构造函数时,Java 决定添加双重编码。

这感觉就像一个中国手指陷阱。

所以两个主要问题:

  • 为什么java.util.URI觉得需要玩编码?
  • 如何在不摆弄原始百分比编码的情况下实现这种规范化方法?

(我宁愿不必实现java.util.URI非平凡的解析/连接方法。)


编辑:这是来自URIjavadoc的更多信息。

  • 单参数构造函数要求在其参数中引用任何非法字符,并保留任何转义的八位位组和其他存在的字符。

  • 多参数构造函数根据它们出现的组件的要求引用非法字符。这些构造函数始终引用百分比字符 ('%')。保留任何其他字符。

  • getRawUserInfo、getRawPath、getRawQuery、getRawFragment、getRawAuthority 和 getRawSchemeSpecificPart 方法以原始形式返回其对应组件的值,而不解释任何转义的八位字节。这些方法返回的字符串可能同时包含转义的八位字节和其他字符,并且不会包含任何非法字符。

  • getUserInfo、getPath、getQuery、getFragment、getAuthority 和 getSchemeSpecificPart 方法解码其相应组件中的任何转义八位字节。这些方法返回的字符串可能同时包含其他字符和非法字符,并且不会包含任何转义的八位位组。

  • toString 方法返回一个包含所有必要引号但可能包含其他字符的 URI 字符串。

  • toASCIIString 方法返回不包含任何其他字符的完全引用和编码的 URI 字符串。

因此,如果类内部没有弄乱 URL 编码,我就不能使用多参数构造函数URI。呸!

4

2 回答 2

10

因为java.net.URI是在java 1.4(2002年推出)中引入的,它基于RFC2396,它将'('和')'视为不需要转义的字符,即使它被转义,语义也不会改变,而且它甚至说除非必要,否则不应逃避它(§2.3,RFC2396)。

但是 RFC3986(2005 年发布)改变了这一点,我猜 JDK 的开发人员决定不改变java.net.URI现有代码的兼容性。

通过随机搜索,我发现Jena IRI看起来不错。

public class IRITest {
public static void main(String[] args) {
    IRIFactory factory = IRIFactory.uriImplementation();
    IRI iri = factory.construct("http://blah.org/A_%28Secret%29.xml#blah");
    ArrayList<String> a = new ArrayList<String>();
    a.add(iri.getScheme());
    a.add(iri.getRawUserinfo());
    a.add(iri.getRawHost());
    a.add(iri.getRawPath());
    a.add(iri.getRawQuery());
    a.add(iri.getRawFragment());
    IRI iri2 = factory.construct("http://blah.org/A_(Secret).xml#blah");
    ArrayList<String> b = new ArrayList<String>();
    b.add(iri2.getScheme());
    b.add(iri2.getRawUserinfo());
    b.add(iri2.getRawHost());
    b.add(iri2.getRawPath());
    b.add(iri2.getRawQuery());
    b.add(iri2.getRawFragment());

    System.out.println(a);
    //[http, null, blah.org, /A_%28Secret%29.xml, null, blah]
    System.out.println(b);
    //[http, null, blah.org, /A_(Secret).xml, null, blah]
}
}
于 2012-03-03T16:53:08.977 回答
4

请注意[§2.2 末尾的这段话;RFC3968]

生成 URI 的应用程序应该对与保留集中的字符相对应的数据字节进行百分比编码,除非 URI 方案特别允许这些字符表示该组件中的数据。如果在 URI 组件中找到保留字符并且不知道该字符的定界角色,则必须将其解释为表示对应于 US-ASCII 中该字符编码的数据八位字节。

因此,只要方案是 http 或 https,编码就是正确的行为。

尝试使用toASCIIString方法而不是toString打印 URI。例如:

System.put.println(norm1.toASCIIString());
于 2012-02-23T19:37:15.240 回答