iOS端URL编码和解码过程
Posted 想名真难
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS端URL编码和解码过程相关的知识,希望对你有一定的参考价值。
一、URL含义
1、URL定义
URL 是Uniform Resource Locator 的缩写,统一资源定位符,对可以从互联网上得到的资源的位置和访问方法的一种简洁的表示,是互联网上标准资源的地址。互联网上的每个文件都有一个唯一的URL,它包含的信息指出文件的位置以及浏览器应该怎么处理它。基本URL包含模式(或称协议)、服务器名称(或IP地址)、路径和文件名、参数,如“协议://授权/路径查询?参数”。
URL 与 URI 很多人会混淆这两个名词。 URL:(Uniform/Universal Resource Locator 的缩写,统一资源定位符)。 URI:(Uniform Resource Identifier 的缩写,统一资源标识符)。 对于URI, 具体的结构如下:
foo://example.com:8042/over/there?name=ferret#nose
\\_/ \\______________/ \\________/\\_________/ \\__/
| | | | |
scheme authority path query fragment
复制代码
URI 属于 URL 更低层次的抽象,一种字符串文本标准。URL 是 URI 的一个子集。 URI 表示请求服务器的路径,定义这么一个资源。而 URL 同时说明要如何访问这个资源(http://)。
2、URL字符编码表
二、URL 编码
1、为什么要编码转义
推荐阅读:字符编码:ASCII、Unicode 和 UTF-8 的区别
世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号,不同的编码方式,解码出来就是乱码,造成数据传输和阅读的极大障碍。互联网的来临,必须要统一字符编码,Unicode(统一码、万国码、单一码)作为计算机科学领域里的一项业界标准应运而生。Unicode 是互联网统一的符号集,只规定了符号唯一的二进制代码值,却没有规定这个二进制代码应该如何存储。UTF-8是一种针对Unicode的可变长度字符编码,UTF-8用1到4个字节编码Unicode字符,在互联网上使用最广的一种 Unicode 的实现方式。其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用。 注意:UTF-8 是 Unicode 的实现方式之一。 如 中 字:
Unicode码值: \\u4e2d
URL编码(UTF-8): %e4%b8%ad
2、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"。
3、URL不需要编码的字符
HTTP URL 使用的RFC3986编码规范,RFC3986文档规定,URL中只允许包含以下四种:
1、英文字母(a-z A-Z)
2、数字(0-9)
3、-_.~ 4个特殊字符
4、所有保留字符,RFC3986中指定了以下字符为保留字符(英文字符): ! * ' ( ) ; : @ & = + $ , / ? # [ ]
5、编码标记符号 %
URL 编码使用 "%" 其后跟随两位的十六进制数来替换非 ASCII 的字符,中文是三个编码组合。十六进制格式用于在浏览器和插件中显示非标准的字母和字符。
4、URL需要编码的字符
Url编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。
4.1、非URL定义的字符
不能在 URL 中包含任何非 ASCII 字符,如中文字符、希腊文字符,拉丁文字符等。如果客户端浏览器和服务端浏览器支持的字符集不同的情况下,中文可能会造成乱码问题。
4.2、会引起歧义的保留字符
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中用到特殊字符或中文字符,需要将这些特殊字符换成相应的十六进制的值。
三、ios端URL具体编码处理
1、URL编码和解码是成对
URL编码和解码是一个可逆的过程,编码和解码的逻辑是翻转对应的。 成对有两层含义: 1、两个方法的逻辑对应。一个固定的编码方式,也对一个固定的逆向解码方式,反之亦然。
2、编码和解码的次数也要一一对应。
这四种种字符后,URL编码后的值还是它本身:
1、英文字母(a-z A-Z)
2、数字(0-9)
3、特殊字符( -_.)
4、部分保留字符(英文字符): ! * ' ( ) ; : @ & = + $ , / ?
说明:~ # []
这四个字符是否被转码成百分号编码,因系统不同会有不同。
URL字符编码使用%百分号加上两位的字符——0123456789ABCDEF——代表一个字节的十六进制形式。因编码后的值含有 %
保留字符。再次编译% 会编译成 %25
。
例如: &
第一次URL编码后:%26
第二次URL编码后:%2526
第三次URL编码后:%252526
正常解码逻辑:
第一次URL解码后:%2526
第二次URL解码后:%26
第三次URL解码后:&
因此,URL编码和解码必须是成对出现的。
初始字符为 & 连续编码三次,连续解码两次,则得到 %26 。 初始字符为 &%26 编码一次:%26%2526 连续解码两次则得到 && 。
2、URL是怎么拆解的
我们看一个常见的接口请求示例:
一般会根据 ://
、:
、 /
、 ?
、 &
、=
等拆分出请求的协议、服务器名称(或IP地址)、端口号、路径和文件名、参数名、参数值等。
3、在组装URL的什么阶段进行URL编码
我们看一个常见的接口请求示例:
字符串 | 说明 |
---|---|
:// | 协议符号 |
/ | 分隔目录和子目录 |
测试 | 代表需要编译处理了的路径 |
? | 分隔实际的URL和参数 |
& | URL中指定的参数间的分隔符 |
= | URL中指定的参数的值 |
搜&索 | 搜索词含有中文,含有保留字段,需要编译 |
× | 是key的一部分,不应该被编译,若多一次编译,会编译为 x |
绿色字体是保留字符,都有特殊的含义,是不应该是被编码的。 红色字体必须要要编译的部分。 黄色背景的字符串,不应该被编译。 若以上操作不正确,会影响整个URL的解析。
常见的拼接过程:
1、先拼接实际的请求地址 https://www.baidu.com/s/测@试?
2、再拼接参数字符串 wd=搜&索×tamp=32424242423
3、将1、2合并凭借成一个网址字符串。
4、将网址字符串转为NSURL 实例。
分析
1、因 测@试
含有中文和保留字符@,需要在步骤1之前,先将 测@试
编码为 %e6%b5%8b%40%e8%af%95
,再拼接到https://www.baidu.com/s/%e6%b5%8b%40%e8%af%95?
2、因 搜&索
含有中文和保留字符& ,& 会影响参数解析。需要先搜&索
编码为 %e6%90%9c%26%e7%b4%a2
,再拼接到wd=%e6%90%9c%26%e7%b4%a2×tamp=32424242423
3、因请求地址和参数列表已经编码过,拼接后的完整请求不应该再次编译。若再次编译 则会因含有 ×
编译为 x 。
小结
上面我们分别编码特殊字符后,最后拼接到一起。也有部分写法是拼接后再统一编码处理的。但因请求路径、请求参数中都可能含有保留字符&、=或中文等特殊字符,造成请求地址解析错误。建议在路径和参数拼接前对路径、参数名、参数值等先行统一编码处理,再行拼接。拼接好后不要再行编码,转为NSURL实例,发送请求。
4、可用的编码和解码API
URL编码是互联网的通用规范,各系统或平台都会提供封装好的API方法供开发者调用。 iOS端在生成NSURL实例
NSURL *url = [NSURL URLWithString:urlString];
特别要注意的是 urlString 中含有超出中文字符等非定URL限定字符时,创建的NSURL对象会失败,url返回为nil。
之前苹果有URL编码的方法, 但是都已经标记为不推荐,此处就不在介绍,只介绍推荐的方式,iOS9之后可用。
推荐的全新的方式: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.3、最优方案和封装处理
创建一个 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解码。
AF的编码方式也可以参考:
/**
Returns a percent-escaped string following RFC 3986 for a query string key or value.
RFC 3986 states that the following characters are "reserved" characters.
- General Delimiters: ":", "#", "[", "]", "@", "?", "/"
- Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
should be percent-escaped in the query string.
- parameter string: The string to be percent-escaped.
- returns: The percent-escaped string.
*/
NSString * AFPercentEscapedStringFromString(NSString *string)
static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
[allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
// return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
static NSUInteger const batchSize = 50;
NSUInteger index = 0;
NSMutableString *escaped = @"".mutableCopy;
while (index < string.length)
NSUInteger length = MIN(string.length - index, batchSize);
NSRange range = NSMakeRange(index, length);
// To avoid breaking up character sequences such as 👴🏻👮🏽
range = [string rangeOfComposedCharacterSequencesForRange:range];
NSString *substring = [string substringWithRange:range];
NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[escaped appendString:encoded];
index += range.length;
return escaped;
5、其他处理方法
可以将需要编码的参数表整体封装为NSData类型,使用post请求发送也是可以的。
四、总结
1、在URL组装拼接前对各个部分的可能会引起歧义的字符串进行全量UTF-8编码。
2、在需要解码的地方,需要先分拆字符串,再分段解码使用。
3、在需要将已组装的数据,进行重组时,需要先拆解,分别解码后再编码,最后再重组。
4、服务端会对请求进行UTF-8解码一次,请确保请求中的字符只进行一次UTF-8编码。
原文,有删减 :全面深度解析iOS端URL编码和解码过程 - 掘金
RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax
以上是关于iOS端URL编码和解码过程的主要内容,如果未能解决你的问题,请参考以下文章