两种惯例的来历
空格在 URI 中不允许直接出现:按通用 URI 规则应写作 %20。但 HTML 表单的编码规则沿用了早期网络的惯例,用加号表示空格,因此服务端在解析查询串与表单体时通常把 + 当空格。差异的关键在于位置:查询串与表单体按表单编码解析,+ 表示空格;而路径部分不套用这条规则,路径里的 + 就是字面加号。混淆两者会得到「参数值里多出加号」或「路径匹配失败」这类问题。
基本信息
| %20 | 通用写法,任何组件中都表示空格 |
|---|---|
| + | 仅在表单编码上下文中表示空格(查询串、表单体) |
| 路径中的 + | 字面加号,不是空格 |
| 片段与其它组件 | 按通用规则使用 %20 |
| 编码函数差异 | quote 用 %20,quote_plus 用 +,encodeURIComponent 用 %20 |
| 签名场景 | 规范通常要求 %20,使用 + 会导致签名不一致 |
典型使用场景
- 表单提交:浏览器按表单规范把空格转成 +,服务端需按同规则解析
- 接口签名:签名前对参数值编码,空格用错会导致校验失败
- 路径生成:文件名或路径段含空格时应使用 %20 或直接用连字符替代
- 日志与抓包:把 + 误认为字面加号,导致对参数值的误判
- 跨端一致性:不同语言的默认编码函数对空格处理不同,需要显式指定
空格处理对比
空格处理对比
python -c "import urllib.parse as u; print(u.quote('a b'))" # a%20b:通用写法
python -c "import urllib.parse as u; print(u.quote_plus('a b'))" # a+b:表单写法
python -c "import urllib.parse as u; print(u.unquote_plus('a+b'))" # a b:表单解码
python -c "import urllib.parse as u; print(u.unquote('a+b'))" # a+b:通用解码不把 + 当空格
# 路径段:空格应编码为 %20;若含加号需编码为 %2B 以区别于空格语义
python -c "import urllib.parse as u; print(u.quote('/p/a b', safe='/'))"
# 签名场景固定使用 %20
python -c "import urllib.parse as u; print(u.quote('a b', safe=''))"位置与写法
| 位置 | 空格写法 | 注意事项 |
|---|---|---|
| 查询串参数值 | %20 或 + | 服务端按表单规则通常把 + 解析为空格 |
| 表单请求体 | %20 或 + | 内容类型为表单编码时按表单规则处理 |
| 路径段 | %20 | 路径里的 + 是字面加号 |
| 片段 | %20 | 通用规则,不套用表单惯例 |
| 字面空格 | 非法 | 会被截断或触发解析错误 |
选择编码函数要显式:同一语言里往往同时提供「通用」与「表单」两种风格,默认值不同。写代码时明确使用哪一种,比记住默认行为更可靠,尤其是在需要与外部规范对齐的签名场景。
空格写法影响签名校验:规范通常要求使用 %20,实现里若用 + 代替,服务端按规范重算时结果不同,表现为签名验证随机失败。遇到「多数请求正常、含空格参数失败」的现象,先查这里。
路径中的空格常被忽略:手工测试时用浏览器地址栏会自动处理,程序拼接时却可能原样写入,导致 404 或网关报错。路径参数建议用连字符等安全字符替代空格,从根源避免歧义。