欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-12 13:35:08
url无处不在,不过似乎开发人员并没有真正地理解它们,因为我在stack overflow上经常看到有人在问如何正确的创建一个url。想知道url语法是如何工作的,可以...

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

url无处不在,不过似乎开发人员并没有真正地理解它们,因为我在stack overflow上经常看到有人在问如何正确的创建一个url。想知道url语法是如何工作的,可以看下lunatech的 这篇文章 ,非常不错 。

本文不会深入介绍url的全部语法(如果你想全面了解url的话,可以读下 rfc 3986 , rfc 1738 , 以及上面提到的那篇文章,还有 w3上面的文档 ), 这里我想讲的是常见的一些库在操作url方面存在的错误,以及如何通过 url-builder 来正确的使用它,这是我们发布的一个用于正确地创建url的java库。

问题1:java的urlencoder

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

utility class for html form encoding.

你可能正纳闷为什么叫urlencoder呢,看到这行就彻底无语了。

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

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

那好,我们来使用下urlencoder。urlencoder.encode(“you & i”, “utf-8″)是结果是you+%26+i。这个%26解码之后就是&,而+号在查询串中代表的就是空格,因此这个url是能正常工作的。

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

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

问题2:groovy httpbuilder以及java的uri

http builder 是groovy的一个http客户端库。

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

new httpbuilder("http://localhost:18080").request(method.get) { 
uri.path = "/foo" 
}

这段代码会发送get /foo http/1.1到服务端(你可以运行nc -l -p 18080之后再执行这段代码验证下)。

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

new httpbuilder("http://localhost:18080").request(method.get) { 
uri.path = "/foo bar" 
}

这个发送的是get /foo%20bar http/1.1,看起来还不错。

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

new httpbuilder('http://localhost:18080').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()

结果是: http://foo.com/with%20spaces/path/with/varargs/&=%3f%2f;matrix=param%3f?fancy%20%2b%20name=fancy?%3dvalue#%23?=

这个例子演示了url各个部分的不同的编码规则,比如说在路径中未编码的&=是允许的,而?/则是需要编码的,但在查询参数中=是需要编码的,但?号则不需要,因为这里已经是查询串的部分了(译注:查询串是从一个?号开始的,因此后面可以包含?号)。

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!