lampbrother
V2EX  ›  Java

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

  •  
  •   lampbrother · Jul 18, 2016 · 3366 views
    This topic created in 3594 days ago, the information mentioned may be changed or developed.

    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 各个部分的不同的编码规则,比如说在路径中未编码的&=是允许的,而?/则是需要编码的,但在查询参数中=是需要编码的,但?号则不需要,因为这里已经是查询串的部分了。

    No Comments Yet
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   1470 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 41ms · UTC 16:58 · PVG 00:58 · LAX 09:58 · JFK 12:58
    ♥ Do have faith in what you're doing.