全面深度解析iOS端URL編碼和解碼過程

1、URL含義

一、URL定義

URL 是Uniform Resource Locator 的縮寫,統一資源定位符,對能夠從互聯網上獲得的資源的位置和訪問方法的一種簡潔的表示,是互聯網上標準資源的地址。互聯網上的每一個文件都有一個惟一的URL,它包含的信息指出文件的位置以及瀏覽器應該怎麼處理它。基本URL包含模式(或稱協議)、服務器名稱(或IP地址)、路徑和文件名、參數,如「協議://受權/路徑查詢?參數」。html

URL 與 URI 不少人會混淆這兩個名詞。 URL:(Uniform/Universal Resource Locator 的縮寫,統一資源定位符)。 URI:(Uniform Resource Identifier 的縮寫,統一資源標識符)。 對於URI, 具體的結構以下:json

foo://example.com:8042/over/there?name=ferret#nose

   \_/ \______________/ \________/\_________/ \__/

    |         |              |         |        |

  scheme     authority      path      query   fragment
複製代碼

URI 屬於 URL 更低層次的抽象,一種字符串文本標準。URL 是 URI 的一個子集。 URI 表示請求服務器的路徑,定義這麼一個資源。而 URL 同時說明要如何訪問這個資源(http://)。瀏覽器

URL 百度百科安全

二、URL字符編碼表

一、URL 編碼 - 從 %00 到 %ffbash

二、HTML特殊字符編碼對照表服務器

2、URL 編碼

一、爲何要編碼轉義

推薦閱讀:字符編碼:ASCII、Unicode 和 UTF-8 的區別網絡

世界上存在着多種編碼方式,同一個二進制數字能夠被解釋成不一樣的符號,不一樣的編碼方式,解碼出來就是亂碼,形成數據傳輸和閱讀的極大障礙。互聯網的來臨,必需要統一字符編碼,Unicode(統一碼、萬國碼、單一碼)做爲計算機科學領域裏的一項業界標準應運而生。Unicode 是互聯網統一的符號集,只規定了符號惟一的二進制代碼值,卻沒有規定這個二進制代碼應該如何存儲。UTF-8是一種針對Unicode的可變長度字符編碼,UTF-8用1到4個字節編碼Unicode字符,在互聯網上使用最廣的一種 Unicode 的實現方式。其餘實現方式還包括 UTF-16(字符用兩個字節或四個字節表示)和 UTF-32(字符用四個字節表示),不過在互聯網上基本不用。 注意:UTF-8 是 Unicode 的實現方式之一。 如 中 字:工具

Unicode碼值: \u4e2dpost

URL編碼(UTF-8): %e4%b8%ad測試

二、URL編碼規則

Url編碼一般也被稱爲百分號編碼,編碼方式很是簡單,使用%百分號加上兩位的字符——0123456789ABCDEF——表明一個字節的十六進制形式。Url編碼默認使用的字符集是US-ASCII。例如a在US-ASCII碼中對應的字節是0x61,那麼Url編碼以後獲得的就是%61,咱們在地址欄上輸入 http://g.cn/search?q=%61%62%63 ,實際上就等同於在google上搜索abc了。又如@符號在ASCII字符集中對應的字節爲0x40,通過Url編碼以後獲得的是%40。

對於非ASCII字符,須要使用ASCII字符集的超集進行編碼獲得相應的字節,而後對每一個字節執行百分號編碼。對於Unicode字符,RFC文檔建議使用utf-8對其進行編碼獲得相應的字節,而後對每一個字節執行百分號編碼。如"中文"使用UTF-8字符集獲得的字節爲0xE4 0xB8 0xAD 0xE6 0x96 0x87,通過Url編碼以後獲得"%E4%B8%AD%E6%96%87"。

三、URL不須要編碼的字符

HTTP URL 使用的RFC3986編碼規範,RFC3986文檔規定,URL中只容許包含如下四種: 一、英文字母(a-z A-Z)

二、數字(0-9)

三、-_.~ 4個特殊字符

四、全部保留字符,RFC3986中指定了如下字符爲保留字符(英文字符): ! * ' ( ) ; : @ & = + $ , / ? # [ ]

五、編碼標記符號 %

URL 編碼使用 "%" 其後跟隨兩位的十六進制數來替換非 ASCII 的字符,中文是三個編碼組合。十六進制格式用於在瀏覽器和插件中顯示非標準的字母和字符。

四、URL須要編碼的字符

Url編碼的原則就是使用安全的字符(沒有特殊用途或者特殊意義的可打印字符)去表示那些不安全的字符。

4.一、非URL定義的字符

不能在 URL 中包含任何非 ASCII 字符,如中文字符、希臘文字符,拉丁文字符等。若是客戶端瀏覽器和服務端瀏覽器支持的字符集不一樣的狀況下,中文可能會形成亂碼問題。

4.二、會引發歧義的保留字符

URL 拼接參數或路徑設置時,拼接的普通字符串中含有保留字符,會引發歧義的狀況。URL 參數字符串中使用 key=value 這樣的鍵值對形式來傳參,鍵值對之間以 & 符號分隔,如寶潔公司的簡稱爲P&G,假設須要當作參數去傳遞,name=P&G&t=1450591802326,由於參數中多了一個&勢必會形成接收 URL 的服務器解析錯誤,所以必須將引發歧義的 & 符號進行轉義編碼。

部分保留字符及其URL編碼

字符 用法描述 編碼
+ 表示空格(在URL中不能使用空格) %2B
空格 URL中的空格能夠用+號或者編碼 %20
/ 分隔目錄和子目錄 %2F
? 分隔實際的URL和參數 %3F
# 表示書籤或錨點 %23
& URL中指定的參數間的分隔符 %26
= URL中指定的參數的值 %3D
% 百分號自己用做對不安全字符進行編碼時使用的特殊字符,所以自己須要編碼 %25

若是須要在URL中用到特殊字符或中文字符,須要將這些特殊字符換成相應的十六進制的值。

3、iOS端URL具體編碼處理

一、URL編碼和解碼是成對

URL編碼和解碼是一個可逆的過程,編碼和解碼的邏輯是翻轉對應的。 成對有兩層含義: 一、兩個方法的邏輯對應。一個固定的編碼方式,也對一個固定的逆向解碼方式,反之亦然。

二、編碼和解碼的次數也要一一對應。

這四種種字符後,URL編碼後的值仍是它自己:

一、英文字母(a-z A-Z)

二、數字(0-9)

三、特殊字符( -_.)

四、部分保留字符(英文字符): ! * ' ( ) ; : @ & = + $ , / ?

說明~ # [] 這四個字符是否被轉碼成百分號編碼,因系統不一樣會有不一樣。

URL字符編碼使用%百分號加上兩位的字符——0123456789ABCDEF——表明一個字節的十六進制形式。因編碼後的值含有 % 保留字符。再次編譯% 會編譯成 %25

例如: &

第一次URL編碼後:%26

第二次URL編碼後:%2526

第三次URL編碼後:%252526

正常解碼邏輯:

第一次URL解碼後:%2526

第二次URL解碼後:%26

第三次URL解碼後:&

所以,URL編碼和解碼必須是成對出現的。

初始字符爲 & 連續編碼三次,連續解碼兩次,則獲得 %26 。 初始字符爲 &%26 編碼一次:%26%2526 連續解碼兩次則獲得 && 。

二、URL是怎麼拆解的

咱們看一個常見的接口請求示例:

通常會根據 ://:/?&= 等拆分出請求的協議、服務器名稱(或IP地址)、端口號、路徑和文件名、參數名、參數值等。

三、在組裝URL的什麼階段進行URL編碼

咱們看一個常見的接口請求示例:

字符串 說明
:// 協議符號
/ 分隔目錄和子目錄
測試 表明須要編譯處理了的路徑
分隔實際的URL和參數
& URL中指定的參數間的分隔符
= URL中指定的參數的值
搜&索 搜索詞含有中文,含有保留字段,須要編譯
&times 是key的一部分,不該該被編譯,若多一次編譯,會編譯爲 x

綠色字體是保留字符,都有特殊的含義,是不該該是被編碼的。 紅色字體必需要要編譯的部分。 黃色背景的字符串,不該該被編譯。 若以上操做不正確,會影響整個URL的解析。

常見的拼接過程

一、先拼接實際的請求地址 https://www.baidu.com/s/測@試?

二、再拼接參數字符串 wd=搜&索&timestamp=32424242423

三、將一、2合併憑藉成一個網址字符串。

四、將網址字符串轉爲NSURL 實例。

分析

一、因 測@試 含有中文和保留字符@,須要在步驟1以前,先將 測@試 編碼爲 %e6%b5%8b%40%e8%af%95 ,再拼接到https://www.baidu.com/s/%e6%b5%8b%40%e8%af%95?

二、因 搜&索 含有中文和保留字符& ,& 會影響參數解析。須要先搜&索 編碼爲 %e6%90%9c%26%e7%b4%a2 ,再拼接到wd=%e6%90%9c%26%e7%b4%a2&timestamp=32424242423

三、因請求地址和參數列表已經編碼過,拼接後的完整請求不該該再次編譯。若再次編譯 則會因含有 &times 編譯爲 x 。

小結

上面咱們分別編碼特殊字符後,最後拼接到一塊兒。也有部分寫法是拼接後再統一編碼處理的。但因請求路徑、請求參數中均可能含有保留字符&、=或中文等特殊字符,形成請求地址解析錯誤。建議在路徑和參數拼接前對路徑、參數名、參數值等先行統一編碼處理,再行拼接。拼接好後不要再行編碼,轉爲NSURL實例,發送請求。

四、可用的編碼和解碼API

URL編碼是互聯網的通用規範,各系統或平臺都會提供封裝好的API方法供開發者調用。 iOS端在生成NSURL實例

NSURL *url = [NSURL URLWithString:urlString];
複製代碼

特別要注意的是 urlString 中含有超出中文字符等非定URL限定字符時,建立的NSURL對象會失敗,url返回爲nil。

4.一、iOS 7以前的編碼解碼處理

4.1.一、方式一:stringByAddingPercentEscapesUsingEncoding

字符串URL編碼實現

NSString *urlStr = @"你好0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/?#[] %25";
NSString *encodingString = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"url編碼 = %@",encodingString);
複製代碼

注意:最後一個是空格符號,下面用黃色背景標記。 打印結果是

字符串URL解碼實現

請求返回的數據格式是%E4%BD%A0%E5%A5%BD,須要進行UTF-8解碼,對應方法是:

NSString *encodingString = @"%E4%BD%A0%E5%A5%BD0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/?%23%5B%5D%25%20";
NSString *decodedStr = [encodingString stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"url解碼 = %@",decodedStr);
複製代碼

打印結果是

經過上面的編碼解碼過程咱們能夠知道用

[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
複製代碼

綠色字體的 0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/? 這些字符不會被編譯成%百分號編碼,只有特別的 #[]%(空格)和中文字符會被編譯。

小結

可經過第二章的4.2小節瞭解到,-_.~&!*'();:@&=+$,/?#[]%(最後一個是空格)這些特殊字符可能存在於網址請求的路徑或參數表中,若不進行轉義編碼,容易在URL解析時,引發歧義,形成解析錯誤,找不到指定的資源,形成網絡請求失敗或錯誤。此方式只能用於處理URL編碼規定字符集以外的字符且不含有以上特殊字符的編碼處理。 該方式不適合處理URL的總體編譯處理,能夠局部編譯不含特殊字符的URL部分,侷限性太強,不建議在URL編碼和解碼時使用此方式,另外在iOS9以後蘋果也廢棄該方式。 另外,還有encodeBase6四、decodeBase64方式,也是不能編譯-_.~&!*'();:@&=+$,/?#[]%特殊字符,處理結果和方式一結果基本一致,就再也不展開說明了。

4.1.二、方式二:CFURLCreateStringByAddingPercentEscapes

字符串URL編碼實現

NSString *urlStr = @"你好0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/?#[]% ";
//方式一編碼對比
NSString *encodingStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"url編碼1-1 = %@",encodingStr);
//方式二編碼定義空字符集
NSString *encodeStr = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)urlStr, NULL, (CFStringRef)@"", kCFStringEncodingUTF8));
NSLog(@"url編碼2-1 = %@",encodeStr);
//方式二編碼定義 ABC-_~.!*'();:@&=+ $,/?%#[] 字符集 NSString *encodeStr2 = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)urlStr, NULL, (CFStringRef)@"ABC-_~.!*'();:@&=+ $,/?%#[]", kCFStringEncodingUTF8));
NSLog(@"url編碼2-2 = %@",encodeStr2);
複製代碼

注意:最後一個是空格符號,編碼2-2強制編譯了自定義字符集 ABC-_~.!*'();:@&=+ $,/?%#[],對結果以下:

字符串URL解碼實現

NSString *encodedString = @"%E4%BD%A0%E5%A5%BD0123456789abcxyz%41%42%43XYZ%2D%5F%2E%7E&%21%2A%27%28%29%3B%3A@&%3D%2B%24%2C%2F%3F%23%5B%5D%25%20";

NSString *decodedStr = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,(CFStringRef)encodedString,CFSTR(""),kCFStringEncodingUTF8));
NSLog(@"url解碼2-1 = %@",decodedStr);
    
NSString *decodedStr2 = (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapesUsingEncoding(kCFAllocatorDefault,(CFStringRef)encodedString,CFSTR(";:@&"),kCFStringEncodingUTF8));
NSLog(@"url解碼2-2 = %@",decodedStr);
複製代碼

打印結果是

小結

經過以上數據能夠看到,方式二在方式一編碼的基礎上,可對自定義的特殊字符集也進行編碼處理,解決了方式一存在的問題。 解碼2-2強制不解碼自定義字符集 ABC-_~.!*'();:@&=+ $,/?%#[],但依然有兩個字符@ &比較特殊,排除在解碼以外,具體暫不明緣由。

iOS7以前,建議採用方式二,要肯定自定義字符集能全面覆蓋有可能存在歧義的字符。

該方式能夠編譯特殊字符,所以,不適合對URL的總體編譯,只能先將各個URL部分編譯後,再組裝在一塊兒。

4.二、iOS 7以後的編碼解碼處理

4.2.一、全新的方式:stringByAddingPercentEncodingWithAllowedCharacters

iOS9以後蘋果建議 使用新方法 stringByAddingPercentEncodingWithAllowedCharacters,其實該方法iOS7以後均可以調用。

蘋果對該方法的註解:將AllowedCharacters集中不包含的全部字符替換爲百分比編碼字符,返回從接收器生成的新字符串。utf-8編碼用於肯定正確的編碼字符百分比。不能對整個URL字符串進行百分比編碼。此方法用於對URL組件或子組件字符串進行百分比編碼,而不是對整個URL字符串進行百分比編碼。7位ascii範圍以外的容許字符中的任何字符都將被忽略。

字符串URL編碼實現

NSString *urlStr = @"你好0123456789abcxyzABCXYZ-_.~&!*'();:@&=+$,/?#[]% ";
//方式一編碼對比
NSString *encodingString = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(@"url編碼1-1 = %@",encodingString);

//方式二自定義字符集 ABC-_~.!*'();:@&=+ $,/?%#[] 編碼對比 NSString *encodeStr2 = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)urlStr, NULL, (CFStringRef)@"ABC-_~.!*'();:=+ $,/?%#[]", kCFStringEncodingUTF8));
    NSLog(@"url編碼2-2 = %@",encodeStr2);

//系統提供的枚舉字符集,這些字符不須要  編譯
NSString *encodeStr3 = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
NSLog(@"url編碼3-1 = %@",encodeStr3);
    
//自定義字符不須要編譯的字符集,爲空字符集,將全部字符用百分號編碼
NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@""];
NSString *encodeStr4 = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:characterSet];
    NSLog(@"url編碼3-2 = %@",encodeStr4);
複製代碼

打印結果是

網上常見的字符集枚舉說明(供參考):

URLFragmentAllowedCharacterSet  "#%<>[\]^`{|} URLHostAllowedCharacterSet      "#%/<>?@\^`{|}
URLPasswordAllowedCharacterSet  "#%/:<>?@[\]^`{|} URLPathAllowedCharacterSet      "#%;<>?[\]^`{|}
URLQueryAllowedCharacterSet     "#%<>[\]^`{|} URLUserAllowedCharacterSet      "#%/:<>?@[\]^`
複製代碼

字符串URL解碼實現

//上段代碼的結果爲encodeStr3入參
NSString *decodedStr3 = [encodeStr3 stringByRemovingPercentEncoding];
NSLog(@"url編碼3-1 = %@",decodedStr3);
//上段代碼的結果爲encodeStr4入參
NSString *decodedStr4 = [encodeStr4 stringByRemovingPercentEncoding];
    NSLog(@"url編碼3-2 = %@",decodedStr4);
複製代碼

打印結果是

解碼接口統一,不須要入參等。

小結 咱們知道url編碼1-1和編碼3-1,系統提供給咱們的URL特定的編碼方式,並不能知足咱們正確編碼解碼下面這樣的常見請求示例:

所以須要根據業務自定義字符集,來定製化URL編碼和解碼。 編碼3-1用的是系統 URLFragmentAllowedCharacterSet 字符集,系統並未提供打印字符集中具體字符的任何入口,咱們並不能保障全部可能有歧義的特殊字符都轉義編碼過。建議咱們採用URL編碼3-2的寫法,自定義特殊字符甚至定義空字符集,來編譯局部全部的字符,最後再拼接成一個總體URL。

4.三、最優方案和封裝處理

iOS7如下的版本可用4.1.2的方式微調便可。目前絕大部分APP都是適配在iOS7及以上的,咱們以iOS7以後的方案爲主。 建立一個 NSString+UTF_8 分類,定義兩個方法實現以下:

/**
 對字符串的每一個字符進行UTF-8編碼
 
 @return 百分號編碼後的字符串
 */
- (NSString *)URLUTF8EncodingString
{
    if (self.length == 0) {
        return self;
    }
    NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:@""];
    NSString *encodeStr = [self stringByAddingPercentEncodingWithAllowedCharacters:characterSet];
    return encodeStr;
}

/**
 對字符串的每一個字符進行完全的 UTF-8 解碼
 連續編碼2次,須要連續解碼2次,第三次繼續解碼時,則返回爲空
 @return 百分號編碼解碼後的字符串
 */
- (NSString *)URLUTF8DecodingString
{
    if (self.length == 0) {
        return self;
    }
    if ([self stringByRemovingPercentEncoding] == nil
        || [self isEqualToString:[self stringByRemovingPercentEncoding]]) {
        return self;
    }
    NSString *decodedStr = [self stringByRemovingPercentEncoding];
    while ([decodedStr stringByRemovingPercentEncoding] != nil) {
        decodedStr = [decodedStr stringByRemovingPercentEncoding];
    }
    return decodedStr;
}

複製代碼

注意

URLUTF8EncodingString UTF-8編碼能夠無限制調用屢次,stringByRemovingPercentEncoding方法的特殊性是字符串不是UTF-8編碼格式,調用時返回爲nil,所以解碼時只需調用一次URLUTF8DecodingString便可將全部字符完全UTF-8解碼。

五、其餘處理方法

能夠將須要編碼的參數表總體封裝爲NSData類型,使用post請求發送也是能夠的。

4、總結

一、在URL組裝拼接前對各個部分的可能會引發歧義的字符串進行全量UTF-8編碼。

二、在須要解碼的地方,須要先分拆字符串,再分段解碼使用。

三、在須要將已組裝的數據,進行重組時,須要先拆解,分別解碼後再編碼,最後再重組。

四、服務端會對請求進行UTF-8解碼一次,請確保請求中的字符只進行一次UTF-8編碼。

參考資料

一、HTML URL 編碼參考手冊

二、HTML URL 編碼

三、在線url網址編碼、解碼

四、站長工具 之 URL編碼解碼

五、URL原理、URL編碼、URL特殊字符

原創不易,轉載請註明做者:擇勢勤

相關文章
相關標籤/搜索