URL 中的编码与乱码(下)

深入介绍了 URL 中的转义编码,用具体例子讲解了不同页面编码的情况下,查询字符串转义时所使用的编码,还顺带对 url 的组成结构作了介绍。

目录

在上篇中,初步谈论了 URL 中含有中文字符时的转义编码,提到了所使用的编码是 utf-8.

不过你可能会有点疑问,一定都是用 utf-8 编码吗?还是因为页面编码本身是 utf-8 的缘故呢?毕竟在那个例子中,页面的编码也恰好是 utf-8。

在 GBK 编码页面下的 URL 转义

这次,将继续测试页面编码是 gbk 时的情况,如下:

gbk 编码页面 中文 url 转义 代码

打开时,是正常的:

gbk 编码页面 中文 url 浏览器测试

当然,你可能会想,打开前,还不知道页面的编码呢,那么事实上也是如此,此时请求的 url 依然是 utf-8 编码的:

gbk 编码页面 中文 url request header

那么继续点击其中的“你好/index.html”链接时呢?结果页面还是 OK 的:

中文路径 url 从 gbk 页面发出 浏览器测试

查看其发出的 url:

中文路径 url 从 gbk 页面发出 request header

尽管此时在 gbk 编码的页面发出此请求,但它的编码还是 utf-8.

那么是否就可以得出结论,即 url 中的编码始终是用 utf-8 呢?然而事情没有那么简单,正像你看到页面中还有一个链接,下面那个带有中文查询字符串的链接:

你好/index.html?s=你好

在 gbk 页面下点击它时是怎样的情况呢?在此之前,先要对 URL 中的结构做些了解。

URL 中的结构简介

多数的 URI 结构可以这样去划分:

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>

下面从一些例子中具体介绍各个部分的含义。一个具体例子:

https://xiaogd.net:443/?p=1699

其中:scheme(协议,方案)为 https;host(主机名,域名)为 xiaogd.net,端口(port)为 443;query(查询字符串)为 p=1699。

这是上一篇文章的一个短连接(short link),点击它会重定向到上一篇章中。

注:这个例子中,没有用户名,密码,没有路径(path)(也可以认为有一个路径,就是根路径“/”本身),没有 params,也没有 frag。https 协议的默认端口即为 443,通常可以省略。

其中查询字符串 query strings,uri 规范中的标准叫法为“query component(查询组件)”,用分隔符问号“?”与其它部分隔开,具体内容可以由多个键值对组成,中间由“&”符号分隔,键与值之间用分隔符等号“=”隔开:

如:http://localhost/foo?userid=9527&gender=male 中的查询字符串有两个键值对:

userid=9527 和 gender=male

另一个例子:

http://exp.xiaogd.net:80/demo/css/stroke-animate/stroke-animation.html

其中,路径 path 为 /demo/css/stroke-animate/stroke-animation.html。

注:http 协议的默认端口即为 80,通常可以省略;如果不是 80 就不能省略,如测试常用的 8080 端口,就要显式地在 url 中带上,这一端口来自于 web server 启动时所绑定(监听)的那个。

frag(fragments,片段,分段) 是页面内的链接(锚点),位于最后,用井号(#)跟其它部分隔开,严格地讲它不属于 url 的一部分,它的值通常即为页面内某个标题元素的 id。一个具体例子:

https://xiaogd.net/url-中的编码与乱码(上)/#tomcat 与 URIEncoding 设置

点击它与点击 https://xiaogd.net/url-中的编码与乱码(上)/ 都是跳到同一个页面,但它会滚动到页面内的某个元素下(通常为某个子标题),跟你打开这个页面单击那个目录下的标题类似:

url fragments 页内链接 示例

其实这个目录下的就是这种页面内的锚点(frag),分享一个比较长的页面时,这种方式能够更加精准地定位到页内的某个段落上,免得别人去翻找。

你可以亲自点击上述两个链接看看结果有什么不同!

其它的一些例子如:

ftp://anonymous@ftp.prep.ai.mit.edu/pub/gnu
ftp://anonymous:my_passwd@ftp.prep.ai.mit.edu/pub/gnu
http://joe:joespasswd@www.joes-hardware.com/sales_info.txt
ftp://prep.ai.mit.edu/pub/gnu;type=d
http://www.joes-hardware.com/hammers;sale=false/index.html;graphics=true

包含有其它一些协议,如 ftp;还有用户名,密码的例子,以及 param 的例子。

这些例子来自《Http 权威指南》一书中。

param 跟查询字符串 query 很类似,跟在 path 后面,用分号“;”与 path 隔开,但这种用法很罕见,反正我是没怎么见过用 param 的,一般都是用 query。

URL 查询字符串(query component)中的编码

这里主要关注 query,在明白它是怎么回事后,在 gbk 页面下点击带有中文查询字符串的链接时:

url 中文查询字符串链接 页面

结果是这样的:

URL 中文查询字符串 地址栏显示

会发现情况有不同了,地址栏中路径上的“你好”两字还能正确显示为中文,但后面的查询字符串中的“你好”,同样的字符此刻却显示为一串转义字符“%C4%E3%BA%C3”。

从四字节的编码“C4 E3 BA C3”来看,显然是 gbk 编码,而不是 utf-8 编码,因为对于 utf-8 而言,常见汉字一个字符至少也是三字节的编码,而现在是一个字符两字节,所以显然是 gbk 编码。

此刻去看请求头中的 url:

URL 中文查询字符串 request header

虽然同样是“你好”两字符,编码却出现了不同。在路径中是 utf-8 编码,在查询字符串中却是页面的编码,gbk。

而当在页面的编码是 utf-8 的页面中点击同样的链接字符串作跳转时,编码则是一致的:

URL 中文查询字符串 地址栏显示和 request header utf-8 编码

可以看到地址栏此刻都显示为“你好”,而请求头中的真实 url 两处的编码也确实是相同的。

综上,可以得出这样一个结论:URL 路径中的转义使用 utf-8 编码,但查询字符串中的转义却是跟随页面编码的。

在其它浏览器中的测试

在火狐浏览器(firefox)中的测试也能得出类似结论,这里不再列举。在微软的 Edge 和 IE(11)中测试则有些要注意的,即使是在 gbk 页面下点击,地址栏的显示始终是一致的:

edge 浏览器 中文url 路径及地址栏 显示

这个地址栏的值拷贝出来它也是字符形式,而不是转义的形式,这跟 chrome 等浏览器的行为又不一样。当用开发人员工具查看请求头时:

edge 浏览器 中文url request header

显示是混乱的,从某些字符来看,像是使用了同一种编码,但有的有转义,有的又没有。这个地方应该是它的调试工具本身的 bug。

如果在外面用 Fiddler 抓包工具查看,发现它其实还是跟 chrome,firefox 一样的,路径用 utf-8,查询字符串跟随页面编码:

fiddler edge 浏览器 中文url request header

IE 11 跟 Edge 的行为类似,而模拟一些更早的 IE 版本时,比如 IE 9,情况又有所不同:

fiddler ie11 浏览器 中文url request header

路径处还是 utf-8 的转义形式,但是查询字符串中显示一些乱码的字符,查看其十六进制形式时:

fiddler ie11 浏览器 中文url request header hexview

发现编码确实为 gbk,但它没有转义。当页面编码为 utf-8 时,查询字符串的编码也是 utf-8,但同样的,IE 9 不会自动帮我们把查询字符串转义。

更多的浏览器及版本下的行为,这里也无法一一去测试,读者如果碰到问题,可自行具体分析。

URL 编码使用的总结与建议

所以,总结来说,url 路径中的中文,浏览器会自动进行 utf-8 编码转义;查询字符串中,现代浏览器会使用页面编码来转义,而较早期的浏览器也会用页面编码,但不会为你转义,你要自己做转义。

另外,前面提到,server 端也要作相应处理,比如 tomcat 中你可能要配置 URIEncoding,而如果你的 url 中如果有多种编码方式共存,处理起来会非常棘手,甚至是不可能的。

如果页面要用 gbk 编码,那么 url 路径中就不要带有中文了,这时就查询字符串中有转义的 gbk 编码,可以避免出现两种编码,这时可在 URIEncoding 中设置为 gbk 来正确接收 url 。

如有可能,最好在所有环节使用 utf-8 编码,在需要转义的地方,手动地进行转义,如此放可保证在各种情况下不会出现问题。

关于 URL 中的编码与乱码的介绍就到这里,这里虽然也涉及到了用查询字符串向后端服务器传递参数,但暂时还没有在服务器端尝试接收这些参数,参数接收的话题将在后续的表单(form)提交时的编码与乱码中一起讨论。