原文: How to percent encode a URL String
作者: @kharrison
译者:CocoaChina-- 飘
在和web服务进行交互时,我们经常需要对URL中的特定字符和传输的表单数据进行 百分号编码 。例如,’&’在百分号编码时会变成’%26’。搞清楚 URL中哪部分的哪些字符应该进行百分号编码了并不是件易事。最好的资料好像是 RFC 3986 和 W3C HTML5 。出于兴趣和教育目的,我创建了swift的String的扩展(和作为对比的Objective-C的分类)。
在 RFC3986 的第2.3节列出了你不需要百分号编码的字符,因为它们在URL中没有特殊的含义。
ALPHA / DIGIT / “-” / “.” / “_” / “~”
α/数字/”-”/”.”/”_”
第3.4节也解释了因为查询往往会本身包含一个URL,最好不要百分号编码斜杠(“/”)和问号(“?”)。这也是受欢迎的iOS HTTP网络库 Alamofire 采取的方法,这给了我信心。
因此,用RFC 3986编码一个兼容性的查询,我们可以百分号编码如上所述以外的所有字符。这很简单,如果我们首先构建一组允许的字符,然后用stringByAddingPercentEncodingWithAllowedCharacters去编码剩余的。
注意:苹果已经在iOS 9中弃用了stringByAddingPercentEscapesUsingEncoding或CFURLCreateStringByAddingPercentEscapes这两个方法。
首先,swift String extension:
extension String { func stringByAddingPercentEncodingForRFC3986() -> String? { let unreserved = "-._~/?" let allowed = NSMutableCharacterSet.alphanumericCharacterSet() allowed.addCharactersInString(unreserved) return stringByAddingPercentEncodingWithAllowedCharacters(allowed) } }
我们可以用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"
推荐 W3C HTML5 对表单数据编码是相似的,但是和RFC 3986有一点不同。在第4.10.22.5节中告诉我们下列字符是不应该百分号编码:
ALPHA / DIGIT / “*” / “-” / “.” / “_”
α/数字/”-”/”.”/”_”
你应该用“+”(0x2B)代替空格(“ ”)。它和RFC 3986 的不同在 Stack Overflow answer 里有描述。波浪号(“~”)被百分号编码了,但是星号(“*”)没有。该建议很好地总结了这种情况: 这种编码的表单数据在很多方面是异常的,多年来的实践的问题和折中解决导致了互通性的一系列必要操作。但是绝不代表好的设计实践。
给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的方法缺少一个可选参数
- (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表单数据的编码