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

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

问题1:Java的URLEncoder

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

Utility class for HTML form encoding.

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

如果你读过兄弟连教育(www.itxdl.cn)的那篇博文,现在你应该明白了,你没法通过这个类将一个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各个部分的不同的编码规则,比如说在路径中未编码的&=是允许的,而?/则是需要编码的,但在查询参数中=是需要编码的,但?号则不需要,因为这里已经是查询串的部分了。

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

  1. java中常见的六种线程池详解

    之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java 中常见的几种线程池,以及在jdk7 加入的 ForkJoin 新型线程池 首先我们列出Java 中的六种线程池如 ...

  2. Java中常见的5种WEB服务器介绍

    这篇文章主要介绍了Java中常见的5种WEB服务器介绍,它们分别是Tomcat.Resin.JBoss.WebSphere.WebLogic,需要的朋友可以参考下 Web服务器是运行及发布Web应用的 ...

  3. Java中常见的Exception种类

    Java中常见的Exception种类 1.ClassNotFoundException 2.IOException 3.NoSuchFieldException 4.NoSuchMethodExce ...

  4. Java基础-JAVA中常见的数据结构介绍

    Java基础-JAVA中常见的数据结构介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是数据结构 答:数据结构是指数据存储的组织方式.大致上分为线性表.栈(Stack) ...

  5. Java中常见的注解

    Java中常见的注解 1.JDK自带的注解@Override  @Deprecated  @Suppvisewarnings 常见第三方注解 Spring:@Autowired  @Service  ...

  6. Java中常见的异常类型

    一. Java中常见的异常类 异常类 说明 ClassCastException 类型准换异常 ClassNotFoundException 未找到相应类异常 ArithmeticException ...

  7. Java 中常见的 final 类

    Java 中常见的 final 类 java.lang 包 public final class Boolean extends Object implements Serializable, Com ...

  8. java中常见的内存泄露的例子

    JAVA 中的内存泄露     Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露.     Java中的内存泄露与C++中的表现有所不同.     在C++ ...

  9. java 中常见异常

    1. Java.lang.NullPointerException  这个异常大家肯定都经常遇到,异常的解释是"程序遇上了空指针",简单地说就是调用了未经初始化的对象或者是不存在的 ...

随机推荐

  1. Web开发框架推导

    本文欲回答这样一个问题:在 「特定环境 」下,如何规划Web开发框架,使其能满足 「期望 」? 假设我们的「特定环境 」如下: 技术层面 使用Java语言进行开发 通过Maven构建 基于Spring ...

  2. Wordpress上传资源报HTTP错误

    简述 在公司内部搭建内部视频学习网站,经过对比选择了wordpress进行站点搭建.但是在上传视频遭遇到了各种问题,特将此处理过程进行记录. 原因排查 1.上传一个十几兆mp4的文件上传进度到达百分之 ...

  3. iOS.Animations.by.Tutorials.v2.0汉化(四)

    第三章 转换 在前面的两章,你学习了如何创建基于视图位置和透明度alpha的动画属性的动画.但是,如果您希望在视图上添加动画或删除动画,您将如何处理呢? 您可以使用前几章的方法来设置进出界面的动画效果 ...

  4. Android自定义Seekbar滑动条,Pop提示跟随滑动按钮一起滑动

    由于项目需要做出此效果,自定义写了一个. 效果图 思路: 原始的seekbar只有滑动条并没有下方的提示文字,所以我们必须要继承Seekbar重写这个控件. 代码: 在values文件夹下新建attr ...

  5. spring-java项目中连接redis数据库

    最近由于项目需要,要从redis数据库中查询一些数据,还没有了解过redis,只好硬着头皮上阵,记录一下连接过程: 1.需要导入两个jar包:jedis.jar,spring-data-redis.j ...

  6. Windows gsl runtime error的解决方案

    经过两天多的奋战,终于把GLAD源码集成进来了. 在编译.链接都正确,程序开始跑之后,又出了一个让人很无奈的runtime error, 就在 /* Initialize minimizer */ T ...

  7. js 函数中的 return+匿名函数

    今天一个刚学js的朋友给了我一段代码问为什么方法不执行,代码如下: 代码如下: function makefunc(x) {  return function (){   return x;  } } ...

  8. idea-全局默认maven配置

    在项目实战中我们依赖的开发IDE可谓是我们的饭碗,怎么高效优化使用IDE将极大提高我们开发的效率,这里通过一些人性化的设置让我们更加舒适的开发,默认IntelliJ IDEA 是有一套自己的IDE整体 ...

  9. Docker学习——Lepus部署

    Lepus部署(基于docker)及mysql慢查询配置 介绍 Lepus是一个由Python+PHP开发的数据库企业级监控系统,可用于MySQL/Oracle/MongoDB/Redis 下载镜像 ...

  10. C++标准库bitset类型(简单使用方法)

    转自此人博客 ```cpp #include<bister> using std::bitset; ``` 一句话定义:可自定义位数,用作记录二进制的数据类型. 一,定义和初始化 ```c ...