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

Java網路程式設計-URI和URL

前提

前面的一篇文章《Java中的Internet查詢》分析完了如何通過IP地址或者主機名確定主機在因特網中的地址。任意給定主機上可能會有任意多個資源,這些資源需要有識別符號方便主機之間訪問對方的資源,因此這篇文章深入分析一下URL和URI。

URI

URI全稱是Uniform Resource Identifier,也就是統一資源識別符號,它是一種採用特定的語法標識一個資源的字串表示。URI所標識的資源可能是伺服器上的一個檔案,也可能是一個郵件地址、圖書、主機名等。簡單記為:URI是標識一個資源的字串(這裡先不必糾結標識的目標資源到底是什麼,因為使用者一般不會見到資源的實體),從伺服器接收到的只是資源的一種位元組表示(二進位制序列,從網路流中讀取)。URI的語法構成是:一個模式和一個模式特定部分。表示形式如下:

模式:模式特定部分

scheme:scheme specific part 

注意:模式特定部分的表示形式取決於所使用的模式。URI當前的常用模式包括:

  • data:連結中直接包含經過BASE64編碼的資料。
  • file:本地磁碟上的檔案。
  • ftp:FTP伺服器。
  • http:使用超文字傳輸協議。
  • mailto:電子郵件的地址。
  • magnet:可以通過對等網路(端對端P2P,如BitTorrent)下載的資源。
  • telnet:基於Telnet的服務的連線。
  • urn:統一資源名(Uniform Resource Name)。

除此之外,Java中還大量使用了一些非標準的定製模式,如rmi、jar、jndi、doc、jdbc等,這些非標準的模式分別用於實現各種不同的用途。

URI中的模式特定部分沒有固定的語法,不過,很多時候都是採用一種層次結構形式,如:

//授權機構/路徑?查詢引數

//authority/path?query

URI的authority部分指定了負責解析該URI其他部分的授權機構(authority),很多時候,URI都是使用Internet主機作為授權機構。例如http://www.baidu.com/s?ie=utf-8,授權機構是www.baidu.com(在URL角度來看,主機名是www.baidu.com)。

URI的路徑(path)是授權機構用來確定所標識資源的字串。不同的授權機構可能會把相同的路徑解析後指向不同的資源。其實這一點很明顯,試下你寫兩個不同的專案,主頁的路徑都是/index.html,它們一定是完全相同的html文件。另外,路徑是可以分層的,分層的各個部分使用斜線"/"進行分隔,而"."和".."操作符用於在分層層次結構中的導航(後面這兩個操作符可能很少見到,瞭解即可)。

URI的語法

URI的模式的組成部分可以是小寫字母、數字、加號、點(.)和連號(-)。典型的URI的其他三個部分(模式特定部分,也就是授權機構、路徑和查詢引數)分別由ASCII字母組成(也就是字母A-Z、a-z和數字0-9),此外,還可以使用標點符號-、_、.、!和~,而定界符(如/、?、&和=)有其預定義的用途。所有的其他字元,包括ASCII中拉丁字母,都需要使用百分號(%)轉義,轉義後的格式是:%+字元按照UTF-8編碼再轉成16進位制的字串表示。注意一點,如果前面提到的定界符沒有作為定界符使用,也需要進行轉義。舉個簡單的例子,如URI中存在中文字元"木",木字的UTF-8編碼為0xE6 0x9C 0xA8,那麼它在URI中應該轉義為%E6%9C%A8。Jdk中的URLEncoder或者apache-codec中的相關類庫提供URI(URL)編碼的功能。

URI中還可以攜帶使用者的口令,因為會有安全漏洞,所以並不常見,這裡也不展開分析。

URI類

URI在Java中抽象為java.net.URI類。

構造URI例項

URI例項構造方法有很多個:

public URI(String str) throws URISyntaxException

public URI(String scheme,String userInfo, String host, int port,
        String path, String query, String fragment) throws URISyntaxException

public URI(String scheme, String authority,
       String path, String query, String fragment)throws URISyntaxException

public URI(String scheme, String host, String path, String fragment) throws URISyntaxException

public URI(String scheme, String ssp, String fragment) throws URISyntaxException

//靜態工廠方法,最終呼叫的是URI(String str)
public static URI create(String str)

注意的是構造URI例項的時候會檢查是否符合URI的語法,否則會丟擲URISyntaxException異常。以上的所有方法都是基於URI的模式+模式特定部分或者URI的各個部分構造URI例項,而靜態工廠方法public static URI create(String str)主要是遮蔽了非檢查型異常URISyntaxException,轉化為檢查型異常IllegalArgumentException,這樣就不需要顯式捕獲異常。

獲取URI的屬性

前面也提到,URI引用包括最多三個部分:模式、模式特定部分和片段識別符號,一般格式為:

模式:模式特定片段:片段識別符號

基於這種語法規範,URI類提供了下面幾個方法獲取這些屬性:

public String getScheme()

public String getRawSchemeSpecificPart()

public String getSchemeSpecificPart()

public String getFragment()

public String getRawFragment()

PS:之所以沒有getRawScheme()方法,是因為URI規範中強制規定,所有的模式名稱必須由URI規範中合法的ASCII字元組成,也就是模式名稱中不允許存在百分號轉義。上面的getRawSchemeSpecificPart()是返回原始的模式特定部分,getSchemeSpecificPart()返回了經過解碼(decode)的模式特定部分。同理,getRawFragment()返回原始的片段識別符號,而getFragment()返回經過解碼(decode)的片段識別符號。當然,還有其他方法獲取URI的基礎屬性:

//是否絕對URI
public boolean isAbsolute()

//是否不透明的URI,如果isOpaque()返回true,URI是不透明的
//只能獲取到模式、模式特定部分和片段識別符號,獲取不了host、port等
public boolean isOpaque()

public String getAuthority()

public String getRawAuthority()

public String getRawUserInfo()

public String getUserInfo()

public String getHost()

public int getPort()

public String getRawPath()

public String getPath()

public String getRawQuery()

public String getQuery()

PS:isOpaque()方法為true的時候,說明URI是不透明的,不透明的URI無法獲取授權機構、路徑、埠和查詢引數等。另外,上面的獲取屬性的方法有些方法存在getRawFoo()的對應方法,這些getRawFoo()方法就是獲取原始屬性的值,如果沒有Raw關鍵字,則返回解碼後的字串值。

解析相對URI

URI中提供了三個方法用於絕對URI和相對URI之間的轉換:

public URI resolve(URI uri)

public URI resolve(String str)

public URI relativize(URI uri)

其中,resolve方法是基於絕對URI和相對URI把相對URI補全為絕對URI,例如:

public static void main(String[] args) throws Exception{
    URI absolute = URI.create("http://localhost:8080/index.html");
    URI relative = URI.create("/hello.html");
    URI resolve = absolute.resolve(relative);
    System.out.println(resolve);
}
//控制檯輸出
http://localhost:8080/hello.html

relativize方法是基於絕對URI和相對URI反解出絕對URI中的相對URI部分,例如:

public static void main(String[] args) throws Exception{
    URI absolute = URI.create("http://localhost:8080/index.html");
    URI relative = URI.create("http://localhost:8080/");
    URI resolve = relative.relativize(absolute);
    System.out.println(resolve);
}

//控制檯輸出
index.html

URI的比較

URI類實現了Comparable介面,因此URI可以排序。URI的相等性不是基於字串直接比較,相等的URI在透明性上必須是一致的,例如都是不透明的,其他部分才可以進行比較。URI比較的時候,模式和授權機構是忽略大小寫的,其他部分必須區分大小寫,用於轉義無效字串的十六進位制數字除外。需要轉義的字元在轉義前和轉義之後進行比較,會認為是不同的URI。

//1.
URI uri1 = URI.create("http://localhost:8000/index.html");
URI uri2 = URI.create("http://LOCALHOST:8000/index.html");
System.out.println(uri1.equals(uri2));
//輸出:true

//2.
URI uri3 = URI.create("http://localhost:8000/index/A");
URI uri4 = URI.create("http://LOCALHOST:8000/index/%41");
System.out.println(uri3.equals(uri4));
//輸出:false

URI的字串表示

URI中有兩個方法返回其字串表示:

public String toString()

public String toASCIIString()

toString()方法返回未編碼的字串形式,也就是特殊的字元不使用百分號轉義,因此這個方法的返回值不能保證符合URI的語法,儘管它的各個部分是遵循URI的語法規範的。toASCIIString()方法返回經過編碼的字串形式(US-ACSII編碼),也就是特殊的字元一定經過了百分號轉義。toString()存在的意義是提高URI的可讀性,toASCIIString()方法存在的意義是提高URI的可用性。

URL

URL全稱是Uniform Resource Location,也就是統一資源位置。實際上,URL就是一種特殊的URI,它除了標識一個資源,還會為資源提供一個特定的網路位置,客戶端可以通過它來獲取URL對應的資源。

URL所表示的網路資源位置通常包括用於訪問伺服器的協議(如http、ftp等)、伺服器的主機名或者IP地址、以及資原始檔在該伺服器上的路徑。典型的URL例如http://localhost/myProject/index.html,它指示本地伺服器的myProject目錄下有一個名為index.html的文件,這個文件可以通過http協議訪問(實際上,URL不一定是指伺服器中的真實的物理路徑,因為我們一般在伺服器中部署應用,如Servlet應用,URL訪問的很可能是應用的介面,至於最終對映到什麼資源可以由應用自身決定)。

URL的語法

URL的語法表示形式為:

protocol://[email protected]:port/path?query#fragment

協議://使用者資訊@主機名:埠/路徑?查詢#片段

protocol:URL中的協議(protocol)是相應於URI中的模式(schema)的另一個叫法。URL中,協議部分可以是file、ftp、http、https、magnet、telnet或者其他定製協議字串(但是不包括urn)。

userInfo:URL中的使用者資訊(userInfo)是伺服器的登入資訊,這部分資訊是可選的。如果這部分資訊存在,一般會包含一個使用者名稱,極少情況下會包含一個口令。實際上URL攜帶使用者資訊是不安全的。

port:URL中的埠號(port)是指伺服器中應用的執行埠,預設埠為80,此部分資訊是可選的(也就如果不指定埠號就使用預設埠80)。

path:URL中的路徑(path)用於表示伺服器上的一個特定的目錄(其實說一個特定的檔案也可以),這個特定的目錄不一定是物理目錄,也有可能是邏輯目錄。這一點很容易說明,一般不可能把伺服器上面的目錄直接公開讓所有人訪問,伺服器上面跑的一般是Web(Java的話一般是Servlet)應用,路徑指向的實際資料來源甚至很大可能是在其他伺服器上的MySQL中的查詢結果。

query:查詢引數(query)一般是一個字串,它表示URL中向伺服器提供的附加引數,一般只使用在http協議的URL中,其中包含了表單資料,來源於使用者的輸入,表示形式是key1=value1&key2=value2&keyn=valuen。

fragment:片段(fragment)表示遠端伺服器資源的某個特定的部分。假如伺服器資源是一個HTML文件,此片段識別符號將制定為該HTML文件的一個錨(Anchor)。如果遠端資源是一個XML文件,那麼這個片段識別符號是一個XPointer。(如果用Markdown寫過部落格就知道,添加了導航目錄之後,片段就是目錄將要導航到的目的章節)

相對URL

URL可以告知瀏覽器一個文件(Document,假設URL對應伺服器上的資源統一叫做文件)的大量資訊:獲取文件所使用的協議、文件所在的主機、文件在該主機中的路徑等。文件中可能也存在引用和當前URL相同的URL,因此,在該文件中使用URL的時候並不要求完整地指定每一個URL,URL可以繼承其父文件的協議、主機名和路徑。繼承了父文件URL的部分資訊的這類不完整的URL稱為相對URL(Reletive URL),相反,完整指定所有部分的URL稱為絕對URL(Absolute URL)。在相對URL中,缺少的各個部分與請求該文件的URL對應的部分相同。舉個例子,我們訪問本地伺服器的一個HTML文件,URL為http://localhost:8080/myProject/index.html,index.html文件中存在一個超連結<a href="login.html">,當我們點選此超連結的時候,瀏覽器會從原始URL(http://localhost:8080/myProject/index.html)中截去index.html然後拼接login.html,最後訪問http://localhost:8080/myProject/login.html。

如果相對URL以"/"開頭,那麼它是相對於文件的根目錄,而不是當前的文件。舉個例子,我們訪問本地伺服器的一個HTML文件,URL為http://localhost:8080/myProject/index.html,index.html文件中存在一個超連結<a href="/login.html">,當我們點選此超連結的時候,瀏覽器會跳轉到http://localhost:8080/login.html。

相對URL有兩個顯著的優點:

  • 1、減少文件編寫量,畢竟可以省略一部分URL內容,不過這個不是重要的優點。
  • 2、重要的優點是:相對URL允許使用多協議來提供一個文件樹,例如http和ftp,使用相對URL編寫的文件可以從一個網站直接複製或者遷移到另一個網站而不會破壞文件內部URL連結。

URL類

java.net.URL類(後面直接叫URL)是JDK對URL的統一抽象,它是一個final修飾的類,也就是不允許派生子類。實際上,URL設計的時候採用了策略模式,不同的協議的處理器就是不同的策略,而URL類構成了上下文,通過它決定選用何種策略。URL的核心屬性包括協議(或者叫模式)、主機名、埠、查詢引數字串和片段識別符號(在JDK中被命名為ref)等,每個屬性都可以單獨設定。一旦一個URL物件被構造之後,它的所有屬性都不能改變,也就是它的例項是執行緒安全的。

構造URL例項

URL例項的主要構造方法如下:

//基於URL的各個部分構造URL例項,其中file相當於path、query和fragment三個部分組成
public URL(String protocol, String host, int port, String file) throws MalformedURLException

//基於URL的各個部分構造URL例項,其中file相當於path、query和fragment三個部分組成,使用預設埠80
public URL(String protocol, String host, String file) throws MalformedURLException

//基於URL模式構造URL例項
public URL(String spec) throws MalformedURLException

//基於上下文(父)URL和URL模式構造相對URL例項
public URL(URL context, String spec) throws MalformedURLException

下面基於上面幾個構造URL例項的方法舉幾個簡單的編碼例子:

//1.
//注意file要帶斜杆字首/
URL url = new URL("http", "127.0.0.1", 8080, "/index");
//輸出http://127.0.0.1:8080/index
System.out.println(url.toString());

//2.
URL url = new URL("http://127.0.0.1:8080/index");
//輸出http://127.0.0.1:8080/index
System.out.println(url.toString());

//3.
URL url = new URL("http", "127.0.0.1", "/index");
//輸出http://127.0.0.1/index
System.out.println(url.toString());

//4.
URL context = new URL("http", "127.0.0.1", "/index");
//構造相對URL,保留協議、host、port部分
URL url = new URL(context, "/login");
//輸出http://127.0.0.1/login
System.out.println(url);

上面只說到通過URL類去構造物件,其實還有其他方法獲取URL例項,例如:

URL systemResource = ClassLoader.getSystemResource(String name)

Enumeration<URL> systemResources = ClassLoader.getSystemResources(String name)

URL resource = UrlMain.class.getResource(String name)

URL resource = UrlMain.class.getClassLoader().getResource(String name)

Enumeration<URL> resources = UrlMain.class.getClassLoader().getResources(String name)

其中,ClassLoader.getSystemResource(String name)ClassLoader.getSystemResources(String name)都是先判斷是否存在SystemClassLoader,若存在則使用SystemClassLoader載入資源,否則使用BootstrapClassLoader(BootstrapClassPath)進行資源載入,簡單來說,這兩個方法就是使用系統類載入器載入資源,載入資源時候,從類路徑中載入資源,例如使用IDEA,則從編譯後的/target目錄中載入資源,這一點可以使用ClassLoader.getSystemResource("")進行驗證。其他三個方法Class#getResource(String name)Class#getClassLoader()#getResource(String name)Class#getClassLoader()#getResources(String name)本質上都是基於AppClassLoader進行資源載入,它們載入資源的時候是從當前的Class所在的類路徑(包括類的包路徑,如果使用IDEA一般也是/target/類所在的包目錄)進行載入,例如有一個類club.throwable.Main.class,如果目錄club.throwable存在一張圖片doge.jpg,則載入圖片的時候可以這樣子club.throwable.Main.class.getResource("doge.jpg")。值得注意的一點是,如果需要載入的資源是在一個特定目錄中,那麼Class#getResource(String name)中的name必須以檔案路徑分隔符開頭,例如Window系統中用'/',其他兩個基於通過ClassLoader例項直接載入的不需要使用檔案路徑分隔符開頭,這一點可以看Class#getResource(String name)方法中的resolveName(name)方法。

獲取URL的屬性

URL例項提供幾個方法用於獲取資料:

public final InputStream openStream() throws java.io.IOException

public URLConnection openConnection() throws java.io.IOException

public URLConnection openConnection(Proxy proxy) throws java.io.IOException

public final Object getContent() throws java.io.IOException

public final Object getContent(Class[] classes) throws java.io.IOException

InputStream openStream()

openStream()方法連線到URL所引用的資源,在客戶端和伺服器之間完成必要的握手之後,返回一個InputStream例項,用於讀取網路流資料。此方法返回的InputStream例項讀取到的內容是Http請求體的原始報文(如果使用Http協議的話),因此有可能是一個原始文字片段或者是一個二進位制的序列(例如圖片)。舉個例子:

    public static void main(String[] args) throws Exception{
        URL url = new URL("https://www.baidu.com");
        InputStream inputStream = url.openStream();
        int c;
        while ((c= inputStream.read()) != -1){
            System.out.print(c);
        }
        inputStream.close();
    }

URLConnection openConnection()

openConnection()openConnection(Proxy proxy)是相似的,只是後者可以使用代理。openConnection()方法為指定的URL新建一個socket,並且返回一個URLConnection例項,它表示一個網路資源開啟的連線,我們可以從這個開啟的連接獲取一個InputStream例項,用於讀取網路流資料。如果上面的過程出現呼叫失敗,會丟擲一個IOException。

Object getContent() Object getContent()Object getContent(Class[] classes)是相似的,後者可以通過Class陣列指定獲取到的URL中的請求體內容轉化為對應的型別的實體。Object getContent()實際上是呼叫了URLConnection例項中的getContent方法,一般來說,不建議使用這兩個方法,因為轉換的邏輯執行後的結果一般是不合符我們預想的結果。

獲取URL屬性

URL的屬性獲取可以理解為分解URL成URL組成的各個部分,這些部分的資訊可以單獨獲取。前面提到過URL的各個組成部分,這裡重複一下,URL分別由下面的幾個部分組成:

  • 模式(schema),也稱協議(protocol)。
  • 使用者資訊(UserInfo),這個並不常用。
  • 授權機構,一般是"主機名(Host):port"的格式。
  • 路徑。
  • 片段識別符號,在Java中稱為段或者ref。
  • 查詢字串。

URL類中提供對應的方法分別是:


//獲取模式(協議)
public String getProtocol()

//獲取主機名
public String getHost()

//獲取授權機構,一般是host:port的形式
public String getAuthority()

//獲取埠號port
public int getPort()

//返回協議的預設埠,如http協議的預設埠號為80,如果沒有指定協議的預設埠則返回-1
public int getDefaultPort()

//返回URL字串中從主機名後的第一個斜杆/一直到片段識別符號的#字元之前的所有字元
public String getFile()

//返回的值和getFile()相似,但是不包含查詢字串
public String getPath()

//返回URL的片段識別符號部分
public String getRef()

//返回URL的查詢字串
public String getQuery()

//返回URL中的使用者資訊,不常用
public String getUserInfo()

其中需要注意的是getFile()getPath(),舉個例子:

URL url = new URL("https://localhost:8080/search?name=doge#anchor-1");
System.out.println(String.format("path=%s", url.getPath()));
System.out.println(String.format("file=%s", url.getFile()));
//控制檯輸出
path=/search
file=/search?name=doge

比較

URL例項的比較通常是使用equals()hashCode()方法,當且僅當兩個URL指向相同的主機、埠和路徑上的資源,並且兩者的片段識別符號和查詢字串都相同的時候,才會認為兩個URL是相等的。equals()呼叫的時候會嘗試使用DNS解析主機,此方法有可能是一個阻塞的IO操作,會造成比較大的效能消耗,這個時候需要考慮使用快取,或者把URL轉化為URI進行比較。

轉換

URL類中有三個常用的例項方法用於轉換為另一種形式:

public String toString()

public String toExternalForm()

public URI toURI()

實際上,toString()最終呼叫的是toExternalForm(),而toExternalForm()方法是使用StringBuilder把URL的各個組成部分拼接,返回的字串可以方便直接使用在瀏覽器中。toURI()方法就是把URL例項轉化為URI例項。

URL的編碼和解碼

我們在做Web專案或者使用POSTMAN的時候經常會看到x-www-form-urlencoded,它是常用的媒體型別或者說內容型別(ContentType),使用這種型別的時候,URL中的字元需要進行編碼,那麼為什麼需要進行編碼呢?這個是歷史遺留原因,因為發明Web或者說Http(s)協議的時候,Unicode編碼並未完全普及,URL中使用的字元必須來自ASCII的一個固定子集,這個固定子集是:

  • 大寫字母A-Z。
  • 小寫字母a-z。
  • 數字0-9。
  • 標點符號字元-_.!~*'(和,)

其他字元如:/&[email protected]#;$+=%也可以使用,但是它們限定於使用在特定的用途,如果這些字串出現在URL路徑或者查詢字串,它們以及路徑和查詢字串的內容必須進行編碼。

這裡有個需要注意的地方:URL編碼只針對URL路徑和URL路徑之後的部分,因為URL規範中規定,路徑之前的部分必須滿足ASCII固定子集的內容。

URL編碼的方法很簡單:除了ASCII數字、字母和部分指定的標點符號之外,所有的其他字元都要轉化為位元組表示,每個位元組要轉化為百分號(%)後面加2位十六進位制數字。空格是一種特殊的字元,它使用得比較普遍,一般空格可以編碼為%20或者加號(+),但是加號本身的編碼為%2B。而/#=&和?不作為分隔符的時候,必須進行編碼。

解碼過程就是上面編碼過程的逆向操作,這裡不具體展開。

Java中提供兩個類java.net.URLEncoder和java.net.URLDecoder分別用於URL的編碼和解碼,注意需要使用兩個引數的靜態方法encode(String value,String charset)decode(String value,String charset),單引數的方法已經過期,不建議使用。注意在使用java.net.URLEncoder和java.net.URLDecoder的時候,它們的API不會判斷URL的什麼部分需要編碼和解碼,什麼部分不需要編碼和解碼,直接整個URL字串丟進去編碼一定會出現意料之外的結果。舉個反面列子:

String url = "http://localhost:9090/index?name=張大doge";
String encode = URLEncoder.encode(url, "UTF-8");
System.out.println(encode);
//輸出:http%3A%2F%2Flocalhost%3A9090%2Findex%3Fname%3D%E5%BC%A0%E5%A4%A7doge

實際上,我們只需要對Path和Path之後的字元進行編碼和解碼,例如對於URLhttp://localhost:9090/index?name=張大doge,我們需要編碼和解碼的部分只有index、name和張大doge這三個部分,其他部分應該保持原樣。正確的例子如下:

public static void main(String[] args) throws Exception {
    String raw= "http://localhost:9090/index?name=張大doge";
    String base = raw.substring(raw.lastIndexOf("//"));
    String pathLeft = base.substring(base.lastIndexOf("/") + 1);
    String[] array = pathLeft.split("\\?");
    String path = array[0];
    String query = array[1];
    base = raw.substring(0,raw.lastIndexOf(path));
    path = URLEncoder.encode(path, "UTF-8");
    String[] queryResult = query.split("=");
    String queryKey = URLEncoder.encode(queryResult[0], "UTF-8");
    String queryValue = URLEncoder.encode(queryResult[1], "UTF-8");
    System.out.println(base + path + "?" + queryKey + "=" + queryValue);
}
//輸出結果:http://localhost:9090/index?name=%E5%BC%A0%E5%A4%A7doge
//其中UTF-8編碼中張的十六進位制表示為E5 BC A0,大的十六進位制編碼為E5 A4 A7

代理(Proxy)

許多系統會通過代理伺服器訪問Web應用或者其他伺服器中的資源,代理伺服器接收從本地客戶端發出的請求再轉發請求到遠端伺服器,然後遠端伺服器返回請求的結果到代理伺服器,代理伺服器接收到結果之後會把結果回傳到本地伺服器。這樣做有兩個比較重要的原因:

  • 1、安全原因,防止遠端的主機瞭解關於本地網路配置的細節。
  • 2、過濾出站請求,限制瀏覽一些禁用的網站。

在Java中除了TCP連線使用傳輸層的SOCKET代理,其他應用層代理都不支援。Java對於SOCKET沒有提供禁用代理的選項,但是可以通過下面三個系統屬性配置來開啟和限制代理:

http.proxyHost:代理伺服器的主機名,預設不設定此係統屬性。
http.proxyPort:代理伺服器的埠號,預設不設定此係統屬性。
http.noneProxyHosts:不需要代理訪問的主機名,多個用豎線|分隔,預設不設定此係統屬性。

舉個例子:
System.setProperty("http.proxyHost", "localhost");
System.setProperty("http.proxyPort", 1080);
System.setProperty("http.noneProxyHosts", "www.baidu.com|github.com");

Proxy類

java.net.Proxy類提供了對代理伺服器更細粒度的控制,也就是說這個類允許在程式設計的使用使用不同的遠端伺服器作為代理伺服器,而不是通過系統屬性全域性配置代理。Proxy目前支援三種代理型別,分別是:

  • Proxy.Type.DIRECT:直連,也就是不使用代理。
  • Proxy.Type.HTTP:HTTP代理。
  • Proxy.Type.SOCKS:socket的V4或者V5版本的代理。

使用的時候如下:

SocketAddress socketAddress = new InetSocketAddress("localhost", 80);
Proxy proxy = new Proxy(Proxy.Type.HTTP, socketAddress);
Socket socket = new Socket(proxy);
//...

ProxySelector類

每個執行中的Java虛擬機器都會存在一個java.net.ProxySelector例項物件,用來確定不同連線使用的代理伺服器。預設的java.net.ProxySelector的實現是sun.net.spi.DefaultProxySelector的例項,它會檢查各種系統屬性和URL的協議,再決定如果連線到不同的遠端代理伺服器,當然,開發者也可以繼承和實現自定義的java.net.ProxySelector,從而可以根據協議、主機、路徑日期等其他標準來選擇不同的代理伺服器。java.net.ProxySelector的幾個核心的抽象方法如下:

//獲取預設的ProxySelector例項
public static ProxySelector getDefault()

//設定預設的ProxySelector例項
public static void setDefault(ProxySelector ps)

//通過URI獲取可用的代理列表
public abstract List<Proxy> select(URI uri)

//告知ProxySelector不可用的代理和出現的異常
public abstract void connectFailed(URI uri, SocketAddress sa, IOException ioe)

如果需要擴充套件的話,最好加入快取的功能,快取可用的Proxy列表,一旦出現Proxy不可用,通過connectFailed進行清理和剔除不可用的代理節點即可。

小結

URL和URI是當前的網路世界或者系統中資源的重要識別符號,瞭解它們的基礎知識才能更好地進行網路程式設計。URI是統一資源識別符號,它可以表示任何介質中的資源。而URL是統一資源位置,一般特指因特網中的網路資源位置,使用於Http或者Https協議中。很顯然,URI可以表示的範圍更大,URI實際上是包含了URL。而兩者的區別可以參看上面的幾個小節。

(c-5-d e-20181003)