URI中 +
号表示空格,这一点已经是常识般的存在。但是还是存在很多令人困扰的地方,并带来许多BUG。
首先是一些Java类,甚至是JDK自带的URI类令人迷惑的表现:
URI uri = new URI(null, null, "1%+ 2", "a=%+ b", null); System.out.println(uri); // 1%25+%202?a=%25+%20b URIBuilder uriBuilder = new URIBuilder(); uriBuilder.setPath("1%+ 2"); uriBuilder.addParameter("a","%+ b"); System.out.println(uriBuilder); // 1%25+%202?a=%25%2B+b
可以看到JDK的URI类和HttpClient的URIBuild类对同样的输入,编码不一样:
+
号不做编码,把空格编码为 %20
+
号不做编码,空格编码为 %20
+
号编码为 %2B
,空格编码为 +
可以看到,即使是应该被认为是客观正确的类,在处理 +
和空格上面,表现差距很大,大到生成的URI的语义不一样了。
那回头来看, URI中+号表示空格
,这个到底正确么?还是一个错误的常识?
查阅了一些资料,比较令人信服的说法是:
+ +
具体讨论见这篇 问答
。其中作者提到,RFC中,只是定义了URI的各个component允许存在哪些字符,但是对于特定的字符是否有特殊的含义,是没有做出说明的。而在HTTP的规范中,对 application/x-www-form-urlencoded
进行说明时,提到了 replace spaces with + and other special characters as in RFC1738
。
也就是说在URI的RFC规范中,没有定义 +
号表示空格,是指说了 +
可以在path和query中存在。而HTTP规范中,在参数编码时,提到了空格编码为 +
号,后者被大规模接受了。
而Java的URI来看起来是是完全按照URI的RFC规范来实现,其在编码各个component时,只有不允许存在的字符才会进行编码, +
是允许存在的,所以都没有进行编码。这样带来的问题是,query中的+号没有被编码,导致访问时异常。
URIBuilder的编码,在编码却query时是按照 application/x-www-form-urlencoded
规则进行的,所以编码出的地址是比较正确的。
回头看另外一个问题,就是常识中,我们认为 URI中+号表示空格
,但是其实path中的 +
号表示加号本身。这个就麻烦了,因为很多时候,我们在进行Java web编码的时候,会使用 URLEncoder
和 URLDecoder
类来处理uri的path,这两个类使用的是 application/x-www-form-urlencoded
规则,会用 +
号表示空格,这样在处理path时就不正确了。
细节好多,感觉分分钟都能踩坑,更别提很多人对URI编码没有概念,代码中大量存在用字符串拼接的方式拼接URI,这种方式的结果就是,带来大量的BUG。。。
但是即使是S3,也没有正确处理好URI。。。这个让我非常意外,在AWS的论坛( 帖子地址
)上有人提到S3对path中的 +
号是按照空格处理的!我试了一下还真是。。
# curl -i "https://xx.s3-eu-west-1.amazonaws.com/1+%2B2.txt" HTTP/1.1 200 OK # curl -i "https://xx.s3-eu-west-1.amazonaws.com/1%20%2B2.txt" HTTP/1.1 200 OK # curl -i "https://xx.s3-eu-west-1.amazonaws.com/1%2B%2B2.txt" HTTP/1.1 403 Forbidden
在这个帖子中,有AWS的员工回复,表示的确有这个问题,但是因为有大量的这种地址存在了,所以不会去修改。。。