program-dog

2016年6月21日星期二

如何写一个HttpClient[1]——URI的处理


在翻阅apache的http client的代码的时候,看到org.apache.http.client.utils.URIBuilder.java的写法,感觉甚妙。特意分析一下源码,并且对比几种不同的URI写法。

HttpClient 
    Apache的HttpClient
 

本文目录

作为字符串的URI

假设我们有一个HttpClient类,这个类用来发起http请求并且做出响应。如下:
class HttpClient{

    ...
}
现在要把URI作为参数,传递给HttpClient类,好让它知道对谁发起http请求。URI可以是诸如 https://www.google.com/#q=编程狗的博客 这样一个简单的string,一个简单的实现就是给HttpClient类提供一个设置URL的方法:
class HttpClient{
    ...
    public void setURI(String uri){
        ...
    }
    ...
}
这应该是最简单的但却是最不成熟的做法了。它有很多弊端,但我们暂时不讲出来,且往下看。

作为类对象的URI

现在我们需要更换URI,但又不是全部更换,什么意思呢?不妨分几个部分来看看URI:
Scheme -> https
Host -> www.google.com
Path -> /#q=编程狗的博客
https的可能换成httpwww.google.com可能会换成www.google.com.hk,/#q=编程狗的博客可能换成其他的关键词。我们暂且把这个过程称为参数的置换。如果我们仅仅给客户端类HttpClient提供一个setURI(String uri)方法,上面的置换只能通过换掉整个URI实现了。比如把https://www.google.com/#q=编程狗的博客 换成 http://www.google.com/#q=编程狗的博客,而没有办法直接把“https”换成“http”
于是,我们不妨提供再一下几个方法,以使我们很方便的做出上面的置换
public void setScheme(String scheme){
    ...
}

public void setHost(String host){
    ...
}

public void setPath(String path){
    ...
}
这三个方法分别用来设置schemehostpath。为了叙述方便,没有对参数进行检查。仅仅提供这三个设置参数的方法是不能过实现置换的,因为对于传入的URI,必须先分割schemehostpath三部分。当然,我们可以的再添加一个splitURI方法,实现分割;但是,我们已然发现如果这样做了,HttpClient类就有至少4个方法处理URI了,将来可能还会增加,HttpClient类可能会有数十个方法的作用与http信息收发不相关,这样势必是一种混乱,并且影响扩展性,因此我们不这样做。我们应当有一个URI类,专门处理URI的置换分割
class URI{
    //构造函数,默认把uri分割好。
    public URI(String uri){
    ...
        splitURI();
    ...
    }

    //分割方法
    private void splitURI(){
    ...
    }

    //set方法
    public void setScheme(String scheme){
    ...
    }

    public void setHost(String host){
    ...
    }

    public void setPath(String path){
    ...
    }


    //get方法
    public String getScheme(){
    ...
        return scheme;
    }

    public String setHost(){
    ...
        return host;
    }

    public String setPath(){
    ...
        return path;
    }
}
同时我们也更新HttpClient类
class HttpClient{
    ...
    public void setURI(URI uri){
        ...
    }
    ...
}
我们可以这样使用它们:
URI uri = new URI("https://www.google.com/#q=编程狗的博客");
uri.getScheme();// https
uri.getHost();// www.google.com
uri.getPath();// /#q=编程狗的博客
...
uri.setScheme();// https(如果你确实需要重新设置)
...
HttpClient client = new HttpClient();
client.setURI(uri);

作为Builder的URI

到现在为止已经可以应付关于URI的很多需求了,但是我们如果要求URI类是一个不可变的类,为什么不可变呢?因为不可变更加安全。但那些set方法就没有用处了。apache把URI的set方法去掉了,如果想设置参数,现在只能通过构造函数:
//重载构造函数
public URI(String str){
...
}
/**
     * @param   scheme    Scheme name
     * @param   userInfo  User name and authorization information
     * @param   host      Host name
     * @param   port      Port number
     * @param   path      Path
     * @param   query     Query
     * @param   fragment  Fragment
*/
public URI(String scheme,
               String userInfo, String host, int port,
               String path, String query, String fragment){
...
}

...
//用法如
URI uri = new URI("http","username:program-dog","program-dog.blogspot.com","/","","");
我们至少有7个参数,如果要满足各种需求的组合,恐怕总共要提供∑(C7i)(i=1~7)种构造函数,显然不现实。然而,URIBuilder既可以造出一个不可变的URI,又可以兼顾N种参数。URIBuilder可以这样用:
//  http://www.google.com/search?q=编程狗的博客&btnG=Google+Search&aq=f&oq=
URI uri = new URIBuilder()
    .setScheme("http")
    .setHost("www.google.com")
    .setPath("/search")
    .setParameter("q", "编程狗的博客")
    .setParameter("btnG", "Google Search")
    .setParameter("aq", "f")
    .setParameter("oq", "")
    .build();
URIBuilder正是采用了Builder Pattern(建造者模式)。等号右边实际上是一行,先创建一个URIBuilder对象实例,调用实例的setScheme方法,此方法顺便返回URIBuilder对象实例,刚刚返回的这个实例调用setHost方法,…,最后一个返回的URIBuilder对象实例调用build方法,返回URI对象。它是如何实现的呢?
原来的URI类的set方法的基础上,添加一个返回值,返回URIBuilder自己就够了:
class URIBuilder{
    public URIBuilder setScheme(String scheme){
    ...
        return this;
    }

    public URIBuilder setHost(String host){
    ...
        return this;
    }

    public URIBuilder setPath(String path){
    ...
        return this;
    }

    //built 方法,把参数拼接,然后返回一个URI类
    public URI built(){
    ...
        return uri;
    }
}
由于URIBuilder每次都返回它自己,所以可以连续的执行 set方法,最后通过built方法返回URI类。

结束

到此为止,一个简单的URI类的写法已经介绍完毕了。我们还是尽量把URI写成类对象,并使它不可变,并且提供相应的Builder。



本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
本文地址:https://program-dog.blogspot.com/2016/06/HowToWriteAHttpClientAboutURI.html

1 条评论 :