轉 Java網路編程之URI、URL研究(上)

來源:互聯網
上載者:User
URI、URL和URN是識別、定位和命名互連網上的資源的標準途徑。本文分析了URI、URL和URN和Java API的URI和URL類(以及與URL相關的類),並示範了如何在程式中使用這些類。

  1989年Tim Berners-Lee發明了互連網(World Wide Web)。WWW被認為是全球互連的實際的和抽象的資源的集合--它按需求提供資訊實體--通過互連網訪問。實際的資源的範圍從檔案到人,抽象的資源套件括資料庫查詢。因為要通過多樣的方式識別資源(人的名字可能相同,然而電腦檔案只能通過唯一的路徑名稱組合訪問),所以需要標準的識別WWW資源的途徑。為了滿足這種需要,Tim Berners-Lee引入了標準的識別、定位和命名的途徑:URI、URL和URN。

  URI、URL和URN是什嗎?

  體系中的URI、URL和URN是彼此關聯的。URI的範疇位於體系的頂層,URL和URN的範疇位於體系的底層。這種排列顯示URL和URN都是URI的子範疇,1所示:


圖1:URI、URL和URN之間的層次關係。URL和URN是URI的子範疇。

  URI表示的是統一的資源標識,它是以某種統一的(標準化的)方式標識資源的簡單字串。典型情況下,這種字串以scheme(命名URI的名字空間的標識符--一組相關的名稱)開頭,文法如下:

  [scheme:] scheme-specific-part

  URI以scheme和冒號開頭。Scheme用大寫/小寫字母開頭,後面為空白或者跟著更多的大寫/小寫字母、數字、加號、減號和點號。冒號把scheme與scheme-specific-part分開了,並且scheme-specific-part的文法和語義(意思)由URI的名字空間決定。其中一個例子是http://www.cnn.com,其中http是scheme,//http://www.cnn.com是 scheme-specific-part,並且它的scheme與scheme-specific-part被冒號分開了。
我們可以把URI按照絕對的或相對的分類。絕對的URI指以scheme(後面跟著冒號)開頭的URI。前面提到的http://www.cnn.com就是絕對的URI的一個例子,其它的例子還有mailto:jeff@javajeff.com、news:comp.lang.java.help和xyz://whatever。你可以把絕對的URI看作是以某種方式引用某種資源,而這種方式對標識符出現的環境沒有依賴。如果使用檔案系統作類比,絕對的URI類似於從根目錄開始的某個檔案的路徑。與絕對的URI不同的,相對的URI不是以scheme(後面跟著冒號)開始的URI。它的一個例子是articles/articles.html。你可以把相對的URI看作是以某種方式引用某種資源,而這種方式依賴於標識符出現的環境。如果用檔案系統作類比,相對的URI類似於從目前的目錄開始的檔案路徑。

URI可以進一步分為不透明的和分層的兩類。不透明的URI指scheme-specific-part不是以正斜杠(/)開頭的絕對的URI。其例子有news:comp.lang.java和前面的mailto:jeff@javajeff.com。不透明的URI並不是用於分解的(超出了識別scheme的範疇),因為不需要驗證scheme-specific-part的有效性。與它不同的是,分層的URI可以是以正斜杠開頭的絕對的URI或相對的URL。

  與不透明的URI不同,分層的URI的scheme-specific-part必須被分解為幾個組成部分。這些組成部分是什嗎?分層的URI標識組件的普通子集的scheme-specific-part符合下面的文法:

  [//authority] [path] [?query] [#fragment]

  可選的authority組件標識了該URI名字空間的命名機構。如果有這一部分,它就是以一對正斜杠開始的,它可以是基於伺服器或基於註冊的,並且它以後面的正斜杠、問號或沒有其它符號結束。基於註冊的授權機構組件有特定大綱的文法(本文沒有討論,因為很少使用它),而基於伺服器的授權機構組件的文法如下:

  [userinfo@] host [:port]

  按照這種文法,基於伺服器的授權機構組件可以隨意的以使用者資訊(例如使用者名稱)開始,後面跟著一個@符號,緊接著是主機的名稱,以及冒號和連接埠號碼。例如jeff@x.com:90就是一個基於伺服器的授權機構組件,其中jeff包含了使用者資訊,x.com包含了主機,90包含了連接埠。

  可選的path組件根據授權機構組件(如果提供了)或大綱(如果沒有授權機構組件)識別資源的定位(或位置)。路徑(path)可以分成一系列的路徑片斷(path segment),每個路徑片斷使用正斜杠與其它的路徑片斷隔開。如果路徑的第一個路徑片斷以一個正斜杠開始,該路徑就被認為是絕對的。否則路徑就被認為是相對的。例如,/a/b/c由三個路徑片斷--a、b和c組成了一個路徑,此外,這個路徑是絕對的,因為第一個路徑片斷(a)的首碼是正斜杠。

  可選的query組件識別要傳遞給某種資源的資料。這種資源使用該資料擷取或產生其它的傳遞迴調用者的資料。例如,http://www.somesite.net/a?x=y, x=y就是一個查詢(query),在這個查詢中,x=y是傳遞給某種資源的資料--x是某種實體的名稱,y是該實體的值。

  最後一個組件是fragment。儘管該組件作為URI的一部分出現,但不是絕對的。當使用URI進行某種檢索操作時,後面執行操作的軟體使用fragment聚焦於軟體感興趣的資源部分(在該軟體成功檢索到資源的資料後)。

  為了實際表現前面提到的組件資訊,可以使用下面的URI:

  ftp://george@x.com:90/public/notes?text=shakespeare#hamlet

  上面的URI把ftp識別為大綱,把george@x.com:90識別為基於伺服器的授權機構(其中george是使用者資訊,x.com是主機,90是連接埠),把/public/notes識別為路徑,把text=shakespeare識別為查詢,把hamlet識別為片斷。本質上它是一個叫做george的使用者希望通過/public/notes路徑在伺服器x.com的90連接埠上檢索shakespeare文本的hamlet資訊。在shakespeare成功的返回到該程式後,程式定位hamlet段並把它呈獻給該使用者。

標準化可以通過目錄術語來理解。假定目錄x直接位於根目錄之下,x有子目錄a和b,b有檔案memo.txt,a是目前的目錄。為了顯示memo.txt中的內容(在微軟Windows下),你可能輸入type /x/./b/memo.txt。你也可能輸入type /x/a/../b/memo.txt,在這種情況下,a和..的出現是沒有必要的。這兩種形式都不是最簡單的。但是如果輸入/x/b/memo.txt,你就指定了最簡單的路徑了,從根目錄開始訪問memo.txt。最簡單的/x/b/memo.txt路徑就是標準化的路徑。

  通常通過基本的和相對的URI訪問資源。基本的URI是絕對的URI,它唯一地標識了某種資源的名字空間,而相對的URI標識了與基礎的URI相對的資源。(與基本的URI不同,相對的URI在某種資源的生存周期內可以永遠不需要改變)。因為基本的和相對的URI都不能完整的識別某種資源,有必要把兩種URI通過解析過程合并。相反地,通過相對化從合并的URI中提取相對的URI也是可行的。

  注意

  不透明的URI與其它的URI不同,它不服從標準化、分解和相對化。

  假定你把x://a/作為基礎的URI,並把b/c作為相對的URI。根據基礎URI分解這個相對的URI將產生x://a/b/c。根據x://a/相對化x://a/b/c將產生b/c。

  URI不能定位或讀取/寫入資源。這是統一的資源定位器(URL)的任務。URL是一種URI,但是它的大綱組件是已知的網路通訊協定(簡稱協議),並且它把URI組件與某種通訊協定處理常式(一種資源定位器和根據協議建立的約束規則與資源通訊的讀/寫機制)。

  URI一般不能為資源提供持久不便的名稱。這是統一的資源命名(URN)的任務。URN也是一種URI,但是全球唯一的、持久不便的,即使資源不在存在或不再使用。

使用URI

  網路API通過提供了URI類(位於java.net程式包中),使我們在原始碼層使用URI成為可能。URI的建構函式建立了封裝URI的URI對象;URI的方法建立URI對象;如果授權機構組件是基於伺服器的就分析它,提取URI組件,決定URI對象的URI是絕對的還是相對的;決定URI對象的URI是不透明的還是分層的;比較兩個URI對象中的URI;標準化(normalize)URI對象的URI;根據URI對象的基礎URI分解某個相對的URI以得到已分解的URI;根據URI對象的基礎URI關聯某個已分解的URI以得到相對的URI,把URI對象轉換為URL對象。
我們進一步查看URI類,在它裡面有五個建構函式。最簡單的是URI(String uri)。這個建構函式把URI作為String類型的參數,把URI分解為組件,並把這些組件儲存在一個新的URI對象中。如果String對象的URI(通過uri引用)違反了RFC 2396的文法規則,其它的四個建構函式URI(String uri)將會產生一個java.net.URISyntaxException對象。

  下面的代碼片斷示範了使用URI(String uri)建立封裝了一個簡單的URI組件的URI對象:

URI uri = new URI ("http://www.cnn.com");

  典型情況下URI建構函式用於建立封裝使用者指定的URI的URI對象。因為使用者可能輸入不正確的URI,所以URI建構函式產生已檢查的URISyntaxException對象。這意味著你的代碼必須明確地嘗試著調用某個URI建構函式並捕捉異常,或者通過在該方法的Throws子句中列舉URISyntaxException以"推卸責任"。

  如果你知道URI是有效(例如在原始碼中的URI),將不會產生URISyntaxException對象。因為在這種情況下處理某個URI建構函式的異常處理要求可能有困難,所以URI提供了靜態create(String uri)方法。這個方法分解通過uri引用的String對象中包含URI,如果該URI沒有違反任何文法規則就建立URI對象(並從方法中返回對它的引用),否則將捕捉到一個內部的URISyntaxException對象,把該對象封裝金一個未檢查的IllegalArgumentException對象中,並拋出這個IllegalArgumentException對象。因為IllegalArgumentException是未檢查的,你不需要明確的嘗試代碼並捕捉異常或把它的類名稱列舉在Throws子句中。

  下面的代碼片斷示範了create(String uri):

URI uri = URI.create ("http://www.cnn.com");

  URI建構函式和create(String uri)方法試圖分解出某個URI的授權機構組件的使用者資訊、主機和連接埠部分。對於按正常形式形成的基於伺服器的授權機構組件,它們是會成功的。對於按拙劣的形式形成的基於伺服器的授權機構組件,他們將會失敗--並且把該授權機構組件當作是基於註冊的。有時你可能知道某個URI的授權機構組件必須是基於伺服器的。你可以確保該URI的授權機構組件分解出使用者資訊、主機和連接埠,或者你可以確保將產生一個異常(伴隨著相應的診斷資訊)。你可以通過調用URI的parseServerAuthority()方法實現這種操作。如果成功分解出URI,該方法將返回包含提取的使用者資訊、主機和連接埠部分的URI的新URI對象的一個引用(但是如果授權機構組件已經被分解過了,將會返回調用parseServerAuthority()的URI對象的引用。),否則該方法將產生一個URISyntaxException對象。

  下面的代碼片斷示範了parseServerAuthority():

// 下面的parseServerAuthority()調用出現後會發生什麼情況?
URI uri = new URI ("//foo:bar").parseServerAuthority();

一旦擁有了URI對象,你就可以通過調用getAuthority()、getFragment()、getHost()、getPath()、getPort()、getQuery()、getScheme()、getSchemeSpecificPart()和 getUserInfo()方法提取多種組件。你也可以通過調用isAbsolute()確定該URI是絕對的還是相對的,通過調用isOpaque()確定該URI是不透明的還是分層的。如果傳回值是true意味著該URI是絕對的或不透明的,如果傳回值是false意味著該URI是相對的或分層的。

  列表1中的程式用命令列參數建立了一個URI對象,調用URI組件提取方法來檢索URI的組件,並調用URI的isAbsolute()和isOpaque()方法把該URI分類為絕對的/相對性和不透明的/分層的。

  列表1: URIDemo1.java

// URIDemo1.java

import java.net.*;

class URIDemo1
{
public static void main (String [] args) throws Exception
{
if (args.length != 1)
{
System.err.println ("usage: java URIDemo1 uri");
return;
}

URI uri = new URI (args [0]);

System.out.println ("Authority = " +uri.getAuthority ());
System.out.println ("Fragment = " +uri.getFragment ());
System.out.println ("Host = " +uri.getHost ());
System.out.println ("Path = " +uri.getPath ());
System.out.println ("Port = " +uri.getPort ());
System.out.println ("Query = " +uri.getQuery ());
System.out.println ("Scheme = " +uri.getScheme ());
System.out.println ("Scheme-specific part = " +
uri.getSchemeSpecificPart ());
System.out.println ("User Info = " +uri.getUserInfo ());
System.out.println ("URI is absolute: " +uri.isAbsolute ());
System.out.println ("URI is opaque: " +uri.isOpaque ());
}
}

  輸入java URIDemo1命令後,列表1的輸出結果如下:

query://jeff@books.com:9000/public/manuals/appliances?stove#ge:
Authority = jeff@books.com:9000
Fragment = ge
Host = books.com
Path = /public/manuals/appliances
Port = 9000
Query = stove
Scheme = query
//jeff@books.com:9000/public/manuals/appliances?stove
User Info = jeff
URI is absolute: true
URI is opaque: false

  上面的輸出顯示該URI是絕對的,因為它指定了一個大綱(query),並且該URI是分層的,因為query後面有/符號。

  技巧

  你應該調用URI的compareTo(Object o)和equals(Object o)來決定URI的次序(為了排序目的)和等同性。你可以參考SDK文檔,查閱這些方法的更多資訊。
URI類支援基本的URI操作,包括標準化(normalization)、分解(resolution)和相對化(relativization)。標準化是通過URI的normalize()方法支援的。調用normalize()時,它返回對新URI對象的引用,該對象包含調用的URI對象的URI的標準的表現。

  列表2示範了normalize()方法。它把URI作為程式的唯一的參數,URIDemo2列印出標準的相等的URI。

  列表2: URIDemo2.java

// URIDemo2.java

import java.net.*;

class URIDemo2
{
public static void main (String [] args) throws Exception
{
if (args.length != 1)
{
System.err.println ("usage: java URIDemo2 uri");
return;
}

URI uri = new URI (args [0]);

System.out.println ("Normalized URI = " +
uri.normalize ().toString ());
}
}

  在編譯URIDemo2後,在命令列輸入java URIDemo2 x/y/../z/./q,將看到下面的輸出:

Normalized URI = x/z/q

  上面的輸出顯示y、..和.消失了。這是因為..意味著你想直接在x下面訪問名字空間的z部分,.意味著你希望訪問與z部分相關的名字空間的q部分。

  URI通過提供resolve(String uri)、resolve(URI uri)和relativize(URI uri)方法支援反向解析和相對化操作。如果uri引用是空的(null)這三個方法都會產生NullPointerException對象。同樣,如果指定的URI違反了RFC 2396文法規則,resolve(String uri)通過的內部的create(String uri)調用間接地產生一個IllegalArgumentException對象。

  列表3的代碼示範了resolve(String uri)和relativize(URI uri)。

  列表3: URIDemo3.java

// URIDemo3.java

import java.net.*;

class URIDemo3
{
public static void main (String [] args) throws Exception
{
if (args.length != 2)
{
System.err.println ("usage: " +
"java URIDemo3 uriBase uriRelative");
return;
}

URI uriBase = new URI (args [0]);
System.out.println ("Base URI = " +uriBase.toString ());

URI uriRelative = new URI (args [1]);
System.out.println ("Relative URI = " +uriRelative.toString ());

URI uriResolved = uriBase.resolve (uriRelative);
System.out.println ("Resolved URI = " +uriResolved.toString ());

URI uriRelativized = uriBase.relativize (uriResolved);
System.out.println ("Relativized URI = " +uriRelativized.toString ());
}
}

  在編譯URIDemo3後,在命令列輸入java URIDemo3 http://www.somedomain.com/ x/../y. ,輸出如下:

Base URI = http://www.somedomain.com/
Relative URI = x/../y
Resolved URI = http://www.somedomain.com/y
Relativized URI = y

  上面的輸出顯示相對的URI的x/../y根據基礎URI http://www.somedomain.com/分解並(在內部)標準化,取得了已分解的http://www.somedomain.com/URI。給定該URI和基礎URI,該已分解的URI根據基礎URI相對化獲得了y,它是原始的但是標準的相對的URI。

  技巧

  調用URI的toURL()方法把URI轉換為URL。

  在本周日的專題中我將向讀者介紹如何使用URL以及MIME(多用途的網際郵件擴充協議)的概念以及它如何與URL發生聯絡,敬請期待。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.