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

作者:佚名

来源:51CTO

时间: 2024-09-20 09:26:31

Java中创建URL的常见问题及解决方案的相关文章

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

URL无处不在,不过似乎开发人员并没有真正地理解它们,因为我在Stack Overflow上经常看到有人在问如何正确的创建一个URL.想知道URL语法是如何工作的,可以看下Lunatech的 这篇文章 ,非常不错 . 本文不会深入介绍URL的全部语法(如果你想全面了解URL的话,可以读下 RFC 3986 , RFC 1738 , 以及上面提到的那篇文章,还有 W3上面的文档 ), 这里我想讲的是常见的一些库在操作URL方面存在的错误,以及如何通过 URL-builder 来正确的使用它,这是我

JAVA学习(六):JAVA中的继承及其常见问题分析

JAVA中的继承及其常见问题分析 1.JAVA中继承的定义 JAVA中,类的继承是通过扩展其他类而形成新类来实现的,原来的类称为父类(Super Class)或基类,新的类称为原来类的子类或派生类.在子类中,不仅包含了父类的属性和方法,还可以增加新的属性和方法,从而使得父类的基本特征可被所有子类对象共享. 注:类的继承并不改变类成员的访问权限,也就是说,如果父类的成员是公有的.被保护的或默认的,它的子类仍具有相应的这些特性. /**********************************

java中创建数组时内存怎么分配????

问题描述 java中创建数组时内存怎么分配???? int[] arr=new int[3]; 问题: 上面创建int数组时,怎么分配内存的?arr是一个引用变量,是通过指针指向new int[3](存在堆里面)对吧,那么,栈里面为arr分配几个指针,是3个还是1个? 补充: 疑问1****: 其实我就是想知道java中创建数组时,栈中分配几个指针,是一个还是"数组长度"个? 疑问2****: 如果是一个,那么,这个指正指向谁???? 疑问3****: 如果指向的是第一个元素,那么,该

使用Either和泛型在Java中创建Scala风格的模式匹配

本系列文章旨在将您的http://www.aliyun.com/zixun/aggregation/12246.html">思维方式向函数式思维方式的方向调整,让您以全新的角度来思考常见问题,并提高您的日常编码工作.本系列介绍了函数式编程概念,函数式编程在 Java 语言中运行的框架.在 JVM 上运行的函数式编程语言,以及语言设计未来的一些方向.本系列主要面向了解 Java 以及其抽象层的工作原理.但缺乏函数式语言使用经验的开发人员. 在 上一期文章 中,我介绍了函数式编程世界的一种通用

java 中的乱码问题汇总及解决方案_java

java中的乱码问题        最近做项目经常会遇到Java中的乱码问题,于是就抽时间整理下出现乱码问题的情况和如何处理,这里做了一个整理, 分析 编码与解码 编码就是将字符转为字节,解码就是就是将字节转换为字符. 字节流与字符流 对文件的读写操作都是通过字节流来实现的,即使JAVA中有字符流,但是其底层仍然使用的字节流. 乱码问题出现 java中使用最频繁的是字符,当我们将文件读入内存并在控制台显示时(字节流--->字符流),就需要用到解码.如果文件是UTF-8编码,而我们解码时错用成GB

在Java中创建PDF:iText JAR

iText是一个免费的Java-PDF库,通过它可以实现on the fly(动态的)创建PDF.iText是那些需要动态PDF文档生成或操作功能来改进应用程序的开发者的理想选择.iText不是一个用户终端工具,也就是说你不用像使用Acrobat或其它PDF工具那样,只需要把iText内建到自己的程序中,它就可以自动的完成PDF生成和操作. iText具有如下功能: ◆将PDF传输到浏览器 ◆通过XML文件或数据库来生成动态文档 ◆支持众多的PDF交互功能 ◆添加书签.页码和水印等等 ◆分割.连

java中创建多维数组

在Java里可以方便地创建多维数组:   //: MultiDimArray.java // Creating multidimensional arrays. import java.util.*; public class MultiDimArray { static Random rand = new Random(); static int pRand(int mod) { return Math.abs(rand.nextInt()) % mod + 1; } public stati

java中创建并初始化字符串方法

1.使用字符串常量直接初始化 String s="hello!"; 2.使用构造方法创建并初始化 String();//初始化一个对象,表示空字符序列 String(value);//利用已存在的字符串常量创建一个新的对象 String (char[] value);//利用一个字符数组创建一个字符串 String(char[] value,int offset,int count);//截取字符数组offset到count的字符创建一个非空串 String(StringBuffer

java 调用C语言,然后在 c中创建jvm(想在C中再调用Java)返回-1!

问题描述 java 调用C语言,然后在 c中创建jvm(想在C中再调用Java)返回-1! ,请教一个问题: 现在Java 调C 代码( C作为 .so 文件), 和C(C作为可执行程序)调用Java 代码都没问题. 但是我现在测试 Java 调用C代码时,在C中同时也 调用Java 代码出现了问题(创建JVM时 失败. 在一些场景下 需要C 主动调用Java),大家之前遇到过这类场景么? 怎么解决, 解决方案 使用JNI进行Java与C/C++语言混合编程(1)--在Java中调用C/C++本