原文:How to percent encode a URL String
作者:@kharrison
譯者:CocoaChina--飄
在和web服務進行交互時,我們經常需要對URL中的特定字符和傳輸的表單數據進行百分號編碼。例如,’&’在百分號編碼時會變成’%26’。搞清楚 URL中哪部分的哪些字符應該進行百分號編碼了並不是件易事。最好的資料好像是RFC 3986和W3C HTML5。出於興趣和教育目的,我創建了swift的String的擴展(和作為對比的Objective-C的分類)。
RFC3986 編碼查詢字符串
在 RFC3986 的第2.3節列出了你不需要百分號編碼的字符,因為它們在URL中沒有特殊的含義。
ALPHA / DIGIT / “-” / “.” / “_” / “~”
α/數字/”-”/”.”/”_”
第3.4節也解釋了因為查詢往往會本身包含一個URL,最好不要百分號編碼斜槓(“/”)和問號(“?”)。這也是受歡迎的iOS HTTP網絡庫Alamofire采取的方法,這給了我信心。
因此,用RFC 3986編碼一個兼容性的查詢,我們可以百分號編碼如上所述以外的所有字符。這很簡單,如果我們首先構建一組允許的字符,然後用stringByAddingPercentEncodingWithAllowedCharacters去編碼剩余的。
注意:蘋果已經在iOS 9中棄用了stringByAddingPercentEscapesUsingEncoding或CFURLCreateStringByAddingPercentEscapes這兩個方法。
Swift
首先,swift String extension:
extension String { func stringByAddingPercentEncodingForRFC3986() -> String? { let unreserved = "-._~/?" let allowed = NSMutableCharacterSet.alphanumericCharacterSet() allowed.addCharactersInString(unreserved) return stringByAddingPercentEncodingWithAllowedCharacters(allowed) } }
Object-C
我們可以用Object-C的NSString的分類來做相同的事。
@implementation NSString (URLEncoding) - (nullable NSString *)stringByAddingPercentEncodingForRFC3986 { NSString *unreserved = @"-._~/?"; NSMutableCharacterSet *allowed = [NSMutableCharacterSet alphanumericCharacterSet]; [allowed addCharactersInString:unreserved]; return [self stringByAddingPercentEncodingWithAllowedCharacters: allowed]; } @end
用例
// Swift let query = "one&two =three" let encoded = query.stringByAddingPercentEncodingForRFC3986() // "one%26two%20%3Dthree" // Objective-C NSString *query = @"one&two =three"; NSString *encoded = [query stringByAddingPercentEncodingForRFC3986]; // "one%26two%20%3Dthree"
對x-www-form-urlencoded進行編碼
推薦W3C HTML5 對表單數據編碼是相似的,但是和RFC 3986有一點不同。在第4.10.22.5節中告訴我們下列字符是不應該百分號編碼:
ALPHA / DIGIT / “*” / “-” / “.” / “_”
α/數字/”-”/”.”/”_”
你應該用“+”(0x2B)代替空格(“ ”)。它和RFC 3986 的不同在 Stack Overflow answer 裡有描述。波浪號(“~”)被百分號編碼了,但是星號(“*”)沒有。該建議很好地總結了這種情況:這種編碼的表單數據在很多方面是異常的,多年來的實踐的問題和折中解決導致了互通性的一系列必要操作。但是絕不代表好的設計實踐。
Swift
給String extension添加一個新的方法
public func stringByAddingPercentEncodingForFormData(plusForSpace: Bool=false) -> String? { let unreserved = "*-._" let allowed = NSMutableCharacterSet.alphanumericCharacterSet() allowed.addCharactersInString(unreserved) if plusForSpace { allowed.addCharactersInString(" ") } var encoded = stringByAddingPercentEncodingWithAllowedCharacters(allowed) if plusForSpace { encoded = encoded?.stringByReplacingOccurrencesOfString(" ", withString: "+") } return encoded }
注意,由於很多 web服務好像不關心我用“+”或者百分號編碼將空格做了可選的編碼。
Object-C
Object-C的方法缺少一個可選參數
- (nullable NSString *)stringByAddingPercentEncodingForFormData:(BOOL)plusForSpace { NSString *unreserved = @"*-._"; NSMutableCharacterSet *allowed = [NSMutableCharacterSet alphanumericCharacterSet]; [allowed addCharactersInString:unreserved]; if (plusForSpace) { [allowed addCharactersInString:@" "]; } NSString *encoded = [self stringByAddingPercentEncodingWithAllowedCharacters:allowed]; if (plusForSpace) { encoded = [encoded stringByReplacingOccurrencesOfString:@" " withString:@"+"]; } return encoded; }
用例:
// Swift let query = "one two" let space = query.stringByAddingPercentEncodingForFormData() // "one%20two" let plus = query.stringByAddingPercentEncodingForFormData(true) // "one+two" // Objective-C NSString *query = @"one two"; NSString *encodedQuery = [query stringByAddingPercentEncodingForFormData:YES]; // "one+two"
源代碼
Swift代碼和一些測試用例你可以在我的Github代碼實例庫的Encode項目裡找到,Object-C的分類和測試用例在TwitterSearch項目裡。歡迎反饋和改進。
深入閱讀
百分號編碼(維基百科)
RFC 3986同一資源標識符(URI):通用語法
W3C HTML5 見第4.10.22.6 節URL表單數據的編碼