Java網路程式設計之URL和URI
URL和URI
URL可以唯一地標識一個資源在Internet上的位置。URL是最常見的URI
URI
URI的結構:
模式:模式特定部分
常見的模式有:
data file ftp http mailto
magnet telnet urn
模式特定部分一般採用層次的結構,非ASCII字元要轉換成UTF-8編碼的十六進位制碼
URLs
URL是一個URI,除了標識一個資源,還會為資源提供一個特定的網路位置,客戶端可以用它來獲取這個資源。與之不同的時,通常URI可以告訴你一個資源是什麼,但是無法告訴你在哪裡,以及如何得到這個資源。
URL中的網路位置通常包括用來訪問伺服器的協議(FTP、HTTP)、伺服器的主機或IP地址、以及檔案在該伺服器上的路徑。
URL的語法為:
protocol://[email protected]:port/path?query#fragment
這裡的protocol就是URI的模式(scheme);userInfo可選;埠也是可選的;使用者資訊、主機和埠構成一個authority;fragment是片段,指向遠端資源的某個特定的部分,比如某個HTML的標籤的ID(這樣的話,嚴格上說是URL引用,但是JAVA不做區分)。
相對URL
URL可以繼承其父文件的協議、主機名和路徑,因此可以使用相對URL,例如:
<a href="javafaq.html">
如果相對連線是以“/”開頭,那麼它相對於文件根目錄,例如:
<a href="/projects/ipv6">
URL類
java的URL是一個final類,因此是執行緒安全的,並且使用了策略設計模式
建立URL的方法:
public URL(String url) throws MalformedURLException;
public URL(String protocol, String hostname, String file) throws MalformedURLException;
public URL(String protocol, String host, int prot, String file) throws MalformedURLEXception;
public URL(URL base, String relative) throws MalformedURLException
能否支援某個協議取決於虛擬機器,如果不支援可以安裝一個協議處理器,當然更好的方法就是使用一個庫
除了驗證能否識別URL模式外,java不會它構造的URL完成任何正確性檢查。程式設計師要負責確保所建立的URL是合法的。
從組成部分建立URL
public URL(String protocol, String hostname, String file) throws MalformedURLException;
此方法等同於呼叫帶四個引數的構造方法,四個引數為 protocol、host、-1(即採用預設埠) 和 file。 此構造方法不執行對輸入的驗證。注意file中不要忘了寫“/”
其他方式獲取URL
java.io.File類有一個toURL()方法,可以產生一個和平臺有關的URL
ClassLoader.getSystemResource(String name)可以返回一個URL
ClassLoader.getSystemRecources(String name)返回包含URL的Enumeration
classloader的例項方法getResource(String name)會在所引用類載入器使用路徑中搜索指定資源的URL
從URL獲取資料
public InputStream openStream() throws IOException;
使用案例:
try{
URL u = new URL("http://www.lolcats.com");
try (InputStream in = u.openStream()){
int c;
while((c=in.read())!=-1) System.out.write(c);
}
} catch(IOException ex){
System.out.println(ex);
}
顯示一個html頁面(這裡預設為ASCII碼字元,實際上html是存在多種編碼的)
public class SourceViewer {
public static void main(String[] args) {
if (args.length > 0) {
InputStream in = null;
try {
URL u = new URL(args[0]);
in = u.openStream();
in = new BufferedInputStream(in);
Reader r = new InputStreamReader(in);
int c ;
while ((c = r.read()) != -1) {
System.out.println((char) c);
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
public URLConnection openConnection() throws IOException
該方法為指定的URL開啟一個socket,並返回一個URLConneciotn物件。該物件表示一個網路資源的開啟的連結,如果失敗將會丟擲異常。如果希望與伺服器直接通訊,應當使用這個方法,除了訪問原始的文件本身外,它還能訪問這個協議指定的元資料,例如HTML的首部。
該方法用一個過載的版本:
可以指定哪個代理伺服器傳遞連線
public URLConnection openConnection(Proxy proxy) throws IOException
public final Object getContent() throws IOException
通過URL資料建立物件,如果URL指示的是某中文字,返回的通常是某種InputStream。如果指示一個影象,返回一個java.awt.ImageProducer物件(getContent會從伺服器獲取的首部查詢Content-type欄位,如果伺服器沒有使用MIME首部,或者是不熟悉的Content-type就返回InputStream)
public final Object getContent(Class[] classes) throws IOException
上面方法的過載版本,允許選擇返回的類(多個類,返回第一個匹配項),常配合instanceof來使用
分解URL
URL由以下5部分組成:模式;授權機構;路徑;片段識別符號;查詢字串
授權機構可以進一步分為:使用者資訊,主機和埠 例如:http://[email protected]:8080/
URL類提供了9個方法訪問這些資料:
public String getProtocol();
public String getHost();
public int getPort();//沒有指定埠,就返回-1
public int getDefaultPort();
public String getFile();//主機名後的都是file
public String getPath();//不包括查詢字串
public String getRef();//返回片段識別符號
public String getQuery();
public String getUserInfo();//返回@符號之前的使用者資訊
public String getAuthority();
equals和hashcode
僅當兩個URL都指向相同的主機、埠和路徑上的相同資源,而且有相同的片段識別符號和查詢字串,才認為兩個URL是相等的。不過,equals會嘗試用DNS解析主機,來判斷連個主機是否相同。這說明URL是一個阻塞的IO操作,因此要避免在HashMap等依賴equals的資料結構中使用(一般情況下應該使用URI來存)。
public boolean sameFile(URL other)
也是比較URL,也會進行DNS查詢,但不會比較片段識別符號
轉換
URL有三個方法可以將一個例項轉換為另一種形式,分別是:
toString()
toExternalForm() 可以在瀏覽器中開啟
toURI()
URI類
URI是對URL的抽象,不僅包括URL,還包括URN(統一資源名),實際上大多URI就是URL。兩者的區別有
URI類只和資源的標識和URI的解析有關,沒有提供獲取RUI所標識資源的方法
相比URI類,URI與相關的規範更一致
URI物件可以表示相對URI,URL類在儲存URI之前會將其絕對化
構造
與URL不同,URI類不依賴於底層協議處理器。只要URI語法正確,java就不需要為了建立URI物件而理解其協議。
public URI(String uri) throws URISyntaxException;
public URI(String scheme, String schemeSpecificPart, String fragment) throw...;
public URI(String shceme, String host, String path, String fragment) throws ...;
public URI(String scheme, String authority, String path, String query, String fragment) throws ...;
public URI(String scheme, String userInfo, String host, int port, String path,String query, String fragment) throws URISyntaxException;
上面方法中任何引數都可以省略,從而忽略該引數
URI的各部分
URI的引用主要包括三個部分:模式、模式特定部分、片段識別符號
如果省略模式,表示這是一個相對URI,如果省略片段識別符號,這個URI就是一個純URI。
主要方法如下:
public Sting getScheme();
public String getSchemeSpecificPart();
public String getRawSchemeSpecificPart();
public String getFragment();
public String getRawFragment();
帶raw的是編碼形式,不帶raw的是對所有用百分號轉義的字元進行解碼,然後返回。scheme只有ASCII碼形式。
有模式的URI是絕對URI,沒有模式的是相對URI
public boolean isAbsolute();//判斷是不是絕對URI
public boolean isOpaque();//是否 不是有層次的URI
如果是有層次的,有相關的獲取相應部分的方法:
public String getAuthority();
public String getFragment();
public String getHost();
public String getPath();
public String getPort();
public String getQuery();
public String getUserInfo();
這些都是解碼後,也就是百分號轉義會改為它們實際表示的字元,如果希望得到原始的部分,在get後面加上Raw,注意,host和Port(返回-1表示省略埠)沒有raw方法,因為都是由ASCII碼組成的
java並不是在開始就檢測授權機構部分中的語法錯誤,所以無法返回授權機構的各個部分:埠、主機、使用者資訊。可以呼叫parseServerAuthority()強制重新解析授權機構。(注意URI是final類)
解析相對URI
public URI resolve(URI uri);
public URI resolve(String uri);
public URI relative(String uri);
相對URI構造絕對URI:
URI absolute = new URI("http://www.example.com");
URI relative = new URI("images/logo.png");
URI resolved = absolute.resolve(relative);
當然也可以通過相對URI構造相對URI
絕對URI構造相對URI:
URI absolute = new URI("http;//www.example.com/images/logo.png");
URI top = new URI("http://www.example.com/");
URI relative = top.relativize(absolute);
equals
在進行equals比較時,相等的URI必須都是層次或非層次的,模式和授權機構不會區分大小寫,轉義字元在比較前不解碼
URI實現了Comparable,一次URI可以排序。
字串表示
toString() 未編碼的字串形式,更方便閱讀,但顯示結果不能保證是一個語法正確的URI
toASCIIString() 編碼後的字串形式,推薦使用
URLEncoder
對字串完成URL編碼
例如:
String encoded = URLEncoder.encode("this*string*has*asterisks","UTF-8");
注意這個方法對於/,&,=,: 都進行了編碼,因此有時候需要分部分的編碼(只編碼需要編碼的,而不是整個URL一起編碼)
URLDEcoder
public static String decode(String s, String encoding) throws UnsupportedEncodingException;
該方法可以傳入整個URL
代理
Proxy類有三種代理:
Proxy.Type.DIRECT //實際就是沒代理
Proxy.Type.HTTP
Proxy.Type.SOCKS
代理資訊可以用SocketAddress物件表示:
SocktAddress address - new InetSocketAddress("proxy.example.com",80);
Proxy proxy = new Proxy(Proxy.Type.HTTP, address);
每個執行中的虛擬機器都有一個Java.net.ProxySelector物件,可以使用自己的ProxySelector子類來替代預設的選擇器
ProxySelector類可以根據URI返回合適的代理:
public abstarct list<Proxy> select(URI uri);//返回代理
public void connectFailed(URI uri, SocketAddress sa, IOException ioe);//同時必須實現這個連線失敗的方法
案例:
public class LocalProxySelector extends ProxySelector {
private List<URI> failed = new ArrayList<>();
@Override
public List<Proxy> select(URI uri) {
List<Proxy> result = new ArrayList<>();
if (failed.contains(uri) || !"http".equalsIgnoreCase(uri.getScheme())) {
result.add(Proxy.NO_PROXY);
} else {
SocketAddress proxyAddress = new InetSocketAddress("proxy.example.com", 80);
Proxy proxy = new Proxy(Proxy.Type.HTTP, proxyAddress);
result.add(proxy);
}
return result;
}
@Override
public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
failed.add(uri);
}
}
改變虛擬機器預設的選擇器:
ProxySelector selector = new LocalProxySelector();
ProxySelector.setDefault(seletor);
因為虛擬機器只有一個ProxySelector,因此不推薦在共享的環境下使用這個方法