url轉(zhuǎn)碼遇到的坑

一、問題背景

? ? 相信很多ios開發(fā)者在項(xiàng)目中都需要用到uiwebview,那就離不開url了,一般符合網(wǎng)絡(luò)標(biāo)準(zhǔn)的url是沒啥問題的,那么當(dāng)遇到一些特殊的url時,你就會踩坑了。。。舉幾個栗子:

順帶說一下一個完整的url格式:

協(xié)議://域名:端口號/路徑?參數(shù)1&參數(shù)2#路由錨點(diǎn)(瀏覽器使用)

url1=http://www.itdecent.cn/這是中文/notebooks&123/3521954/notes(路徑含中文和&)

url2=http://www.itdecent.cn/notebooks/352195?location=%&23!&userid=8957(參數(shù)含%&!特殊字符)

url3=http://www.itdecent.cn/notebooks/352195?username=逗比&userid=89757(參數(shù)含中文)

url4=http://www.itdecent.cn/writer?name=ha#ha#/notebooks/35214/notes(參數(shù)帶#,路徑后面包含#錨點(diǎn))

當(dāng)你遇上以上幾種url,你如果上來就粗暴的搞個[NSURL URLWithString:url],然后用webview開始loadrequest,那你就會驚喜的發(fā)現(xiàn):根本打不開,404錯誤赫然出現(xiàn)在眼前。此時,你就需要對url先進(jìn)行轉(zhuǎn)碼了,下面我們就url轉(zhuǎn)碼的問題好好說說。

二、url轉(zhuǎn)碼的具體原因

????在iOS程序中,訪問一些HTTP/HTTPS的資源服務(wù)時,如果url中存在中文或者特殊字符時,會導(dǎo)致無法正常的訪問到資源或服務(wù),想要解決這個問題,需要對url進(jìn)行編碼。

網(wǎng)絡(luò)標(biāo)準(zhǔn)RFC 1738規(guī)定url中只能包含英文字母和阿拉伯?dāng)?shù)字,以及一些特殊字符:

"...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'()," [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL."

只有字母和數(shù)字[0-9a-zA-Z]、和特殊符號”$-_.+!*’(),”[不包括雙引號]、及某些保留字,才可以不經(jīng)過編碼直接用于URL?!?/p>

此時如果url中包含如漢字或者其他特殊字符則需要對它進(jìn)行編碼,編碼的意義在于,假如url的參數(shù)中的中文或特殊字符在發(fā)送到服務(wù)端時,服務(wù)端無法解析它的真正意義,會導(dǎo)致服務(wù)端不能理解客戶端的請求,如前面列舉的幾個特殊的url。

三、轉(zhuǎn)碼coding

? ??1. 使用CoreFoundation對url參數(shù)進(jìn)行encode

使用API:

CFStringRef CFURLCreateStringByAddingPercentEscapes(CFAllocatorRef allocator, CFStringRef originalString, CFStringRef charactersToLeaveUnescaped, CFStringRef legalURLCharactersToBeEscaped, CFStringEncoding encoding)

- (void)encodeUrl { ? ?

NSString *para1 = [self encodeParameter:@"%&23"];? ?// p1=%&23 ? ? ?

NSString *para2 = [self encodeParameter:@"我是參數(shù)8957"];? ?// p2=我是參數(shù)8957

NSString *encodeUrl = [NSString stringWithFormat:@"http://www.itdecent.cn?p1=%@&p2=%@", para1, para2];? ?

NSLog(@"%@", encodeUrl);

}

- (NSString *)encodeParameter:(NSString *)originalPara {? ?

CFStringRef encodeParaCf = CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)originalPara, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), kCFStringEncodingUTF8);? ?

NSString *encodePara = (__bridge NSString *)(encodeParaCf);? ? CFRelease(encodeParaCf);? ?

return encodePara;

}

打印結(jié)果如下:

H5ViewController.m:59 http://www.itdecent.cn?p1=%25%2623&p2=%E6%88%91%E6%98%AF%E5%8F%82%E6%95%B08957

可以看到轉(zhuǎn)碼對象中,除了中文正常轉(zhuǎn)碼外,特殊字符只要包含在!*'();:@&=+$,/?%#[]這些字符范圍內(nèi)的都進(jìn)行了轉(zhuǎn)碼。

此方法適用于,url前綴不包含中文以及其它非法字符的情況,只需要對參數(shù)進(jìn)行編碼即可。

2.使用Foundation框架對完整url進(jìn)行encode

這里有兩個方法都用于url轉(zhuǎn)碼:

方法一.- (nullable NSString *)stringByAddingPercentEscapesUsingEncoding:(NSStringEncoding)encode;(已廢棄)

調(diào)用代碼示例如下:

- (void)encodeUrl {? ?

NSString *url = [@"http://www.itdecent.cn/這是中文/notebooks&123/3521954/notes" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];? ? NSLog(@"%@",url);

}

打印結(jié)果如下:

2018-06-12 14:03:57.864836+0800 haha[10930:15910902] http://www.itdecent.cn/%E8%BF%99%E6%98%AF%E4%B8%AD%E6%96%87/notebooks&123/3521954/notes

可以看到原先url帶的中文都被轉(zhuǎn)碼了,這里需要說明的是,該方法已經(jīng)被蘋果廢棄,因?yàn)樵摲街С值淖址容^少,只對`#%^{}[]|\"<> 加空格共14個字符編碼,不包括&?等符號

方法二.- (nullable NSString *)stringByAddingPercentEncodingWithAllowedCharacters:(NSCharacterSet *)allowedCharacters;(推薦)

說到方法二,雖說拓展性更強(qiáng)了,允許你自定義字符集,也是有坑的呀,下面看一下蘋果的官方api:

// Returns a new string made from the receiver by replacing all characters not in the allowedCharacters set with percent encoded characters. UTF-8 encoding is used to determine the correct percent encoded characters. Entire URL strings cannot be percent-encoded. This method is intended to percent-encode an URL component or subcomponent string, NOT the entire URL string. Any characters in allowedCharacters outside of the 7-bit ASCII range are ignored. ?

最后一句Any characters in allowedCharacters outside of the 7-bit ASCII range are ignored.,意思就是說,任何非7-bit ASCII字符擱到allowedCharacters里面也將被忽略,也就是allowedCharacters里面的字符跟7-bit ASCII字符不會被編碼。

換句話說,上面方法在處理的時候會編碼url的中的非7-bit ASCII字符,如這些【`#%^{}"[]|\<>】,如果需要忽略之,需要通過(NSCharacterSet *)allowedCharacters這個參數(shù)指定。

到此坑就來了,有的同學(xué)可能通過各種文章了解這個方法就是上文說的意思,其實(shí)不是的,測試代碼如下:

- (void)encodeUrl {? ?

NSString *url = @"http://www.itdecent.cn/notebooks/352195?location=%&23!&userid=8957";? ?

NSString *encodeUrl = [url stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "]];? ? NSLog(@"%@",encodeUrl);?? ?? ?

NSString *testStr = @"2#&!@測試";? ?

NSString *testEncodeUrl = [testStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "]];? ? NSLog(@"%@",testEncodeUrl);?? ?

}

按照上面的解釋,字符集合[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "]包含的字符都不會編碼,也就是代碼中url中的正常的7-bit ASCII字符(字母,數(shù)字等)和字符集中的%不會被編碼,但是結(jié)果卻嚇了我一跳:

2018-06-12 14:32:46.432905+0800 haha[11266:15930653] %68%74%74%70%73%3A%2F%2F%77%77%77%2E%6A%69%61%6E%73%68%75%2E%63%6F%6D%2F%6E%6F%74%65%62%6F%6F%6B%73%2F%33%35%32%31%39%35%3F%6C%6F%63%61%74%69%6F%6E%3D%%26%32%33%21%26%75%73%65%72%69%64%3D%38%39%35%37

2018-06-12 14:32:46.433076+0800 haha[11266:15930653] %32#%26%21%40%E6%B5%8B%E8%AF%95

可以看到第一個url被全部編碼了,僅僅除了一個%沒有被編碼(第一條log可能有點(diǎn)長,看不出來,但從第二條log可以看出只有#沒有被編碼),按理說正常的字符也不會被編碼啊,怎么常見的字母和數(shù)字都被編碼了呢?

由此可以看出不是我們之前所理解的“allowedCharacters里面的字符跟7-bit ASCII字符不會被編碼”,而是只有allowedCharacters里的字符才不會被編碼?。。?/b>

那么怎么才能正確忽略部分字符對url正常轉(zhuǎn)碼呢?

invertedSet的作用就來了,代碼如下:

- (void)encodeUrl {? ?

NSString *url = @"http://www.itdecent.cn/notebooks/352195?location=%&23!&userid=8957";? ?

NSString *encodeUrl = [url stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet];? ? NSLog(@"%@",encodeUrl);?? ?? ?

NSString *testStr = @"2#&!@測試";? ?

NSString *testEncodeUrl = [testStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet];? ? NSLog(@"%@",testEncodeUrl);?? ?

}

打印結(jié)果如下:

2018-06-12 15:13:05.725613+0800 haha[11629:15956002] http://www.itdecent.cn/notebooks/352195?location=%25&23!&userid=89572018-06-12 15:13:05.725997+0800 haha[11629:15956002] 2%23&!@%E6%B5%8B%E8%AF%95

可以看到,通過集合反轉(zhuǎn)之后得到的結(jié)果才是我們想要的,但是此處的意思是反的,就是對集合進(jìn)行invertedSet,表示集合內(nèi)的字符和非7-bit ASCII字符是需要轉(zhuǎn)碼的,所以我們以后使用這個方法進(jìn)行轉(zhuǎn)碼的時候要從反面進(jìn)行轉(zhuǎn)碼,把想要進(jìn)行轉(zhuǎn)碼的特殊字符寫在集合里就好了,注意這里說的是想要轉(zhuǎn)碼的特殊字符(!*'();:@&=+$,/?%#[]),像中文會被認(rèn)為是非7-bit ASCII字符會自動轉(zhuǎn)碼的,所以中文你就不用操心了。

舉個例子,我想對url中的%不轉(zhuǎn)碼,其他的特殊字符都轉(zhuǎn)碼,那你在集合中寫上想轉(zhuǎn)碼的字符就好了,不要寫%。補(bǔ)充說一下,用這個方法轉(zhuǎn)碼的時候不用擔(dān)心ios系統(tǒng)適配的問題,這個方法支持ios7之后的機(jī)器,目前蘋果機(jī)型基本上沒有低于ios7之后的機(jī)器了。

四.關(guān)于url含有#的問題

項(xiàng)目中H5給的url中總是含有符號#,每次有#的時候的時候我們的webview就打不開了,但是粘貼到safri能打開,后來才發(fā)現(xiàn)我們之前使用的轉(zhuǎn)碼方式是用的老的stringByAddingPercentEscapesUsingEncoding進(jìn)行轉(zhuǎn)碼的,默認(rèn)會把#轉(zhuǎn)成%23,但是后來了解到#是url中的一個重要組成部分,是跟在url參數(shù)之后的的最后一部分,作為一個url的錨點(diǎn),用于瀏覽器的定位,且#之后的部分是不會傳到服務(wù)器的,僅供瀏覽器使用。我們之前之所以打不開是因?yàn)?被轉(zhuǎn)成%23了,瀏覽器找不到#這個location定位,所以就打不開了。

那么此時的#是不能轉(zhuǎn)碼的,于是我就對現(xiàn)有的url轉(zhuǎn)碼寫了個分類:

- (NSString *)dfStringByAddingPercentEncoding{? ?

NSString *encodeStr = @"";? ?

if (self.length > 0) {? ? ? ?

//針對中文和`%^{}\"[]|\\<> 進(jìn)行轉(zhuǎn)義,#作為H5路由標(biāo)志,不處理? ? ? ?

encodeStr = [self stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`%^{}\"[]|\\<> "].invertedSet]; ? ?} ? ?

return encodeStr;

}

這里對所有#都不轉(zhuǎn)碼,其實(shí)也是有風(fēng)險的,比如一個url當(dāng)中不僅參數(shù)中含#,而且也有#作為錨點(diǎn)符號,比如第一部分中的url4,

url4=http://www.itdecent.cn/writer?name=ha#ha#/notebooks/35214/notes(參數(shù)帶#,路徑后面包含#錨點(diǎn))

對于這個url,如果你不對#轉(zhuǎn)碼,那么#之后的所有內(nèi)容都不會傳到服務(wù)器,也就是僅僅只有http://www.itdecent.cn/writer?name=ha,而這個url的真正意圖是,第一個#是作為參數(shù)中的一個字符出現(xiàn)的,這個#是服務(wù)器需要的一個參數(shù),第二個#才是瀏覽器的錨點(diǎn)定位,此時就有問題了。

由此我們可以看到,#作為url中前面的路徑或者參數(shù)出現(xiàn)的時候,這部分是需要傳到服務(wù)器的,是需要轉(zhuǎn)碼的,而后面的#作為錨點(diǎn)又是不能轉(zhuǎn)碼的,這就矛盾了啊。

可能有的同學(xué)會說,我們轉(zhuǎn)碼的時候只轉(zhuǎn)url的參數(shù)部分,也就是?和#之間的部分,不就行了?

還是有問題,比如#作為參數(shù)的一部分,你通過?和#是無法正確分割的。再比如有一些不和規(guī)范的url,路徑上就有#出現(xiàn),這個#也是需要轉(zhuǎn)碼的,你只轉(zhuǎn)參數(shù)部分也是不行的。

綜上,我們轉(zhuǎn)碼還是需要對整個url全部轉(zhuǎn)碼,至于參數(shù)中出現(xiàn)的#時,就需要我們對參數(shù)中這種特殊的#進(jìn)行先轉(zhuǎn)碼然后再拼到url當(dāng)中,整個url轉(zhuǎn)碼時忽略掉#(就是我寫的分類的處理方式)。

五.拓展一下NSCharacterSet中幾個url集合

????URLFragmentAllowedCharacterSet "#%<>[\]^`{|}

? ? URLHostAllowedCharacterSet? ? ? "#%/<>?@\^`{|}

? ? URLPasswordAllowedCharacterSet? "#%/:<>?@[\]^`{|}

? ? URLPathAllowedCharacterSet? ? ? "#%;<>?[\]^`{|}

? ? URLQueryAllowedCharacterSet? ? "#%<>[\]^`{|}

? ? URLUserAllowedCharacterSet? ? ? "#%/:<>?@[\]^`

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容