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

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

1.URI

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

1.1 URI 的前世此生

爲何會有 URI?
隨着萬維網的發展,須要有各類不一樣類型的資源被在網絡上查找以及傳輸。所以,也就須要一種惟一的可在萬維網上傳播的標識,這樣的統一資源標識就稱爲 URI。固然,資源在這裏是一種籠統概念,或者抽象概念,能夠泛指能夠被標識的實體,就像一個網頁,一本e-book, 一份 pdf 等等,只要有須要被呈現或者傳輸,均可以稱爲一種資源。git

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

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

  • 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 = *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開發專家)

相關文章
相關標籤/搜索