URLConnection是一個協議處理器中的一個類,它是表示指向URL所指定的資源的活動連接。 主要用於兩個方面,一個是與服務器(特別是HTTP服務器)的交互,可以用來查看服務器發送的首部,設置連接的屬性,設置客戶端的請求的首部等。利用它也 可以實現POST和PUT方法來發送數據。另一個方面是Java的協議處理器機制的一部分。所謂的協議處理器就是將處理協議的細節從處理特定數據類型中分 離出,會涉及到客戶端與服務器端的交互,如生成正確的請求格式,解釋與數據一起返回的首部等。
獲取URLConnection
根據一個已經創建的URL來通過openConnection來打開生成一個URLConnection,雖然利用url來獲取,其實內部調用的是 URLStreamHandler的openConnection,該方法是一個抽象方法,它返回的URLConnection會根據協議的類型返回,倘 若是一個http url則就會返回一個HTTPURLConnection。
URL url=new URL("http://www.baidu.com");
URLConnection uc=url.openConnection();
此時該uc並沒有連接,本地與遠程主機是無法進行收發數據的,需要調用uc.connect()方法來建立連接。不過在使用getInputStream(),getHeaderField()等其它要求的時候會首先自動調用這個方法。
利用此URLConnection可以很容易讀取來自服務器端的數據,只要其getInputStream。
URL和URLConnection的區別
URLConnection提供了對於HTTP首部的訪問
URLConnection可以配置客戶端向服務器端發送的請求參數即首部
URLConnection可以利用POST,PUT等請求方法與服務器進行交互
利用URL與URLConnection都可以向服務器發送HTTP請求。
1,創建一個URL,傳遞一個字符串形式的HTTP請求,可以加上查詢字符串,此時默認動作類型是get,也就是查詢,從服務器返回一個資源,再通過getInputStream正式的建立客戶端與服務器之間的連接,將該HTTP請求發送到服務器端,建立必要的握手,由於HTTP是全雙工的,此時就可以在這裏面獲取服務器的返回輸出流。
2,通過url的opentConnection獲取一個指向url地址的活動連接,初次獲取的時候是沒有連接的,本地和遠程根本不能收發數據,也就是沒有socket來連接這臺主機。要利用connect()方法來在本地和遠程主機之間建立一個TCP socket連接,不過一般都是在使用getInputstream自動調用這個方法,使用getInputStream纔是真正的進行發送HTTP的請求消息。
讀取服務器響應的首部
對於服務器響應的首部,當然這裏都是以HTTP服務器響應爲準,可以來獲取響應中的首部字段。
Content-type,Content-length,Content-encoding,Date,Last-modified,Expires
可以利用getContentType()來獲取數據的MIME類型,判斷字符編碼,並且以正常的格式顯示出,如
String encoding="ISO-8859-1";//HTTP默認的編碼方式
URL u=new URL(url);
URLConnection uc=u.openConnection();
String type=uc.getContentType();
//獲取字符編碼方式
int star=type.indexOf("charset=");
if(star!=-1)
encoding=type.substring(star+8);
System.out.println("CharSet:"+encoding);
InputStream raw=uc.getInputStream();
Reader r=new InputStreamReader(new BufferedInputStream(raw),encoding);
利用getContentLength來獲取二進制文件大小,從而來下載文件
URLConnection uc=url.openConnection();
String contentType=uc.getContentType();
int contentLength=uc.getContentLength();
if(contentType.startsWith("text/")||contentLength==-1)
{
thrownew IOException("This is not a binary file");
}
InputStream raw=new BufferedInputStream(uc.getInputStream());
byte[] data=newbyte[contentLength];
int bytesRead=0;
int offset=0;
while(offset<contentLength)
{
bytesRead=raw.read(data, offset, contentLength);
if(bytesRead==-1)break;
offset+=bytesRead;
}
raw.close();
if(offset!=contentLength)
{
thrownew IOException("Only read "+offset+ "bytes;Expected
"+contentLength+" bytes");
}
//根據URL中獲取文件路徑的名,將獲取的流寫入到本地
String file=url.getFile();
int start=file.lastIndexOf("/");
String filename=file.substring(start+1);
FileOutputStream fout=new FileOutputStream(filename);
fout.write(data);
fout.flush();
fout.close();
getHeaderField(String name);可以根據指定首部名來不區分大小寫獲取該名對應的值
getHeaderFieldKey(int n)和getHeaderField(int n)依此返回首部中的名和值,很有用
URL u=new URL(url);
URLConnection uc=u.openConnection();
for(int j=1;;j++)
{
String header=uc.getHeaderField(j);
if(header==null)break;//skip
the loop
System.out.println(uc.getHeaderFieldKey(j)+":"+header);
}
配置連接
所謂的配置主要就是定義了客戶端如何向服務器做出請求。
一般客戶端在默認情況下doInput爲true,表示可以接受來自服務器端發送的數據,這些必須在URLConnection連接之前設置。如果想要利用POST和PUT進行交互就必須要設置doOutpu爲true,默認是false
配置客戶端發送的HTPP首部
客戶端進行服務器訪問的時候,有些協議需要加上首部,配置一些名值對,可以通過setRequestProperty(name,value)設置,多個值之間利用逗號隔開。
客戶端利用POST來發送數據
package com.urlconnection;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URL;
import java.net.URLConnection;
//模擬表單提交處理
publicclass FormPoster {
private URL url;
private QueryString query=new QueryString();
//必須要保證是http協議
public FormPoster(URL url)
{
if(!url.getProtocol().toUpperCase().startsWith("HTTP"))
{
thrownew IllegalArgumentException("Posting only works for http
urls");
}
this.url=url;
}
publicvoid add(String name,String value)
{
query.add(name, value);
}
public URL getURL()
{
returnthis.url;
}
public InputStream post()throws IOException
{
URLConnection uc=url.openConnection();
uc.setDoOutput(true);
Writer out=new OutputStreamWriter(uc.getOutputStream(),"ASCII");
//POST行,Content-type和Content-length是由URLConnection發送的
//只需要發送數據即可
out.write(query.toString());
out.write("\r\n\r\n");
out.flush();
out.close();
return uc.getInputStream();
}
publicstaticvoid main(String[] args) {
// TODO Auto-generated method stub
String u="http://www.baidu.com";
URL url=null;
try{
url=new URL(u);
}catch(IOException e)
{
}
FormPoster poster=new FormPoster(url);
poster.add("hawk", "fdafda");
poster.add("good morning", "fdafa");
try{
InputStream in=poster.post();
//讀取響應
InputStreamReader r=new InputStreamReader(in);
int c;
while((c=r.read())!=-1)
{
System.out.print((char)c);
}
in.close();
}catch(IOException ex)
{
System.err.println(ex);
}
}
}
HTTPURLConnection
這個是一個專門操作http URL的類,繼承子URLConnection,主要有7個請求的方法,
利用setRequestMethod(String method)設置不同的請求方法,默認是get,區分大小寫
GET,從服務器端獲取數據
POST,向服務器端提交表單
PUT,上傳文件至服務器
HEAD,獲取服務器端響應的頭部
OPTIONS,查詢服務器支持哪些請求方法
DELETE,刪除服務器中的文件
TRACE,服務器返回客戶端發送的HTTP首部,用於檢測代理服務器對HTTP首部進行怎樣修改
服務器響應的格式如下:
HTTP/1.1 200 OK
...
這個類添加了兩個方法
getResponseMessage()獲取響應碼對應的消息,如OK
getResponseCode()獲取響應碼,如200
協議處理器
協議處理器主要就是根據URL中的協議來找到合適的協議處理器來進行客戶端與服務器端的交互。主要涉及四個類,具體類URL,抽象類 URLConnection和URLStreamHandler,以及接口URLStreamHandlerFactory。協議處理器的流處理器總是根 據指定的協議找到最適合的URLConnection
要想自己建立協議處理器,需要編寫兩個URLConnection和URLStreamHandler的子類,然後創建一個URLStreamHandlerFactory。
URLConnection子類主要處理與服務器交互,將服務器發送的數據轉換爲InputStream,將客戶端發送的所有數據轉換爲OutputStream。URLStreamHandler子類主要將URL的字符串表示解析爲各個部分,用這些部分來設置URL對象的各個部分,並且創建一個理解次URL協議的URLConnection。
協議處理器的基本流程如下:
1 程序先利用字符串構建某一個協議的URL對象,在創建URL模式中,只會驗證是否識別URL模式,而不會對其格式的正確性進行檢查
2 構造函數利用所傳遞的參數來確定URL的協議部分,如http
3 URL()構造函數以如下方式嘗試進行找到給定的URLStreamHandler
a 如果以前使用過此協議,從緩存中獲取URLStreamHandler
b 否則,若設置了URLStreamHandlerFactory,將此字符串傳遞給工廠
c 若兩個都沒有,則嘗試實例化一個位於java.protocol.handler.pkgs屬性列出的 protocol.Handler的URLStreamHandler
d 如果實例化失敗,就嘗試實例化一個位於sun.net.www.protocol中的protocol.Handler 的URLStreamHandler
e 如果其中一個成功了,則設置屬性字段handler字段。如果都不成功,則拋 出MalformedURLException異常
4 程序調用URL對象的openConnection方法
5 URL會根據協議讓URLStreamHandler返回一個適合於此URL的URLConnection
6 使用URLConnection進行與遠程資源的交互
URLStreamHandler解析URL中字段的過程
URL(String)-->URL(URL,String)-->URL(URL,String,String)-->URLStreamHandler.parseURL()
-->URLStreamHandler.setURL()-->URL.set();
要新建立一個URLStreamHandler一般只需要修改openConnection方法即可,根據需要來修改parseURL
爲每一個URLConnection子類重寫一個connect方法,該方法包含了Socket的具體建立步驟。
再利用URLStreamHandlerFactory來註冊這些流處理器
在信息交互系統設計中,不乏有自定義通訊協議設計。本章會介紹如何利用 java.net.URL 類來自定義協議。
一般而言, URL 的格式是: protocol://[authority]hostname:port /resource?queryString 。 URL 類能夠解析出 protocol、 hostname 、 port 等信 息。 Protocol 決定了交互規範,通用的協議,比如 HTTP 、 File 、 FTP 等協議, JDK 自帶了默認的通訊實現。當然,自定 義實現是允許的。 Hostname 和 port 一般用於 Socket 或者基於 Socket 其他協議通訊方式。Resource 即資源上下 文。可能讀者利用 URL ,通過指定協議( protocol )來獲取指定資源的讀寫,比如 JDK 內置了HTTP 、 File 、 FTP 等 協議的處理方法。哪麼 URL 的工作原理到底是怎麼樣的呢?
在成功地構造 URL 實例之後, URL API 中定義了一個 openConnection() 方法,返回一個 java.net.URLConnection 抽象類型的實例。不過,這 裏 URL 對象是代理對象(組合了 其 對象),實際調用的是, java.net.URLStreamHandler 對象 的 openConnection() 方法。
java.net.URLStreamHandler 對象可以有兩條途徑得到:實現 java.net.URLStreamHandler ,或者實現java.net.URLStreamHandlerFactory 。
java.net.URLStreamHandler 是 一個工廠類,通過 openConnection(java.net.URL) 方法來創建 java.net.URLConnection的實 例。 java.net.URLStreamHandler 實現靈活度很大,既可以通過不同 protocol 的 URL 實例,產生 java.net.URLConnection 對象。還可以通過相同 protocol 的多個 URL 對象,來產生對象。通用性實現,一種協議對應 一個java.net.URLStreamHandler 實現。比如,在 SUN JDK 中 sun.net.www.protocol 子包下面的多個 Handler 類就是很好的例子。如果讀者有興趣,可以去看看相關實現。
1. 通過 java.net.URLStreamHandlerFactory 機制
java.net.URLStreamHandlerFactory , 顧名思義,它是 java.net.URLStreamHandler 的工廠,即抽類工廠接口。通過調 用 createURLStreamHandler(String protocol) 來創建 java.net.URLStreamHandler 對象。因此,建議 java.net.URLStreamHandlerFactory 實現類應該採用 one protocol one hander 的模式, SUN JDK 也採用該模式。
大致解了他們關係之後,再通過 UML Class diagram 來熟悉下:
(圖 1 )
圖 1 所 示, URL 包含了名爲 factory 的 URLStreamHandlerFactory 類對象 和 handler 的 URLStreamHandler 的實例對象。對於 URL 而言, handler 對象是必須的,因爲前面說到實際處 理 openConnection() 方法是 handler 對象,而 factory並不是必須的。接下來,來分析這兩個對象是如何和 URL 交 互的。
在 URL 的 構造方法中,暫時不用關心協議字符串等參數,更多的關注於 URL context 和 URLStreamHandler 參數。URL 實例能夠依賴於 URL context ,當 URLStreamHandler 參數爲空的情況下,當前 URL 實例將會採用 URL context 的URLStreamHandler 成員對象。當 Context 和 URLStreamHandler 參數都爲空的 時。 URL 會調用 getURLStreamHandler( String) 方法,從而根據協議 (protocol) 獲得協 議 URLStreamHandler 對象。
在 URL 底層實現中,最初會初始化一個 protocol 和 hander 鍵值關係的 Map 映射。如果找到已有的映射關係,立即返回 URLStreamHandler 對象(第一次是取不到 URLStreamHandler 對象的)。
如果 找不到的話,並且 URL 類中的類成員 URLStreamHandlerFactory 實例不爲空的情況下,這個實例通過 URL#setURLStreamHandlerFactory 方法來註冊。 getURLStreamHandler 方法會調用這個類成員的 createURLStreamHandler(String) 方法來創建 URLStreamHandler 實例。
URL.setURLStreamHandlerFactory(new MyURLStreamHandlerFactory());
(代碼 1 )
class MyURLStreamHandlerFactory implements URLStreamHandlerFactory{
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return null;
}
}
(代碼 2 )
當 createURLStreamHandler 方法返回 null 的時候, URL 的 getURLStreamHandler 方法會採用 URLStreamHandler處理機制。
2. 通過 java.net.URLStreamHandler 機制
2.1. 實現類包路徑定義
通過 JVM 啓動參數 -D java.protocol.handler.pkgs 來設置 URLStreamHandler 實現類的包路徑,例如 -Djava.protocol.handler.pkgs=com.acme.protocol , 代表處理實現類皆在這個包下。如果需要多個包的話,那麼使用“ |” 分割。比如 -D java.protocol.handler.pkgs=com.acme.protocol|com.acme.protocol2 。 SUN 的 JDK 內部實現類均是在sun.net.www.protocol. 包下,不必設置。 路徑下的協議實現類,採用先定義先選擇的原則 。
2.2. 實現類的命名模式
類的命名模式爲 [package_path]. [protocol].Handler ,比如默認實現” sun.net.www.protocol.[protocol].Handler”, 比如 HTTP 協議的對應的處理類名爲 -sun.net. www.protocol.http.Handler 。同樣,自定義實現的處理類,例 如, JDNI 協議實現類命名 com.acme.protocol.jndi.Handler 。
2.3. 實現類必須又默認構造器。
結合代碼分析,如下:
URL httpURL = new URL(null, "http://www.google.com",URLStreamHandler)null);
URLConnection urlConn = httpURL.openConnection();
(代碼 3 )
代碼 3 中沒有配置 URLStreamHandler 的類,並且代碼 2 中工廠類沒有實現了 HTTP 協議。這樣, URL 會獲取默認的 HTTP 處理類 sun.net. www.protocol.http.Handler 。
Java 1.5 開始支持網絡代理的操作,因此 URLStreamHandler 實現類儘量覆蓋 openConnection(URL) 和openConnection(URL,Proxy) 兩個方法。