url转码遇到的坑

一、问题背景

    相信很多ios开发者在项目中都需要用到uiwebview,那就离不开url了,一般符合网络标准的url是没啥问题的,那么当遇到一些特殊的url时,你就会踩坑了。。。举几个栗子:

顺带说一下一个完整的url格式:

协议://域名:端口号/路径?参数1&参数2#路由锚点(浏览器使用)

url1=https://www.jianshu.com/这是中文/notebooks&123/3521954/notes(路径含中文和&)

url2=https://www.jianshu.com/notebooks/352195?location=%&23!&userid=8957(参数含%&!特殊字符)

url3=https://www.jianshu.com/notebooks/352195?username=逗比&userid=89757(参数含中文)

url4=https://www.jianshu.com/writer?name=ha#ha#/notebooks/35214/notes(参数带#,路径后面包含#锚点)

当你遇上以上几种url,你如果上来就粗暴的搞个[NSURL URLWithString:url],然后用webview开始loadrequest,那你就会惊喜的发现:根本打不开,404错误赫然出现在眼前。此时,你就需要对url先进行转码了,下面我们就url转码的问题好好说说。

二、url转码的具体原因

    在iOS程序中,访问一些HTTP/HTTPS的资源服务时,如果url中存在中文或者特殊字符时,会导致无法正常的访问到资源或服务,想要解决这个问题,需要对url进行编码。

网络标准RFC 1738规定url中只能包含英文字母和阿拉伯数字,以及一些特殊字符:

"...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'()," [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL."

只有字母和数字[0-9a-zA-Z]、和特殊符号”$-_.+!*’(),”[不包括双引号]、及某些保留字,才可以不经过编码直接用于URL。”

此时如果url中包含如汉字或者其他特殊字符则需要对它进行编码,编码的意义在于,假如url的参数中的中文或特殊字符在发送到服务端时,服务端无法解析它的真正意义,会导致服务端不能理解客户端的请求,如前面列举的几个特殊的url。

三、转码coding

    1. 使用CoreFoundation对url参数进行encode

使用API:

CFStringRef CFURLCreateStringByAddingPercentEscapes(CFAllocatorRef allocator, CFStringRef originalString, CFStringRef charactersToLeaveUnescaped, CFStringRef legalURLCharactersToBeEscaped, CFStringEncoding encoding)

- (void)encodeUrl {    

NSString *para1 = [self encodeParameter:@"%&23"];   // p1=%&23      

NSString *para2 = [self encodeParameter:@"我是参数8957"];   // p2=我是参数8957

NSString *encodeUrl = [NSString stringWithFormat:@"https://www.jianshu.com?p1=%@&p2=%@", para1, para2];   

NSLog(@"%@", encodeUrl);

}

- (NSString *)encodeParameter:(NSString *)originalPara {   

CFStringRef encodeParaCf = CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)originalPara, NULL, CFSTR("!*'();:@&=+$,/?%#[]"), kCFStringEncodingUTF8);   

NSString *encodePara = (__bridge NSString *)(encodeParaCf);    CFRelease(encodeParaCf);   

return encodePara;

}

打印结果如下:

H5ViewController.m:59 https://www.jianshu.com?p1=%25%2623&p2=%E6%88%91%E6%98%AF%E5%8F%82%E6%95%B08957

可以看到转码对象中,除了中文正常转码外,特殊字符只要包含在!*'();:@&=+$,/?%#[]这些字符范围内的都进行了转码。

此方法适用于,url前缀不包含中文以及其它非法字符的情况,只需要对参数进行编码即可。

2.使用Foundation框架对完整url进行encode

这里有两个方法都用于url转码:

方法一.- (nullable NSString *)stringByAddingPercentEscapesUsingEncoding:(NSStringEncoding)encode;(已废弃)

调用代码示例如下:

- (void)encodeUrl {   

NSString *url = [@"https://www.jianshu.com/这是中文/notebooks&123/3521954/notes" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];    NSLog(@"%@",url);

}

打印结果如下:

2018-06-12 14:03:57.864836+0800 haha[10930:15910902] https://www.jianshu.com/%E8%BF%99%E6%98%AF%E4%B8%AD%E6%96%87/notebooks&123/3521954/notes

可以看到原先url带的中文都被转码了,这里需要说明的是,该方法已经被苹果废弃,因为该方支持的字符比较少,只对`#%^{}[]|\"<> 加空格共14个字符编码,不包括&?等符号

方法二.- (nullable NSString *)stringByAddingPercentEncodingWithAllowedCharacters:(NSCharacterSet *)allowedCharacters;(推荐)

说到方法二,虽说拓展性更强了,允许你自定义字符集,也是有坑的呀,下面看一下苹果的官方api:

// Returns a new string made from the receiver by replacing all characters not in the allowedCharacters set with percent encoded characters. UTF-8 encoding is used to determine the correct percent encoded characters. Entire URL strings cannot be percent-encoded. This method is intended to percent-encode an URL component or subcomponent string, NOT the entire URL string. Any characters in allowedCharacters outside of the 7-bit ASCII range are ignored.  

最后一句Any characters in allowedCharacters outside of the 7-bit ASCII range are ignored.,意思就是说,任何非7-bit ASCII字符搁到allowedCharacters里面也将被忽略,也就是allowedCharacters里面的字符跟7-bit ASCII字符不会被编码。

换句话说,上面方法在处理的时候会编码url的中的非7-bit ASCII字符,如这些【`#%^{}"[]|\<>】,如果需要忽略之,需要通过(NSCharacterSet *)allowedCharacters这个参数指定。

到此坑就来了,有的同学可能通过各种文章了解这个方法就是上文说的意思,其实不是的,测试代码如下:

- (void)encodeUrl {   

NSString *url = @"https://www.jianshu.com/notebooks/352195?location=%&23!&userid=8957";   

NSString *encodeUrl = [url stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "]];    NSLog(@"%@",encodeUrl);       

NSString *testStr = @"2#&!@测试";   

NSString *testEncodeUrl = [testStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "]];    NSLog(@"%@",testEncodeUrl);    

}

按照上面的解释,字符集合[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "]包含的字符都不会编码,也就是代码中url中的正常的7-bit ASCII字符(字母,数字等)和字符集中的%不会被编码,但是结果却吓了我一跳:

2018-06-12 14:32:46.432905+0800 haha[11266:15930653] %68%74%74%70%73%3A%2F%2F%77%77%77%2E%6A%69%61%6E%73%68%75%2E%63%6F%6D%2F%6E%6F%74%65%62%6F%6F%6B%73%2F%33%35%32%31%39%35%3F%6C%6F%63%61%74%69%6F%6E%3D%%26%32%33%21%26%75%73%65%72%69%64%3D%38%39%35%37

2018-06-12 14:32:46.433076+0800 haha[11266:15930653] %32#%26%21%40%E6%B5%8B%E8%AF%95

可以看到第一个url被全部编码了,仅仅除了一个%没有被编码(第一条log可能有点长,看不出来,但从第二条log可以看出只有#没有被编码),按理说正常的字符也不会被编码啊,怎么常见的字母和数字都被编码了呢?

由此可以看出不是我们之前所理解的“allowedCharacters里面的字符跟7-bit ASCII字符不会被编码”,而是只有allowedCharacters里的字符才不会被编码!!!

那么怎么才能正确忽略部分字符对url正常转码呢?

invertedSet的作用就来了,代码如下:

- (void)encodeUrl {   

NSString *url = @"https://www.jianshu.com/notebooks/352195?location=%&23!&userid=8957";   

NSString *encodeUrl = [url stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet];    NSLog(@"%@",encodeUrl);       

NSString *testStr = @"2#&!@测试";   

NSString *testEncodeUrl = [testStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`#%^{}\"[]|\\<> "].invertedSet];    NSLog(@"%@",testEncodeUrl);    

}

打印结果如下:

2018-06-12 15:13:05.725613+0800 haha[11629:15956002] https://www.jianshu.com/notebooks/352195?location=%25&23!&userid=89572018-06-12 15:13:05.725997+0800 haha[11629:15956002] 2%23&!@%E6%B5%8B%E8%AF%95

可以看到,通过集合反转之后得到的结果才是我们想要的,但是此处的意思是反的,就是对集合进行invertedSet,表示集合内的字符和非7-bit ASCII字符是需要转码的,所以我们以后使用这个方法进行转码的时候要从反面进行转码,把想要进行转码的特殊字符写在集合里就好了,注意这里说的是想要转码的特殊字符(!*'();:@&=+$,/?%#[]),像中文会被认为是非7-bit ASCII字符会自动转码的所以中文你就不用操心了。

举个例子,我想对url中的%不转码,其他的特殊字符都转码,那你在集合中写上想转码的字符就好了,不要写%。补充说一下,用这个方法转码的时候不用担心ios系统适配的问题,这个方法支持ios7之后的机器,目前苹果机型基本上没有低于ios7之后的机器了。

四.关于url含有#的问题

项目中H5给的url中总是含有符号#,每次有#的时候的时候我们的webview就打不开了,但是粘贴到safri能打开,后来才发现我们之前使用的转码方式是用的老的stringByAddingPercentEscapesUsingEncoding进行转码的,默认会把#转成%23,但是后来了解到#是url中的一个重要组成部分,是跟在url参数之后的的最后一部分,作为一个url的锚点,用于浏览器的定位,且#之后的部分是不会传到服务器的,仅供浏览器使用。我们之前之所以打不开是因为#被转成%23了,浏览器找不到#这个location定位,所以就打不开了。

那么此时的#是不能转码的,于是我就对现有的url转码写了个分类:

- (NSString *)dfStringByAddingPercentEncoding{   

NSString *encodeStr = @"";   

if (self.length > 0) {       

//针对中文和`%^{}\"[]|\\<> 进行转义,#作为H5路由标志,不处理       

encodeStr = [self stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet characterSetWithCharactersInString:@"`%^{}\"[]|\\<> "].invertedSet];    }    

return encodeStr;

}

这里对所有#都不转码,其实也是有风险的,比如一个url当中不仅参数中含#,而且也有#作为锚点符号,比如第一部分中的url4,

url4=https://www.jianshu.com/writer?name=ha#ha#/notebooks/35214/notes(参数带#,路径后面包含#锚点)

对于这个url,如果你不对#转码,那么#之后的所有内容都不会传到服务器,也就是仅仅只有https://www.jianshu.com/writer?name=ha,而这个url的真正意图是,第一个#是作为参数中的一个字符出现的,这个#是服务器需要的一个参数,第二个#才是浏览器的锚点定位,此时就有问题了。

由此我们可以看到,#作为url中前面的路径或者参数出现的时候,这部分是需要传到服务器的,是需要转码的,而后面的#作为锚点又是不能转码的,这就矛盾了啊。

可能有的同学会说,我们转码的时候只转url的参数部分,也就是?和#之间的部分,不就行了?

还是有问题,比如#作为参数的一部分,你通过?和#是无法正确分割的。再比如有一些不和规范的url,路径上就有#出现,这个#也是需要转码的,你只转参数部分也是不行的。

综上,我们转码还是需要对整个url全部转码,至于参数中出现的#时,就需要我们对参数中这种特殊的#进行先转码然后再拼到url当中,整个url转码时忽略掉#(就是我写的分类的处理方式)。

五.拓展一下NSCharacterSet中几个url集合

    URLFragmentAllowedCharacterSet "#%<>[\]^`{|}

    URLHostAllowedCharacterSet      "#%/<>?@\^`{|}

    URLPasswordAllowedCharacterSet  "#%/:<>?@[\]^`{|}

    URLPathAllowedCharacterSet      "#%;<>?[\]^`{|}

    URLQueryAllowedCharacterSet    "#%<>[\]^`{|}

    URLUserAllowedCharacterSet      "#%/:<>?@[\]^`

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,045评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,114评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,120评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,902评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,828评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,132评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,590评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,258评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,408评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,335评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,385评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,068评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,660评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,747评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,967评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,406评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,970评论 2 341

推荐阅读更多精彩内容