URL 中的编码与乱码(上)

深入介绍了 URL 中的转义编码,用具体例子讲解了中文 URL 中的转义情况,以及 tomcat Connector 中的 URIEncoding 设置。

目录
[隐藏]

在之前说完了静态 html 页面的中的编码(),接着又谈论了动态 html 页面中的编码问题,具体以 java 平台为例,谈论了 servlet 中的编码与乱码问题以及 jsp 中的编码与乱码问题

虽然没有涉及更多的语言平台,比如 php,asp,乃至 nodejs,python,ruby 等,但背后的原理基本也是相通的。

这一次将转入一个新的话题,就是 URL 中的编码与乱码问题。

带有中文的 URL

我们依然从一些简单的实验开始去探讨,而不是直接给出一些结论。先创建两个有着中文文件名的 html,页面编码分别是 utf-8 和 gbk;以及一个中文名的文件夹,下面再放一个普通的 html 文件,如下所示:

中文 url 文件名 路径名

自然,你可能听说过很多前辈们的警告:千万别用中文作文件名或路径名(文件夹名)。这种警告应该说是中肯的,但也不意味着用了中文就一定有问题。

前面三个文件的内容如下:

中文 url 文件 html 内容

在前面两个文件中还使用了一个 a 标签的超链接,href 中的内容也是包含有中文的:

<a href=”你好/index.html”>

有的还带有中文的查询字符串:

<a href=”你好/index.html?s=你好”>

把以上部署到 tomcat8 中,然后在 chrome 中访问【主页utf8.html】 文件:

浏览器 中文 url  访问

首先,打开是没有问题的,地址栏的链接也是中文,不过地址栏这里浏览器为了对用户友好,实际上使用了一点障眼法。如果你把地址栏的内容拷贝出来,粘贴到一个文本文件上,会发现实质的内容是这样的:

中文 url  转义表示

又或者你打开“开发者工具”,在其中的请求头标签页下查看:

中文 url  请求头转义表示

会发现【主页】两个汉字被取代了,真正的内容是“%E4%B8%BB%E9%A1%B5”这样一串字符。

URL 中的转义表示

那么,像这样的一串字符“%E4%B8%BB%E9%A1%B5”,其中的每一个部分用【%XX】来表示,其中 XX 表示一个十六进制的数(hexadecimal digits),这样的表示就是所谓的“URL 的转义表示”,也叫“百分号编码”(Percent-Encoding)。

如果把其中的百分号 % 去掉,会发现结果是“E4 B8 BB E9 A1 B5”,总共 6 个字节,其实就是“主页”两字的 utf-8 编码。

如果你还记得先前说到的 utf-8 的编码模式EX XX XX 通常就是常用汉字的模式。

那么现在比较清楚了,URL 路径中的中文需要转义,具体编码用的是 utf-8。

为什么 URL 中中文需要转义

你可能会想,为什么 URL 中的中文要转义呢?转成那样一串东西,猛看上去也不知道它到底代表什么汉字,怪怪的。那么这个原因简单讲就是 URL 中的规范就是这么规定的。

更准确地说是 URI 规范。URL 是 URI 的一种,常见的 URI 通常也都是 URL,一般情况下会混着用两者。

关于 URI 规范,具体可见 http://www.ietf.org/rfc/rfc2396.txthttp://www.ietf.org/rfc/rfc3986.txt。(后面的为更新的规范)

URI 规范中对于转义的规定

考虑到 URI 在各种平台间传输时的兼容性,URI 规范中规定只有 US-ASCII 字符集中的字符可以直接出现在 URI 中。事实上,甚至 ASCII 本身的许多字符也不允许直接出现在 URI 中,有的也要转义。

可以直接用的有 26 个大小写字母,10 个数字,还有以下四个标点符号:

“-” / “.” / “_” / “~”(具体为“短横”、“点号”、“下划线”、“波浪线”)

其它的有的属于保留字,用作为分隔符(delimiters),它们有:

“:” / “/” / “?” / “#” / “[” / “]” / “@” / “!” / “$” / “&” / “‘” / “(” / “)” / “*” / “+” / “,” / “;” / “=”

比如正斜杠“/”就是一个最常用的分隔符。如果想在 URL 中包含这样符号,又不想它们被解析为分隔符,就要对其转义。

分隔符还具体分为通用分隔符和子部件分隔符,具体什么时候需要转义的细节参见上述提到的规范。

其它的还有一些不可以打印的字符也要转义,比如“空格”等。

所以,URI 中可用的字符实际是比较有限的,最好只使用那些安全的字符,就是 26 个字母加数字。

如果用了其它,就免不了要转义,然后各种编码,解码,通过各种网关,代理,各种转发过程时等也容易发生错误。

浏览器对中文路径 URL 的转义

比如上面说到的中文,那就要转义,虽然在 href 中直接写的是中文,但正规的浏览器都会对其先转义然后才发送请求。

比如,在上述页面中,继续点击其中的链接“你好/index.html”:

浏览器访问中文 url 路径

就进入了那个中文文件夹路径下,具体查看一下请求,发现也是转义了的:

中文 url 路径 请求头 转义表示

虽然在 href 中是直接写的中文,但浏览器悄悄地做了转义。

当然你也可以自己先行转义好放在 href 那,具体请参考 urlEncode 等方法,这样浏览器就不会再转了。

tomcat 与 URIEncoding 设置

另一方面,除了浏览器这边的转义外,server 端也要能正确地解码。其实这里把它部署到 tomcat8 中,没有出现问题,但假如是部署到 tomcat7 中,再请求时会发现 404 错误,找不到文件了:

tomcat6 tomcat7 中文 url 找不到文件 404

请求的 url 还是一样的,也是转义的了,但是问题出在 tomcat7 接受到这个转义 url 的处理上,原来它默认是按 iso-8859-1 来解码的,导致无法正确地得到“主页utf8.html”,因此就出错了。

为此,还要去到 server 的配置文件目录下,找到 server.xml:

tomcat server.xml

再在 server.xml 文件中找到端口为 8080 的那个 Connector,增加一个设置 URIEncoding=”UTF-8″,这样之后才能正常:

tomcat connector port 8080 URIEncoding utf-8

也即这样:

<Connector connectionTimeout=”20000″ port=”8080″ protocol=”HTTP/1.1″ redirectPort=”8443″ URIEncoding=”UTF-8″/>

那为什么 tomcat8 又 OK 呢?原来从 8 开始(具体为 8.0.0-RC3),这个默认值改为了 utf-8,于是就不用你自己去设置了。具体见:https://wiki.apache.org/tomcat/FAQ/CharacterEncoding#Q2

其实严格地讲,还会受一个叫“strict servlet compliance”的设置的影响,这个设置默认是 off,这时的值就是 utf-8;而假如为 on 的话,表明严格地遵循 servlet,此时即便在 tomcat8 下也是 iso-8859-1,那么就也要手动配置这个值。

如果你用的不是 tomcat,那么你要自行查阅资料了解缺省值的配置。

由于篇幅关系,涉及到 gbk 页面下的测试及查询字符串相关的一些情况留待下篇再分析。

发表评论

电子邮件地址不会被公开。