URL 参数为什么被序列化了两次?一次 IOS 与浏览器标准差异的排查

常见技术问题

刘宇帅 878 0 6月前

文章内容

在我们 iOS 客户端的开发中,有一个长期困扰的问题:部分链接打开后,URL 里的参数被序列化了两次

比如一个原始链接是这样的:

/path?fff[]=jjj ll

结果在 iOS 里打开后,变成了:

/path?fff%255B%255D=jjj%2520ll

注意看:

  • %5B 又变成了 %255B
  • %20 又变成了 %2520

也就是说,参数被二次序列化了。这个问题追了很久,直到最近才找到真正的原因。


1. 背景差异:iOS vs 浏览器

  • iOS 标准库 中,[] 被认为是 需要编码的特殊字符。 所以 fff[] 会被序列化成 fff%5B%5D

  • 而在 浏览器标准 (WHATWG URL) 里,查询串部分允许保留 [] 不编码,因为很多 Web 框架(例如 PHP)习惯用 ids[] 这种形式传数组。 所以 <a> 标签或 document.createElement("a") 得到的结果里,[] 会原样保留。

这就造成了一个差异:

  • 浏览器里:fff[]=jjj llfff[]=jjj%20ll(空格被转义,[] 保留)
  • iOS 里:fff[]=jjj llfff%5B%5D=jjj%20ll(空格和 [] 都被转义)

2. 为什么会出现二次序列化?

当一个 URL 在不同环境下被“来回处理”时:

  1. 浏览器环境里,[] 没有被编码
  2. 传给 iOS 处理时,iOS 认为 [] 必须编码 → %5B%5D
  3. 但如果开发者又手动调用了一次编码方法,就会对 %5B%5D 再做一次序列化 → %255B%255D

这样,参数就被双重编码了。


3. 复现代码对比

在浏览器里:

var a = document.createElement("a");
a.href = "/path?fff[]=jjj ll";
console.log(a.search);
// 输出: ?fff[]=jjj%20ll

在 iOS(Swift)里:

let url = URL(string: "/path?fff[]=jjj ll", relativeTo: baseURL)!
print(url.query ?? "")
// 输出: fff%5B%5D=jjj%20ll

可以清楚看到差异。


4. 解决方案

最小改动方案

在前端解析函数里,强制把 [] 也转义,保证跟 iOS 保持一致:

query: a.search
  .replace(/ /g, "%20")
  .replace(/\[/g, "%5B")
  .replace(/\]/g, "%5D")

这样就不会出现“浏览器保留 [],iOS 又去二次编码”的情况。


推荐方案

使用标准化的 URL + URLSearchParams,而不是自己手写 split:

const u = new URL("/path?fff[]=jjj ll", location.origin);
console.log(u.search);  // "?fff%5B%5D=jjj+ll"
console.log(u.searchParams.get("fff[]")); // "jjj ll"

这能确保在所有环境下,参数都被一致地编码。


5. 总结

这个问题折腾了我们很久,直到深入比对 iOS 与浏览器的标准才找到原因:

  • 浏览器 a 标签[] 不强制序列化
  • iOS 标准库[] 强制序列化

于是跨环境传递 URL 时,很容易出现“二次序列化”的问题。

✅ 最佳实践:

  • 要么在生成 URL 时统一转义规则,显式 encode []
  • 要么在前后端约定好统一的参数序列化方案(推荐用 URLSearchParams

这样,就能避免再掉进这个双重编码的坑。

专栏信息

常见技术问题
作者 刘宇帅
发布时间 6月前
阅读量 878
评论数 0

摘要


暂无评论~

更多专栏文章

idea 提示 the file size exceeds the configured limit .Code insight features are not avaliable

在 IntelliJ IDEA 中,提示 “the file size exceeds the configured limit. Code insight features are not available” 表示当前文件的大小超出了 IDEA 的默认限制,因此无法启用代码自动提示、语法高亮等功能。默认文件大小限制为 2.5 MB。 解决方法 方

查看文章

ProxySQL详解

ProxySQL 是一个高性能、高可用性的 MySQL 代理,旨在为 MySQL 数据库提供负载均衡、读写分离、故障转移、查询缓存等高级功能。它通过在客户端和 MySQL 服务器之间充当中间层,实现对数据库连接和查询的智能管理,从而提升整体系统的性能和可靠性。

查看文章

git仓库迁移方法

迁移 Git 仓库是一个常见需求,尤其是在更换代码托管平台(如从 GitHub 迁移到 GitLab,或从本地仓库迁移到远程仓库)时。以下是几种常见的 Git 仓库迁移方法,具体使用哪种方法取决于你需要保留的内容(如历史记录、分支、

查看文章

Base64详解

Base64 是一种常用的编码方式,用于将二进制数据转换为ASCII字符,以便在文本环境中安全地传输和存储。由于许多传输媒介(如电子邮件、HTTP协议)对二进制数据有特定的限制或处理方式,Base64提供了一种可靠的方法来确保数据在这些环境中的完整性和可读性。

查看文章

Maven setting.xml 详解

一、什么是 settings.xml settings.xml 是 Maven 的配置文件,用于定义用户级别或全局的构建配置。它包含了对 Maven 构建过程影响较大的设置,如: 本地仓库的位置 远程仓库的镜像 代理服务器配置 认证信息(如私有仓库的用户名和

查看文章