一、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字符编码表
1、URL 编码 - 从 %00 到 %ff
2、HTML特殊字符编码对照表
二、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。
4.1、iOS 7之前的编码解码处理
4.1.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之后苹果也废弃该方式。
另外,还有encodeBase64、decodeBase64方式,也是不能编译-_.~&!*'();:@&=+$,/?#[]%
特殊字符,处理结果和方式一结果基本一致,就不再展开说明了。
4.1.2、方式二: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.2、iOS 7之后的编码解码处理
4.2.1、全新的方式: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、最优方案和封装处理
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解码。
5、其他处理方法
可以将需要编码的参数表整体封装为NSDdata类型,使用post请求发送也是可以的。
四、总结
1、在URL组装拼接前对各个部分的可能会引起歧义的字符串进行全量UTF-8编码。
2、在需要解码的地方,需要先分拆字符串,再分段解码使用。
3、在需要将已组装的数据,进行重组时,需要先拆解,分别解码后再编码,最后再重组。
4、服务端会对请求进行UTF-8解码一次,请确保请求中的字符只进行一次UTF-8编码。
参考资料
1、HTML URL 编码参考手册
2、HTML URL 编码
3、在线url网址编码、解码
4、站长工具 之 URL编码解码
5、URL原理、URL编码、URL特殊字符
原创不易,转载请注明作者:择势勤