Java 中常见的 URL 问题及解决方案 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
lampbrother
V2EX    Java

Java 中常见的 URL 问题及解决方案

  •  
  •   lampbrother 2016-07-18 11:35:02 +08:00 3040 次点击
    这是一个创建于 3380 天前的主题,其中的信息可能已经有所发展或是发生改变。

    URL 无处不在,不过似乎开发人员并没有真正地理解它们,因为在 Stack Overflow 上经常看到有人在问如何正确的创建一个 URL 。想知道 URL 语法是如何工作的,可以看下兄弟连教育( www.lampbrother.net )总结的这篇文章,非常不错。

    本文不会深入介绍 URL 的全部语,这是我们发布的一个用于正确地创建 URL 的 Java 库。

    问题 1 : Java 的 URLEncoder 这个类不仅名字取的很差,而且它的文档上来第一句话就不太对头。

    Utility class for HTML form encoding. 你可能正纳闷为什么叫 URLEncoder 呢,看到这行就彻底无语了。

    如果你读过兄弟连教育( www.lampbrother.net )的那篇博文,现在你应该明白了,你没法通过这个类将一个 URL 串奇迹般地转化成一个安全,正确编码的 URL 对象,当然如果你没做足功课的话,这里有个小例子可以帮助你理解下。

    假设你有个 HTTP 的服务端点,它接受一个查询参数 p , p 的值就是要查找的字符串。如果你搜索"You & I"这个串的话,你第一次创建的搜索的 URL 可能是这样:。这个当然没法工作,因为&是分隔查询参数 name/value 对的分隔符。如果你拿到这个错乱的 URL 串的话,你对它简直束手无策,因为首先你就没法正确的解析它。

    那好,我们来使用下 URLEncoder 。 URLEncoder.encode("You & I", "UTF-8")是结果是 You+%26+I 。这个%26 解码之后就是&,而+号在查询串中代表的就是空格,因此这个 URL 是能正常工作的。

    现在假设你想使用你的查询串来拼接 URL 路径,而不是放到 URL 参数里面。很明显,是错误的。不幸的是, URLEncoder.encode()的结果也是错的。解码后会得到 /search/You+&+I ,因为+号在 URL 路径中是不会解析成空格的。

    URLEncoder 或许能满足你的一些场景。但不幸的是,它这个过于通用的名字使得开发人员很容易误用它。因此最好的方法就是不要使用它,免得后面别的开发人员在你的基础上又使用了别的功能时犯错(除非,你真的是在进行"HTML 表单编码")。

    问题 2 : Groovy HttpBuilder 以及 Java 的 URI HTTP Builder 是 Groovy 的一个 HTTP 客户端库。

    创建一个普通的 GET 请求非常简单:

    new HTTPBuilder.request(Method.GET) { uri.path = "/foo" } 这段代码会发送 GET /foo HTTP/1.1 到服务端(你可以运行 nc -l -p 18080 之后再执行这段代码验证下)。

    我们来试一下包含空格的 URL 。

    new HTTPBuilder.request(Method.GET) { uri.path = "/foo bar" } 这个发送的是 GET /foo%20bar HTTP/1.1 ,看起来还不错。

    现在假设我们的路径中有一段就叫做 foo/bar 。这可不能简单地发送 foo/bar 就完了,因为这会被认为成路径中包含两段, foo 和 bar ,那我们试下 foo%2Fbar 吧(把 /替换成对应的编码)。

    new HTTPBuilder.request(Method.GET) { uri.path = '/foo%2Fbar' } 这个发送的则是 GET /foo%252Fbar HTTP/1.1 。这可不太妙。%2F 中的%被重复编码了,这样解码后拿到的路径是 foo%2Fbar 而不是 foo/bar 。这里其实真正要怪的是 java.net.URI ,因为这个 HTTPBuilder 里的 URIBuilder 类用的就是它。

    上述代码中的配置闭包中暴露的 uri 属性的类型是 URIBuilder 。如果你通过 uri.path = ...来更新 uri 的 path 属性的话,它最终会调用 URI 的一个构造方法,这个方法对于传入的 path 属性是这么描述的:

    如果提供了 path 参数,则将它追加到 URL 后面。 path 里面的字符,只要不是非保留,标点,转义及其它分类(译注:这几个分类在 RFC 2396 中有详细说明)的字符,同时又不是 /或者 @号的,都会进行编码。 这个做法意义不大,因为如果未编码前的文本包含特殊字符的话,它就无法生成一个正确编码的路径分段。换句话说,“我会对这个字符串进行编码,而编码之后它就是正确的”,这当然是个谬论,而 URI 正好是这个谬论的牺牲品。如果字符串已经正确编码了,那就没什么问题,如果不是的话,那就完蛋了,因为这个串没法解析。事实上,文档里说的不会对 /号转义的意思是,它假设 path 串已经正确地编码了(就是说正确地使用 /来分隔路径),同时又还没有正确地编码(除了 /外的其它部分仍然需要进行编码)。

    如果 HTTPBuilder 不使用 URI 类的这个存在缺陷的功能就好了,当然了,如果 URI 自己本身没问题的话就更好了。

    正确的做法 我们写了这个 url-builder ,它能帮助开发人员方便的拼接各种类型的 URL 。它遵循了篇首那几个参考资料中的编码规范,同时它还提供了流式的 API 。下面这个使用示例几乎可以涵盖所有的使用场景了:

    UrlBuilder.forHost("http", "foo.com") .pathSegment("with spaces") .pathSegments("path", "with", "varArgs") .pathSegment("&=?/") .queryParam("fancy + name", "fancy?=value") .matrixParam("matrix", "param?") .fragment("#?=") .toUrlString()

    这个例子演示了 URL 各个部分的不同的编码规则,比如说在路径中未编码的&=是允许的,而?/则是需要编码的,但在查询参数中=是需要编码的,但?号则不需要,因为这里已经是查询串的部分了。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2485 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 05:17 PVG 13:17 LAX 22:17 JFK 01:17
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86