开发技术分享

  • 文章分类
    • 技术
    • 工作
    • 相册
    • 杂谈
    • 未分类
  • 工具
    • AI 试衣
靡不有初,鲜克有终
[换一句]
  1. 首页
  2. 技术
  3. 正文

深究 URL encode

2022年1月3日 149点热度

一、一段陈年老代码

在读一份陈年老代码时,发现了下面这段有意思的东西,这段代码的目的,是将 web 请求的入参 url encode 之后,计算 md5 当做签名。

import java.net.URLEncoder;

encodedFix(URLEncoder.encode(value, "UTF-8"))

private static String encodedFix(String encoded) {

    encoded = encoded.replace("+", "%20");
    encoded = encoded.replace("*", "%2A");
    encoded = encoded.replace("%7E", "~");

    return encoded;
}

那么问题就来了,url encode 就 encode 呗,为什么还要再 encodedFix 修复一下呢?

为了探究这样写的原因,我们得看一看 url encode 的具体定义或者规范。事实上,url encode 并不是一个标准的正式名称,但它对应的编码规则来自 RFC 3986(统一资源标识符 URI 通用语法)和更早的 HTML form encoding(application/x-www-form-urlencoded) 规范。

二、URI

URI(Uniform Resource Identifier,统一资源标识符)是用来标识互联网上某一资源的字符串。它的主要目的是唯一标识某个资源,不一定能访问,但必须能区分。

URI 有两种主要形式:

类型 全称 举例 用途
URL Uniform Resource Locator https://example.com/page.html 既标识资源,也提供获取方式(协议)
URN Uniform Resource Name urn:isbn:978-3-16-148410-0 标识资源的“名字”,但不指明位置

URI 的通用结构为:

scheme:[//authority]path[?query][#fragment]

比如:

https://www.example.com:8080/path/to/page?lang=en#section2

RFC 3986 规范中明确的指出了,对于 URI 来说:

  1. 保留字符 - 通用分隔符(gen-delims): : / ? # [ ] @
  2. 保留字符 - 子分隔符(sub-delims):! $ & ' ( ) * + , ; =
  3. 非保留字符: 字母 数字 - . _ ~

保留字符 - 通用分隔符(gen-delims),主要用于分隔 URI 各个主要组成部分。

字符 用途举例
: 分隔 scheme 和其他部分,如 http:
/ 分隔路径层级
? 启动查询参数
# 启动 fragment
[ ] IPv6 地址
@ 用户信息与主机的分隔符,如 user:pass@example.com

保留字符 - 子分隔符(sub-delims),则用于细化每个部分内部的结构,比如 query 参数的键值对、列表项之间的分隔等。这些字符的具体用途并不是 RFC 3986 固定规定的,而是留给具体 URI 的使用场景去定义,像 HTTP、OAuth、HTML 表单 等场景,均可以有自己的设定。

这些保留字符在是否要编码取决于它们的作用,如果用作语法结构就不能被编码,否则 URI 就被破坏了。如果这些字符是普通数据的一部分,就必须编码,避免歧义。

三. URL encode

urlencode 是一种对 URL 中的字符串进行编码的方式,目的是确保 URL 在传输时不会因特殊字符而出错。常用于:

  1. 将数据作为查询字符串(query string)附加到 URL 后面
  2. 表单提交(application/x-www-form-urlencoded)

针对第一种场景(url 参数),因为 URL 是 URI 的子集,因此,也适用 RFC3986 规范,即非保留字符出现在 key/value 中,保持不变,而保留字符如果出现在 key/value 中,则都需要编码

针对第二种场景(表单提交),则与 RFC 3986 不完全一致,它的额外规则:空格 被转换成 + (而不是 %20)

四、不同编程语言的表现

在不同的编程语言中,对 url encode 有着不同的实现(字母和数字都是完全一样的,这里就不列出来了):

原字符 java urllib.parse.quote(xxx, safe='/') safe参数的默认取值是 '/' urllib.parse.quote(xxx, safe='') urllib.parse.urlencode encodeURI encodeURIComponent
: %3A %3A %3A %3A : %3A
/ %2F / %2F %2F / %2F
? %3F %3F %3F %3F ? %3F
# %23 %23 %23 %23 # %23
[ %5B %5B %5B %5B %5B %5B
] %5D %5D %5D %5D %5D %5D
@ %40 %40 %40 %40 @ %40
! %21 %21 %21 %21 ! !
$ %24 %24 %24 %24 $ %24
& %26 %26 %26 %26 & %26
' %27 %27 %27 %27 ' '
( %28 %28 %28 %28 ( (
) %29 %29 %29 %29 ) )
* * %2A %2A %2A * *
+ %2B %2B %2B %2B + %2B
, %2C %2C %2C %2C , %2C
; %3B %3B %3B %3B ; %3B
= %3D %3D %3D %3D = %3D
- - - - - - -
. . . . . . .
_ _ _ _ _ _ _
~ %7E ~ ~ ~ ~ ~
空格 + %20 %20 + %20 %20
制表符 \t %09 %09 %09 %09 %09 %09
换行符 \n %0A %0A %0A %0A %0A %0A

我们可以看到,urllib.parse.quote(xxx, safe='') 的结果和 RFC3986 的要求是一致的。

而 java.net.URLEncoder 的处理结果,和 RFC3986 中要求的并不完全一致:

  1. 空格 被处理成了 +,而不是 %20
  2. * 没有按要求进行处理。
  3. ~ 应该不变,但是被处理成了 %7E

这下就清晰了,陈年代码中的 encodedFix 其实是为了让 java.net.URLEncoder 的处理结果,和 RFC3986 保持一致:

import java.net.URLEncoder;

encodedFix(URLEncoder.encode(value, "UTF-8"))

private static String encodedFix(String encoded) {

    // 表单中的空格,被转换成 + 了,需要按照 RFC3986 转为 %20
    encoded = encoded.replace("+", "%20");

    // URLEncoder 没有处理 *,需要额外处理下
    encoded = encoded.replace("*", "%2A");

    // URLEncoder 编码了 ~,但它属于非保留字符,不需要编码,所以要还原回去
    encoded = encoded.replace("%7E", "~");

    return encoded;
}
标签: 暂无
最后更新:2025年6月6日

zt52875287

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >
文章目录
  • 一、一段陈年老代码
  • 二、URI
  • 三. URL encode
  • 四、不同编程语言的表现

Copyright © by zt52875287@gmail.com All Rights Reserved.

Theme Kratos Made By Seaton Jiang

陕ICP备2021009385号-1