1. 程式人生 > >Java網路程式設計之URL和URI

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,因此不推薦在共享的環境下使用這個方法