程序員都應該知道的URI,一文幫你全面瞭解

URI 是每個程序員都應該瞭解的概念,同時相關聯的還有 URL, URN 等概念簇。瞭解這些概念,可以幫助我們更好地窺探萬維網(WWW)的設計,同時也能幫我們在工作中有效解決跟 URI 相關概念的問題,更加理解 encode,decode 工作原理,更好地助力網絡編程!

1.URI

URI(Uniform Resource Identifier) ,意爲統一資源標識符,提供了一套簡單可擴展的方式對資源進行標識。

1.1 URI 的前世今生

爲什麼會有 URI?
隨着萬維網的發展,需要有各種不同類型的資源被在網絡上查找以及傳輸。因此,也就需要一種唯一的可在萬維網上傳播的標識,這樣的統一資源標識就稱爲 URI。當然,資源在這裏是一種籠統概念,或者抽象概念,可以泛指可以被標識的實體,就像一個網頁,一本e-book, 一份 pdf 等等,只要有需要被呈現或者傳輸,都可以稱爲一種資源。

萬維網奠基人Tim Berners-Lee關於超文本(hypertext)的提案中間接提出了用來標識超鏈接的想法–URL(Uniform Resource Locator)。因此,URL 也就最早被用來進行網絡上可以提供訪問的地址表示。隨着HTTP, HTML 以及瀏覽器的逐步發展,越來越需要把標識資源可訪問地址以及單出命名錶示資源這兩種方式分開,因此也就提出了 URN(Uniform Resource Name),並用來表示後者。
1601481963133af1d4cf9300e41299cdd8e76d4ce49a3.png

IETF(網絡工程任務小組)主要負責 URI 相關標準制訂。
160151617244204c7ab6d7b584c66aaa5fb4ee94d6e20.png

  • 1994年發佈RFC1630, 指出了 URL 和 URN 的存在,同時定義了 URI 的正式語法。
  • 94年12月,RFC1738正式提出了 absolute 和relative URL, RFC2141則補充了URN 相關的文法和語法定義。
  • 1999年的 RFC2732允許 URI 使用 IPv6地址
  • 在2005年發佈的RFC3986標準,解決了上述標準提出的一些短板,同時標誌着URI 通用語法正式稱爲官方互聯網協議
  • RFC3305標準指出,雖然 URL 名詞被廣泛使用,但是其本身可能被逐漸廢棄,並且只用來做爲一些 URI 作爲間接提供該資源訪問地址的提示。並且指出資源標識符不需要表示該資源通過網絡的訪問地址,或者根本不需要隱含該資源是基於網絡提供的。(這裏相當矛盾,其實 URL 已經作爲民間事實標準並被廣泛使用,也不是標準想推翻就能立刻推翻的 - -)

1.2 URI 和 URL,URN 比較

瞭解到 URI 和 URL,URN 整體的歷史,可以看出來最早 URI和 URL 其實是一脈相源的。後來爲了兼容單純通過命名或者名稱來標識某個資源(並不是可被網絡直接訪達或者包含包含網絡訪問地址)的情況,提出了 URN標準。由此可見,這三個名稱都可以表示對一項資源的定位標識。比較有意思的問題是,在平常的工作溝通中,如何區分,並且在什麼樣的場景下該使用哪個名稱?
160281482081581bfcde4ae77465ca7966aa28b6e005c.png

1.2.1 基本概念

先具體瞭解每個名稱的基本概念:
1.URI
統一資源標識符。
用來表示某個特定資源。設計出來可以進行任何實體或者非實體的標識,但是目前被經常用於在網絡上可傳輸內容的標識。URI 是由一串特定字符集的字符組成,並且由 IETF 制訂的標準定義了一組語法規則,用來保證某個資源的統一和唯一標識。

2.URL
統一資源定位符。
也可以被稱爲網絡地址。在萬維網上,每個資源都有可以有唯一地址指向該資源,同時,通過該地址可以進行資源的讀寫,這樣的地址標識就稱爲 URL。URL 包含了目前網絡上常見的格式,包括 web 站點地址 http, 文件傳輸協議ftp, emal 地址協議 mailto以及數據庫訪問地址 JDBC 等。

3.URN
統一資源名稱。
URN用來通過名稱標識在特定命名空間的某個資源,同時希望爲資源可以提供一種較持久的,與位置和存取方式無關的表示方式。URN 並不關注這個表示名稱裏是否隱含了該資源的位置,或者如何獲取它,也不一定代表該資源一定可用。
舉個例子,在ISBN(Internal Standard Book Number)系統中,一個編號(類似9971-5-0210-0)代表了一個書本資源,該編號在 URN 中可以表示爲 urn:isbn:9971-5-0210-0, 但是這個編號並沒有給出在哪裏或者如何找到這本書的信息,它只能唯一標識了這本書。

1.2.1 三者之間的關係

先上圖來說明 URI,URL 和 URN 之間的關係。
1601520706993142c193f0da14e8bb6acaf3b173cabf2.png
URI 可以認爲是一個抽象的概念,所有的 URL 以及 URN 都是 URI。RFC3986標準中有這樣一段:

A URI can be further classified as a locator, a name, or both. The term “Uniform Resource Locator” (URL) refers to the subset of URIs that, in addition to identifying a resource, provide a means of locating the resource by describing its primary access mechanism (e.g., its network “location”).
rfc 3986, section 1.1.3

URI 可以被分類成 locator 或者對應的名稱表示,也就是包含了 URL 和 URN 的概念。因此,平常我們在說 URL 的時候,它其實也可以被稱爲 URI。

同樣,這裏有個非常有意思的問題,URN 其實比較好區分開,在使用唯一標識資源名稱時可以使用,但是 URI 和 URL 如何區分在哪個場景進行使用?
這個問題其實和 RFC3986標準定義的不夠清楚有關,請再看下面這一段:

The URI itself only provides identification; access to the resource is neither guaranteed nor implied by the presence of a URI.

rfc 3986, section 1.2.2

URI 不保證提供該資源的訪問方式,或者隱含保證該資源是否存在(其實語義就是該 URI 就是一個名稱表示),但是在上一段中又聲明瞭URI 會被分類成name 或者 locator,表示 URI 應該包含locator 這種訪問方式。再看下面這一段:

Each URI begins with a scheme name, as defined in Section 3.1, that refers to a specification for assigning identifiers within that scheme.

rfc 3986, section 1.1.1

每個 URI 都需要包含有起始 scheme 名稱。比如:https://www.example.com,這樣的一串字符串就可以稱爲 URI,但是明確標識了應該如何去訪問這個資源,同時它也是 URL,因爲 URL 是用來告知接收方獲取該資源的方式。

IETF在RFC3986中也有一段關於 URI 和 URL 使用方式的說明:

Future specifications and related documentation should use the general term “URI” rather than the more restrictive terms “URL” and “URN”

rfc 3986, section 1.1.3

這樣看來,好像IETF 更支持使用 URI 來代替 URL 這個稱呼。但是考慮到 URL 目前已經成爲用來描述網絡上資源定位的事實名稱,而且 RFC3986已經誕生超過15年了(有些條目確實跟不上時代發展速度),所以在針對互聯網資源定位(即網絡地址)的時候,URL 可以算是更貼切的名稱。當然,如果對方跟你談 URI等等,這也沒問題,因爲 URI 算是超類,並且也可以代表該資源。

下面是這個問題結論:

  • URI 是一種標記符
  • URL 是可以告訴你如何去訪問或者獲取該資源的一種標記符
  • 在描述網絡資源地址的時候,用哪種都沒問題,需要明確的原則就是最好和你的信息接收方用同樣的稱呼,方便理解
  • 如果覺得不好拿捏屬於 URL 或者 URN,那就可以直接使用 URI 描述

2.URI 字符集

2.1 URI的設計點

URI 需要提供一種簡單,可擴展的方式來唯一標識資源。同時,又需要考慮到在不同媒介上進行傳播的表示形式。因此,URI 在設計時需要考慮到以下幾點:

  • URI 需要是可移植的。

不同的系統,或者不同的接收方之間都可以使用 URI 協議來標識資源。URI 可以被表示成多種形式,比如說在紙上書寫的字符串,或者屏幕上的像素,或者一系列通過編碼的二進制流等。URI 的解析只跟這些呈現方式所關聯的字符串有關,而跟具體表現方式,載體無關。
考慮到 URI 更多需要在網絡場景傳輸,因此:

  • URI 是由一串字符序列組成
  • URI 可能會從非網絡環境中移植到網絡環境下,但是網絡環境的輸入一般受制於鍵盤,鼠標等輸入載體,因此最好由可以被這些物理載體方便輸入的字符呈現
  • URI 一般需要被人們記住並使用,所以這些字符最好是人們經常使用並且熟悉的內容

基於上述考慮,URI 爲一串受限的字符所組成的字符串,並選擇 US-ASCII 作爲字符集。US-ASCII 字符集基本上被所有系統支持,而且兼容性良好,能夠支持 URI 所需要的移植性。

  • URI 需要將標識和動作分開

這一層思想其實是需要將表示和表現分開。URI 只關注某個資源的標識,如果進行這個資源的存取或者訪問不做任何方式的保證。同資源相關的動作,引用等,在設計時被交給具體實現 URI 下 scheme 的協議來制訂,例如,http 協議會具體關心一個用’http’ scheme 表示的資源如何進行’get’, ‘update’,'delete’等一系列操作等。
這樣可以保證 URI 協議的相對穩定,以及比較好的擴展性

  • 層級標識

由於資源經常具有層級關係,比如在一個 example.com 站點下可能會掛有多個資源,或者下面會有一個目錄’dir’, 該目錄下會包含多個資源,這就意味着URI 需要有一種層級的組織方式。
在設計中也考慮到了這樣類型的資源組織方式,允許 URI 按照層級組織,並且在字符串上按照從左到右的順序拆分組件。
類似於常用操作系統的文件系統一樣,URI 可以用來還原具有層級關係的資源系統的組織結構。

2.2 URI 所選擇的字符集

如上所屬,URI 選擇 通過US-ASCII 字符集來進行表示,並限制使用從其中所挑選的一部分字符,數字以及符號。而且,由於需要支持層級結構,以及 URI 自身包含了不同的部分,因此也需要保留一些字符用來做這些有語義的部分的分隔。

Note: 由於需要對字符集或者語法進行描述,下文都是用 IETF使用的通用描述系統ABNF(Augmented Backus-Naur form), 即增強巴科斯範式。
增強巴科斯範式所定義的語法結構一般如下:

rule = definition / definition; comment CR LF
rule = <a>*<b>element

表示一組規則由一系列字符串組成的定義來描述,第一組 rule通過’/‘來表示定義中’或者’的關係。如果該條規則需要增加註釋,那麼需要通過’;'來標識註釋的開始
第二組 rule 表示重複規則,其中 a標識最少重複次數,b 標識最多重複次數。例如,2*3element標識 element 最少出現兩次,最多出現三次

關於增強巴科斯範式的具體內容請參照:
https://en.wikipedia.org/wiki/Augmented_Backus%E2%80%93Naur_form

2.2.1 Percent-Encoding

由於 URI 在協議中只挑選了部分ASCII 字符,數字以及符號,那麼當需要表示不在這個範圍之內的符號,字符,或者該字符在 URI 中被用來分隔符等特殊用途時,就需要對這個字符進行%編碼。百分號編碼也可以叫做URLEncode,其一般格式爲:

pct-encoded = "%" HEXDIG HEXDIG

將不能直接使用的字符先轉爲字節流表示(一般爲 utf-8編碼,需要具體看上下文和 URI scheme 協議制訂),然後每個字節轉換爲%加兩個十六進制字符來表示。例如:
“00101011” 該字節需要編碼爲 “%2B” ,在 ASCII 碼錶中表示爲 "+"號

Note: 百分號編碼不關心大小寫,但是爲了統一和一致,最好應該使用大寫字符

2.2.2 Reserved Characters

URI 保留字符集。
URI 自身定義時包含了 components以及 subcomponents,那麼這些不同的 components 就需要通過分隔符來進行標識。這些被用來進行表示分隔的字符就成爲保留字符集,這些字符集可能會被用作(或者將來會被用作)URI 不同部分的分隔符。
以下爲 reserved character 所涉及的字符集表示:

reserved    = gen-delims / sub-delims

gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"

sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
                  / "*" / "+" / "," / ";" / "="

gen-delims 字符集用來表示 URI component 之間的分隔符,考慮到 component 內會由不同的 subcomponents 組成,因此需要 sub-delims 字符集來定義 subcomponents之間的分隔符。

Note:這些字符在 URI 中一般具有特殊語義,因此不能被編碼。同時,如果在進行兩個 URI 相等性比較時,如果其中一個對協議中component 部分不能編碼的保留字符進行編碼,即使解碼後兩個 URI 字符相同,也會被認爲是兩個不同的 URI

2.2.3 Unreserved Characters

允許出現在URI 中,並且不會被拿來用作保留字符集的字符集合成爲 Unreserved Characters。所涉及到字符ABNF 表示爲:

unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"

ALPHA = a-z / A-Z

DIGIT = 0-9

這些字符爲非保留字符,在 URI 使用過程中是不需要進行編碼的。

Note: 如果在 URI 比較中包含這些字符,那麼該字符本身或者其編碼格式都應該認爲是相等的,即這些字符編碼不編碼不會影響相等性。另外,這些字符在使用時最好不要編碼,即使已經被編碼,那麼在使用時也應該先對這些字符進行解碼。

2.2.4 總結

一圖來表示在 URI 中所涉及到的保留和非保留字符,需要注意的是保留字符在不做分隔符或者具有特殊含義的時候是需要編碼的。
alphadigit.png

3.URI Component

URI 語法規則由一系列 component 組成,並且在設計時需要考慮到擴展性以及對各個資源定位類型的兼容,因此在其起始都會有一個 scheme 頭來特定標識這個 URI 所定義的資源類型標識符。另外,URI 由於是所有資源類型的超集(會細分爲 URL 和 URN),所以 URI 所涉及的定義都是需要被遵守的基本定義。
URI component 一般由以下 component 組成(使用 ABNF 描述):

URI         = scheme ":" [ //authority ] path [ "?" query ] [ "#" fragment ]

authority   = [ userinfo@ ] host [ :port ]

Note:

schme 和 path 爲 required

有了上述語法規則的定義,舉個例子來說明 URI 下兩種不同的標識符所定義的各個 component 部分
image.png

下文將詳細介紹各個組件部分,以及相應的語法規則。

3.1 URI component

3.1.1 Scheme

component scheme
允許字符集 a-z  A-Z  0-9 + . -
是否 case-sensitive
component 結束標識符 :

Note:

  • 表中字符集爲了呈現清晰,因此正則中通過非必要空格進行分隔,並且表或者關係
  • 結束標識符表示語法解析時該 component 解析結束符

scheme用來標識URI 所對應的具體協議。每個 URI 都必須以 scheme 開頭。URI 的語法規則如下(使用 ABNF 描述):

scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

如上文所說,URI 定義通用的語法規則,scheme 所標識的具體協議會定義通用規則外的具體語法規則。例如,以 geo 爲scheme 的協議 URI,表示特定地理位置標識,其語法規則如下:

geo:<lat>,<lon>[<alt>][u=<uncertainty>]

參考自 RFC 5870

URI scheme 的官方註冊信息目前由 IANA(Internet Assigned Numbers Authority) 組織進行添加和維護,目前約包含了335種不同協議 scheme,具體可參考https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml

3.1.2 Authority

component Authority
component 開始標識符 //
component 結束標識符 /  ?  #

authority component 設計的目的爲設定一個命名空間,並且標識這個命名空間被哪個機構所管理,例如 baidu.com, google.com 等等。authority 一般由三部分組成,包含了可選的 userinfo, port 以及必選的 host 部分。
關於爲什麼 Authority 部分會選擇 // 作爲起始符號的原因,Tim Berners-Lee 曾回答過:

  1. 需要選擇一個命名系統來進行資源的層級化命名,/ 作爲 unix 系統通用的分隔符可以在 URI 的設計中得到複用,因此使用 / 來作爲 relative URI 的分隔符
  2. 需要有符號將 host 部分(類似 www.example.com)同URI 的其他部分進行區分,這部分設計參考了當時 Apollo domain system (其使用//computername/file/path進行命名)的設計方式
  3. 現在來看,他認爲這個語法是比較冗餘的,更喜歡直接通過:來進行域名分隔,例如 http://www.example.com/foo/bar 轉寫爲 http:www.example.com/foo/bar, 這樣寫同樣可以識別到server 並且更爲簡化

由此可見,標準的設計也是需要再不斷地迭代和試驗中前進 :)

3.1.2.1 Userinfo

component Userinfo
允許字符集 pct-encode字符集 unreserved字符集 sub-delims字符集  :
是否 case-sensitive
component 結束標識符 @

userinfo 包含了用戶相關信息(一般爲名稱,舊式格式 user:password 由於涉及安全風險已被棄用),同時需要通過@符合和 host 進行分隔。Userinfo 部分的語法規則如下(使用 ABNF 描述):

userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )

3.1.2.2 Host

component Host
允許字符集 pct-encode字符集 unreserved字符集 sub-delims字符集
是否 case-sensitive
component 結束標識符 /  :

服務提供商通過 host來提供服務,同時基於 dns 域名解析, server 和 host 之間可以做到非一一對應。host 部分可以有三種表示方式,IPv6, IPv4或者 registered name。registered name host的語法規則如下(通過 ABNF 描述):

host        = IPv6address / IPv4address / reg-name

IPv6address = [ HEXDIG *( :: HEXDIG ) ]

IPv4address = DIGIT "." DIGIT "." DIGIT "." DIGIT

reg-name    = *( unreserved / pct-encoded / sub-delims )

 

3.1.2.2 Port

component Port
允許字符集 0-9
component 結束標識符 /

port 爲可選項,同時通過十進制進行表示。在URI語法中,port 需要跟在 : 後。port 的語法規則如下(使用 ABNF 描述):

port        = *DIGIT

每種 scheme 一般會定義一個默認端口。例如, http 定義80默認端口,https 定義443默認端口等。

3.1.3 Path

component Path
允許字符集 pct-encode字符集 unreserved字符集 sub-delims字符集  @  :
component 結束標識符 ? #  EOF

path標識了 host 下特定的資源路徑,包含了一系列通過 / 分隔的 segments。需要注意的是,如果URI已經包含了 authority 部分,那麼 path部分或者爲空,或者需要以 / 來開頭。另外,URI還允許 relative-path 的使用方式,這樣的方式第一段 path segment 不能包含 :(如果包含,會被 parser 認爲是 authority 部分)。以下是簡化的 path 語法規則(使用 ABNF 描述):

path          = path-abempty  /  path-relative

path-abempty  = *( "/" segment )

path-relative = segment-nocolon *( "/" segment )

segment       = *pchar

pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"

segment-nocolon = unreserved / pct-encoded / sub-delims / "@"

 

3.1.4 Query

component Query
允許字符集 pct-encode字符集 unreserved字符集 sub-delims字符集  @  :
component 開始標識符
component 結束標識符 #  EOF

query 部分提供了定位資源的輔助信息,query其內部語法並沒有明確定義,但是一般由name-value 鍵值對組成的字符串組成,中間通過分隔符 & 進行分隔。例如:name1=value1&name2=value2。query 的語法規則如下(使用 ABNF 描述):

query       = *( pchar / "/" / "?" )

pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"

3.1.5 Fragment

component Query
允許字符集 pct-encode字符集 unreserved字符集 sub-delims字符集  @  :  /  ?
component 開始標識符 #
component 結束標識符 EOF

fragment 爲段落標識符,一般用來標識一個 resource 的特定部分(一個資源子集或者一部分,或者通過這個資源來描述的一些其他資源)。 fragment 以 # 作爲起始標識符,其語法規則如下(通過 ABNF 描述):

fragment    = *( pchar / "/" / "?" )

pchar       = unreserved / pct-encoded / sub-delims / ":" / "@"

3.1.6 小結

各個component 允許的字符集部分是我們需要特別關注的,需要注意在五個 component 之間允許使用 gen-delims 字符集,在每個 component 內(即小組件間)允許使用 sub-delims 字符集。

3.2 解析 URI

如何通過程序來解析 URI, 並得到 URI 各個 component?
如上一節 ABNF 語法規則描述,URI 滿足上下文無關文法。因此,我們可以通過語法圖來呈現整體 URI 的解析規則,如下:
16025072234541dc24351cc2b4627a4d6fac6257dbdda.png

有了上圖,使用遞歸下降,解析的僞代碼就非常好寫了:

 
/** * 讀取下一個字符 **/ function next() { skip space; read next char and return; } /** * 預掃描,查看對應的 input 字符串是否包含有 special_char, * 以及其位置 **/ function contains(input, special_char) { start = input.start, end = input.end; while (start < end) then if special_char equals start then return; end return start } /** * 對 uri 的解析函數 * 具體的解析 component 方法爲 parse_*, 需要匹配的字符集以及語法規則可參照上文中各個 ABNF **/ function parse(string uri) { parse_scheme; skip next ';' ; if next() == "//" then if contains(substring_uri(// until path), '@') then parse_userinfo; end parse_host; if next() == ':' then parse_port; end end parse_path; if next() == '?' then parse_query; end if next() == '#' then parse_fragment; end }

5.再論Encode 和 Decode

什麼時候該 encode 或者 decode?
先說 URI 的設計目的,URI 被設計出並可在萬維網上進行廣泛傳播,因此對各個子系統,瀏覽器等媒介的兼容性是最重要的,因此被設計使用被廣泛使用的 ASCII 碼進行承載。
因此,在生成 URI 過程中,應該先完成各個 componet 部分的編碼,然後在聯合 gen-delimiter 拼接成 URI。由於各個 scheme 的具體協議不同,因此只有在生成 URI 的過程中,纔可以知道具體哪些 delimiter 會需要被編碼,或者會被使用作爲真正的 delimiter。一旦 URI 被生成,該 URI 在傳播時就應該保持其 百分號 encode 的格式。
當百分號編碼的 URI 在解碼時,應該先通過 gen-delimiter 以及 sub-delimiter 將各個 component 進行分離,然後再對各個 component 進行分別解碼。這樣可以保證按照生成的 URI 被完整解碼。
另外,需要注意的是,2.2.3中提到的 unreserved 字符集可以在任意時刻被編碼和解碼,但是推薦在生成 URI 時不對這些字符集進行編碼,同時在解碼時應該優先對這些字符集的百分號編碼格式進行解碼。

Note: 不應該對同一個 URI 重複次編碼或者解碼,這樣會導致 URI所代表的語義失效。例如,對已經進行百分號編碼的 URI 再進行編碼時,又會再次對其中的百分號進行二次編碼,從而導致 URI 在進行解碼時含義錯誤。

5.1 實現 encode 和 decode

按照上文的說法,encode 需要先根據對應的 component 部分來組成不需要進行 escape(即不需要編碼) 字符的規則,然後再進行逐一的判斷和編碼,之後再將編碼過後的 component 拼接稱爲 URI(當然,如果所有的 delimiter 都不需要進行編碼,那可以直接對整個 URI 進行編碼,不需要 escape 的字符集直接包含這些 delimiter 字符)。 decode 則需要先將各個 component 按照 delimiter 進行拆分,然後分別對各個 component 在需要解碼的字符規則下進行解碼。

Note: 在標識 ASCII 以外的字符集時,一般是用 Unicode 字符集,編碼方式爲 UTF-8。
因此,在編碼和解碼過程中,如果編程語言層面使用 UTF-16進行字符編碼(類似於 Java 和 JavaScript),那麼需要將其轉爲 UTF-8編碼,同時需要針對 UTF-16帶來的 surrogate pair 進行額外處理。
關於surrogate pair 描述,可以參考
https://stackoverflow.com/questions/5903008/what-is-a-surrogate-pair-in-java#:~:text=The%20term%20%22surrogate%20pair%22%20refers,values%20between%200x0%20and%200x10FFFF.&text=This%20is%20done%20using%20pairs%20of%20code%20units%20known%20as%20surrogates.

5.1.1 encode

encode 的實現中需要注意的就是對需要編碼的字節進行%編碼,僞代碼如下:

 
/** * 對某一段 string s 進行 URI encode 編碼 * 傳入 s 以及不需要編碼的字符集 dontNeedEncodingSet, 返回 URI encode後的string * * dontNeedEncodingSet 字符集需要根據3.1中的 component描述來定,例如 Path 中的不需要編碼字符集 * 一般爲 unreserved字符集 sub-delims字符集 @ :(sub-delims 字符集以及@ : 如果其本身需要出現在 * component 中而不是用來做分隔語義,那麼同樣需要進行 encode),另外不同的語言實現在不需要編碼字符集 * 上可能會有不同的選擇 **/ function encode(s, dontNeedEncodingSet) { // 聲明 R 爲結果字符串 def R, index = 0, strLen = s.length(); while index < strLen then def c 爲 s 在 index 下的字符表示; if c 包含在 dontNeedEncodingSet 裏 then R += c; else def 臨時結果 out; /** * 這裏需要考慮如果是 utf-16字符編碼,那麼需要判斷 surrogate pair **/ if c 在 surrogate pair中的第一個字符所表示的範圍內 then def c2 爲 ++index 位置字符; 將 c c2兩個字符組成 utf-16並進行 utf-8編碼; 將上述結果賦值給 out; else 如果 c 爲 utf-16編碼,需要轉爲 utf-8編碼; out = c; end // 核心百分號 encode 取 out 中每一個字節 out_byte; R += '%' + ((out_byte >> 4) & 0xF)轉爲16進制大寫表示 + ((out_byte) & 0xF)轉爲16進制大寫表示; end ++index; end return R; }

 

5.1.2 decode

decode 的實現中需要注意在遇到%號時讀取後續字符進行解碼,同時如果語言實現使用 utf-16編碼那麼需要對 surrogate pair 進行還原(這部分語言本身一般都提供方法來對 utf-8進行轉換),僞代碼如下:

 
/** * 對 s 進行解碼,返回解碼後的 string **/ function decode(s) { // 聲明 R 爲結果 string def R, index = 0, lenStr = s.length(); while index < lenStr then def c 爲 s 在 index 下的字符表示; if c == '%' then def 中間臨時結果 out; while c == '%' && index + 2 < lenStr then 讀取index+1, index+2 字符 c1, c2; // 核心 decode out += (字符轉爲 hex 表示(c1)) << 4 | (字符轉爲 hex 表示(c2)); index += 3; end // 異常情況報錯 if c == '%' && index < lenStr then 拋出錯誤; // 注意:如果語言實現需要 utf-16編碼,那麼需要先行將 out 轉爲 utf-16編碼 R += out; else R += c; ++index; end end return R; }

5.1.3 小結

相信各位已經對 URI 有了一個相對全面的瞭解,在實際工作的使用中,還需要根據語言所提供的對應 encode,decode 方法文檔來進一步瞭解其編解碼所定義的 component 部分特殊保留字符,這樣會對所使用語言提供的 encode/decode 有更深入的瞭解 :)
**
Enjoy your coding trip~

作者:王陽(好未來Java開發專家)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章