如何寫一個HttpClient[1]——URI的處理
在翻閱apache的http client的代碼的時候,看到org.apache.http.client.utils.URIBuilder.java的寫法,感覺甚妙。特意分析一下源碼,並且對比幾種不同的URI寫法。
本文目錄
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
的可能換成http
,www.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){
...
}
這三個方法分別用來設置scheme
,host
和path
。為了敘述方便,沒有對參數進行檢查。僅僅提供這三個設置參數的方法是不能過實現置換的,因為對於傳入的URI,必須先分割成scheme
,host
和path
三部分。當然,我們可以的再添加一個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個參數,如果要滿足各種需求的組合,恐怕總共要提供∑(C^7^~i~)(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