背景
Web 项目中经常会遇到处理 URL 中 Query 的情况,来看下下面问题你有疑惑吗?
项目中发现会用到 qs、query-string、URLSearchParams、甚至querystring几种不同的库,其到底差异在哪里,我该用哪个?在 query 中 key=a&key=b 这种情况 key 取值是什么?和 key[]=a&key[]=b 有区别嘛? 在 query 中会有结构如 %HH 的数据,为什么是这样形式的?我们为什么要使用 encodeURIComponent 进行编码?和过时的 escape 又有何区别? Content-type中x-www-form-urlencoded的取值,是怎么一回事?
于是梳理一下关于 URL Query 的相关知识点,用来去伪解惑。
URL QueryString
首先介绍下 Query String 的基本概念,这是一切问题的开始。下面是 wiki[1] 的描述:
A query string is a part of a uniform resource locator[2] (URL) that assigns values to specified parameters.
通常的理解就是 URL 中问号(?)后面的部分,其设计最初是用做 HTML form 表单提交时的传参。
基本结构
下面我们看下 query 的基本结构 field1=value1&field2=value2&field3=value3...
包含了如下标准:
Query String 由一组键值对( field-value)组成;每组键值对的 field和value用=分割;每组数据用 &分割;
补充个冷知识:除了使用&分割每对数据外,W3C 曾在 1999 年建议所有 Web 服务器同时支持分号;分割符:
We recommend that HTTP server implementors, and in particular, CGI implementors support the use of ";" in place of "&" to save authors the trouble of escaping "&" characters in this manner.
但在 2014 年以来,就只建议使用 & 作为分隔符了。也就目前我们用到的方式。
允许多个 value被关联到同一个field上,但field如何取值,其实并无明确的处理标准。
例如:field=a&field=b时,field 的值应该是 a、b、['a', 'b']、'a, b' 并无任何权威解释。
关于处理标准这点实在令人出乎意料。通常这类情况会按照数组的方式处理,即 field 值为 ['a', 'b'],但这仅是不同的框架的决定了如何实现而已。
关于这个问题可以前往 stackoverflow[3] 上查看。
数据编码
前面定义好了整体结构,接下来我们看下数据是如何在 query 中传输的。
由于某些字符集(如中文)和在 URL 中有特殊含义的字符(如 空格、%、&、=、?、# 等)无法直接在 Query String 中使用,因此使用了一种叫做「百分号编码[4] Percent-encoding[5]」的方式先将这类特殊字符进行编码后,再进行传输。
其基本结构就是 % + 2 个 16 进制数字(一个 Byte 的内容),范围 %00 - %FF。
具体规则如下:
对保留字符进行编码,具体对应如下:
| ! | # | $ | & | ' | ( | ) | * | + | , | / | : | ; | = | ? | @ | [ | ] |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| %21 | %23 | %24 | %26 | %27 | %28 | %29 | %2A | %2B | %2C | %2F | %3A | %3B | %3D | %3F | %40 | %5B | %5D |
其对应的就是这些字符的 ASCII 编码的 16 进制格式;
如下非保留字符不进行编码,包含:
[A-Z]、[a-z]、[0-9]、-、_、.、~;%百分号编码为%25;空格编码为
+或%20;其余字符数据使用某种编码方式转换为字节流,再用百分号编码
%HH方式表示。
这里需要注意的是,由于早期规范中未明确应使用何种编码,所以会导致如果不明确说明使用何种编码,数据的解析会有歧义。因此在 2005 年发布的 RFC 3986 [6]建议是先转成 UTF-8 编码,再对每个字节进行%HH的编码。
注意,如果使用 from 表单 action 方式时,具体编码会根据 meta 头的 charset 的选择。
当然上述只是标准,实践中 JavaScript 内置了使用 UTF-8 编码的 encodeURI/encodeURIComponent 函数,大大简化的编码过程。
关于指定编码,这里有个有趣的事情:
在使用百度时,你会发现 URL 中有个 ie 参数,其实含义就是 Input Encoding(对,不是 IE 浏览器),目的就是指定关键词 wd 的编码格式。曾默认是 GB2312(因为当时很多网站还使用 GB2312 编码),当然现在已经默认成 UTF-8 。(不过百度结果里依然有不少文章还在说 ie 的默认值是 GB2312