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://)。

URL 百度百科

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中指定的参数的值
搜&索搜索词含有中文,含有保留字段,需要编译
&times是key的一部分,不应该被编译,若多一次编译,会编译为 x

绿色字体是保留字符,都有特殊的含义,是不应该是被编码的。 红色字体必须要要编译的部分。 黄色背景的字符串,不应该被编译。 若以上操作不正确,会影响整个URL的解析。

常见的拼接过程

1、先拼接实际的请求地址 https://www.baidu.com/s/测@试?

2、再拼接参数字符串 wd=搜&索&timestamp=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&timestamp=32424242423

3、因请求地址和参数列表已经编码过,拼接后的完整请求不应该再次编译。若再次编译 则会因含有 &times 编译为 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编码和解码过程的主要内容,如果未能解决你的问题,请参考以下文章

URL编码与解码

URL编码与解码

URL编码与解码

URL编码与解码

URL编码与解码

实际项目中前后端传输字符串URL编解码过程中遇到的一些问题