当前位置: 首页 > news >正文

URL 使用规范

文章目录

      • 一、URL 简介
      • 二、服务端
      • 三、客户端
        • 2.1、客户端构造的 URL 地址的正确方式
          • 2.1.1、直接构造
          • 2.1.2、使用`NSURLComponents`构造
        • 2.2、服务器下发的 URL 的正确使用方式
          • 2.2.1 直接使用
          • 2.2.2 防护之后使用
          • 2.2.3 二次加工之后使用
        • 2.3、读取 URL 中的参数

处理 URL 是常见的开发需求,但是在十几年的从业生涯中,几乎所有公司,包括以技术见长互联网大厂,都发生过URL相关的问题,究其根本原因,其实就是 URL 使用不规范造成的。

一、URL 简介

​ 根据 RFC 3986 标准,URL 由 scheme、host、path、query 等部件(Component)通过保留符号连接而成。

[scheme]://[username]:[password]@[host]:[port]/[path]?[query]#[fragment]

保留符号除了上面这些,包括如下在部件中使用的符号:

  • 在 scheme 中用到的-符号。
  • 在 host 中用到.符号。
  • 在 path 中用到的/符号。
  • 在 query 中用到的&=符号。

​ 标准 RFC 3986 还规定了 URL 每个部件可使用的字符集,且每个部件可用字符集都不相同,下面是 iOS 中对这些字符集的定义。

NSCharacterSet.URLUserAllowedCharacterSet;// usernameNSCharacterSet.URLPasswordAllowedCharacterSet;// passwordNSCharacterSet.URLHostAllowedCharacterSet;// hostNSCharacterSet.URLPathAllowedCharacterSet;// pathNSCharacterSet.URLQueryAllowedCharacterSet;// queryNSCharacterSet.URLFragmentAllowedCharacterSet;// fragment

二、服务端

服务端下发给客户的的 URL 必须是符合 RFC 3986 规范的形式,具体操作就是:

  1. 应该先将 URL 的各个部件进行编码,然后再用保留符号连接起来。
  2. 对于部件的编码,应该是先对部件的“组成部分”进行编码,然后用保留符号连接,比如对 query 编码,应该先将字段名和字段值分别编码,用=连接起来后,再然后用&连接起来,不能先连接然后再整体编码。
  3. 对部件或部件组成部分的编码,应该使用encodeURIComponent方法,如果平台没有该方法,应将除字母和数字之外的所有字符,都进行编码。

三、客户端

​ 在 iOS 客户端中,使用到的 URL 要么是客户端生成的,要么是服务器下发的,需要根据具体情形来处理。

2.1、客户端构造的 URL 地址的正确方式

​ 由客户端直接产生的 URL 地址,比如构造接口请求的地址,需按情形处理。

2.1.1、直接构造

​ 如果 URL 中所有字符都是已知可以预知的,都符合 URL 规范或已进行 URL 编码,那么就可以直接构造。

  1. 编码时使用.alphanumericCharacterSet字符集,仅保留字母和数字,编码其它所有字符。
  2. 不能使用.URLQueryAllowedCharacterSet字符集,这个字符集包含=&?这三个符字符,如果字段名或字段值包含这三个字符,就无法正常使用了。
// URL 中所有字符串都是已知的,且符合规范[NSURL URLWithString:@"https://api.host.com/path?key=value"];// URL 中有不合法的字符,但是可以提前对不合法的字符进行转码[NSURL URLWithString:@"https://api.host.com/path?key=%20%5F"];// 可以预知结果的简单拼接。NSString*encodedValue=[originValue stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.alphanumericCharacterSet];NSString*urlString=[NSString stringWithFormat:@"https://api.host.com/path?key=%@",encodedValue];NSURL*url=[NSURL URLWithString:urlString];
2.1.2、使用NSURLComponents构造

​ 如果在 URL 中,用到从外部传入的动态数据时,推荐使用NSURLComponents,不推荐使用字符串拼接。

// 创建 components 对象,不包含参数NSURLComponents*components=[NSURLComponents componentsWithString:@"https://api.host.com/path"];// 固定参数。如果有的话,key 和 value 必须都使用 NSString 类型NSMutableDictionary*parameters=[NSMutableDictionary dictionaryWithDictionary:@{@"key":@"value"}];// 动态参数:将外部传入参数 inputParameters 与固定参数合并[inputParameters enumerateKeysAndObjectsUsingBlock:^(id key,id obj,BOOL*stop){// 将动态参数的 key 和 value 都转换为 NSString 类型if(![key isKindOfClass:NSString.class]){key=[NSString stringWithFormat:@"%@",key];}if(![obj isKindOfClass:NSString.class]){obj=[NSString stringWithFormat:@"%@",obj];}parameters[key]=obj;}];// 生成参数NSMutableArray*queryItems=[NSMutableArray arrayWithCapacity:parameters.count];[parameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key,id _Nonnull obj,BOOL*_Nonnull stop){// 在上面 key obj 已处理为字符串类型// 在 NSURLQueryItem 中,使用原始值即可,不需要事先编码。NSURLQueryItem*item=[NSURLQueryItem queryItemWithName:key value:obj];[queryItems addObject:item];}];components.queryItems=queryItems;// 将参数赋值给 components 对象// 生成 URLNSURL*apiURL=components.URL;
2.2、服务器下发的 URL 的正确使用方式
2.2.1 直接使用

​ 一般场景下,服务器下发的地址,比如图片地址,因为可以动态调整,且影响较小,应由服务端进行编码,客户端可以直接使用。

NSURL*imageURL=[NSURL URLWithString:imageURLString];NSLog(@"%@",imageURL);
2.2.2 防护之后使用

​ 重要场景,比如页面跳转,即使服务器已经进行了编码,客户端也应该进行必要的防护。

​ 不合法 URL 千差万别,实际很难有万全的防护逻辑,大部分情况下都是先尝试能不能创建 URL 如果不能则尝试转码之后再试。

NSString*pageURLString;// 必要的防护。NSString*safePageURLString=[pageURLString addCustomURLEncoding];NSURL*nextPageURL=[NSURL URLWithString:safePageURLString];NSLog(@"%@");
2.2.3 二次加工之后使用

​ 在实际操作中,不可避免的要对服务器下发的URL进行二次加工,比如处理 URL 参数。

服务器下发的URL任何形式都有可能,只建议使用NSURLComponents处理。

NSURLComponents*components=[NSURLComponents componentsWithString:urlStringFromServer];// 如果服务器下发的 URL 不合法,先尝试使用系统的方式处理if(components==nil){components=[NSURLComponents componentsWithString:urlStringFromServer encodingInvalidCharacters:YES];}// 如果系统的处理不了,使用自定义的防护逻辑if(components==nil){NSString*fixedURLString=[urlStringFromServer addCustomURLEncoding];components=[NSURLComponents componentsWithString:fixedURLString];}// 对 NSURLComponents 二次加工(见后文)// 从 NSURLComponents 获取 NSURLNSURL*url=components.URL;
  • 1、删除参数
// 逆向遍历删除NSMutableArray*queryItems=components.queryItems.mutableCopy;for(NSInteger i=queryItems.count-1;i>=0;i--){NSURLQueryItem*item=queryItems[i];if([item.name isEqualToString:keyToDelete]){[queryItems removeObjectAtIndex:i];// 如果确定字段只有一个值,可以直接 break}}components.queryItems=queryItems;
  • 2、添加参数
NSMutableArray*queryItems=components.queryItems.mutableCopy?:NSMutableArray.new;// 从字典中添加参数[parameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key,id _Nonnull obj,BOOL*_Nonnull stop){// key obj 都必须是字符串if(![key isKindOfClass:NSString.class]){key=[NSString stringWithFormat:@"%@",key];}if(![obj isKindOfClass:NSString.class]){obj=[NSString stringWithFormat:@"%@",obj];}// 在 NSURLQueryItem 中,使用原始值,不需要事先编码。NSURLQueryItem*item=[NSURLQueryItem queryItemWithName:key value:obj];[queryItems addObject:item];}];components.queryItems=queryItems;
  • 3、修改参数

​ 由于NSURLQueryItem是只读的,修改参数按照“先删除后添加”的方式处理即可,即上面的“删除参数”和“添加参数”部分。

  • 4、修改其它部分
// 使用原始值,直接使用对应的属性即可,NSURLComponents 会自动处理编码components.host=@"new.host";// 修改域名components.path=@"/new/path";// 修改路径// 若已有现成的、确定已编码的值,否则不建议使用 encoded 开头的属性。components.encodedHost=@"encoded.new.host";components.percentEncodedPath=@"encoded/new/path";
2.3、读取 URL 中的参数

​ URL 中的参数,一般有下面几种形式。

形式说明
key=value@"value"一般情况
key=@""空字符串
keynil包含字段,但没有值
key=value&key=&key@[@"value", @"", NSNull.null]值为数组,值为上面三种任意情况

使用字符分割的方式读取参数,需要自行处理URL编码,不建议。

NSURLComponents*components=[NSURLComponents componentsWithString:urlFromServer];NSMutableArray*results=[NSMutableArray array];// 以下通用处理逻辑,一般情况下,找到第一个符合的就可以结束循环。NSArray*queryItems=components.queryItems;for(NSInteger i=queryItems.count-1;i>=0;i--){NSURLQueryItem*item=queryItems[i];if([item.name isEqualToString:keyToRead]){// keyToRead 为待读取的字段名if(item.value){[results addObject:item.value];}else{[results addObject:NSNull.null];}}}switch(results.count){case0:value=nil;break;case1:value=results[0];break;default:value=results;break;}if(value){NSLog(@"URL 中包含 %@ 字段",keyToRead);if(value==NSNull.null){NSLog(@"URL 中字段 %@ 的值为 nil",keyToRead);}elseif([value isKindOfClass:NSArray.class]){NSLog(@"URL 中字段 %@ 的包含多个值:%@",keyToRead,value);}else{NSLog(@"URL 中字段 %@ 的值为:%@",keyToRead,value);}}else{NSLog(@"URL 中不包含 %@ 字段",keyToRead);}

​ 值为数组的字段处理方式。

// 假设字段 key 的值为 value1, value2, value3 组成的数组。NSArray*values=@[@"value1",@"value2",@"value3"];// 一般服务器默认都支持 key=value1&key=value2&key=value3 这种形式,iOS默认也是这种形式。for(NSString*valueinvalues){[queryItems addObject:[NSURLQueryItem queryItemWithName:@"key"value:value]];}// 部分服务器默认也支持 key[]=value1&key[]=value2&key[]=value3 这种形式,需要自行处理。for(NSString*valueinvalues){[queryItems addObject:[NSURLQueryItem queryItemWithName:@"key[]"value:value]];}// 上述两种方式的区别就是,第一种方式无法表示只有一个元素的数组。// 在实践中,不论使用何种方式,都要与后端约定好,比如还可以使用 JSON 字符串。NSString*json=[values toJSONString];[queryItems addObject:[NSURLQueryItem queryItemWithName:@"key"value:json]];
http://www.gsyq.cn/news/1618142.html

相关文章:

  • Pikachu靶场从入门到精通(五):RCE、XXE、SSRF与反序列化漏洞实战
  • 硬件学习笔记
  • Go escape逃逸分析
  • 孤能子视角:Karpathy LLM Wiki,一个人工观察符自动编织系统
  • 第4章 RAG 检索增强生成全链路架构《AI Agent 开发平台资深技术专家 AI Agent 应用架构师 CTO 面试题库详解》
  • 生成式引擎优化(GEO)在酒店民宿行业的落地实践:对抗 OTA 流量截流
  • 智能合约开发中的威胁建模:代码生成前的安全基线构建
  • AI 编译优化入门:算子融合不是为了少写几行代码
  • Kiran Biometrics:开源生物识别认证系统的完整指南
  • ActiveReports for .NET 20.0J SP1-AIレポートウィザードがさらに進化
  • c++复习自存
  • Cursor Free VIP破解工具:3分钟解除AI编程助手试用限制的终极指南
  • 西安共享茶室平台开发?时段预约锁房技术源码讲解
  • 【小白也能轻松玩转龙虾】虾壳云一键部署入门攻略,分步搭建桌面端 OpenClaw v2.7.9(附最新安装包)
  • AI 辅助:独立创作:工具应放大作者,而不是替代作者
  • 后端开发者转型AI大模型的必备技能与实战指南
  • AI 辅助:少说漂亮话:基础设施要用事故假设来设计
  • 5个场景化解决方案:用taskt告别重复劳动,实现桌面自动化革命
  • Harness Engineering(驾驭工程)简单的演化过程
  • 那些与量子纠缠有关的物理概念和现象
  • “借道”MoP封装,AMD打破“存储墙”与“空间锁”
  • 2.4 中间层:底层驱动与标准库——固收与负债的“稳态输出”
  • 一张图讲清楚:MCP边界
  • 子任务想换个便宜模型跑?Sub-Agent 这样设计
  • 语音一键转文字超简单!2026多款免费软件详细步骤,新手一看就会
  • 开源视频生成模型选择
  • SpringBoot+Vue 私人西服定制_leabo管理平台源码【适合毕设/课设/学习】Java+MySQL
  • 用最新 GPT-5.6 润色论文是一种怎么样的体验?
  • 一张图讲清楚:Codex上下文
  • SPARK技术:5G/6G无线通信中的辐射模式压缩革命