URL之get、post 乱码处理
Web 开发常见 get、post 乱码问题。
只要保证 Client 和 Server 各自使用的字符集在编码、解码时一致即可。
URL
URL 技术简单成一个字符串(实际上 URL 的结构非常复杂,只是我们使用的简单而已)。
更多可参考 每个 Web 开发者都应该知道的关于 URL 编码的知识
Part | Data | Server API |
---|---|---|
Scheme | http/https | request.getScheme() |
user | bob | |
pass | bobby | |
host address | www.oschina.net | request.getServerName() |
port | 80/443 | request.getServerPort() |
path | /file | request.getServletPath() |
query parameters | q=2 | request.getQueryString()、request.getAttribute() |
fragement | 100 |
path 是应用路径部分,是 URL 去掉协议、域名、端口和查询参数剩下的部分。
query parameters 是 path 后紧跟的 ? 后面的部分,用 & 连接各个查询参数。
path 和 query parameters 不一样,有些浏览器对查询參数的编码和path部分的编码是不一致的。
fragment 信息片断。字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释(也称为锚点)。
详细参考关于 URL 编码
依据上面的文章总结的规律(这个文章较老了,有些规律可能已经不用了,但也说明了一定问题):
- path 部分或者说除查询参数外的 URL 部分,各浏览器用UTF-8编码;
- 查询參数,各浏览器依据操作系统编码决定;
Tomcat
一般,请求都是先发给Web容器(以 Tomcat 为例),URL 会被 Web 容器解码。
Tomcat 容器在config/server.xml
的 connector 标签中设定解码配置。
- Tomcat 8 以下(默认ISO-8859-1)
默认配置:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
- Tomcat 8 及以上(默认UTF-8)
解码方式依赖 connector 标签的”strict servlet compliance”值,默认 off 时使用 UTF-8 解码,如果打开的话,使用 ISO-8859-1 解码。
解决
- 方案一:指定 Tomcat 解码方式(如UTF-8)
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="utf-8" />
- 方案二:使用Http Header中指定的charset解码
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" useBodyEncodingForURI="true" />
-
使用http header中指定charset进行decode(例如:Content-Type: charset=UTF-8)。
-
指定方法:
Html4 页面:<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
Html5 页面:<meta charset="UTF-8">
或
Controller层:request.setCharacterEncoding("UTF-8");
- 方案三(建议):手动转化
如果 Tomcat 容器里不止一个应用时,我们可以编码设置。
String _keys = request.getParameter("keys") != null ? request.getParameter("keys").toString() : "";
String keys = new String(_keys.getBytes("ISO8859-1", "UTF-8"));
总结:对于上面三种解决方法,要保证 Web 容器对 URL 的编码是用的 ISO8859-1,如果是人为因素或容器配置因素等造成的其他编码则不适用。
定位浏览器对 query parameters 的编码方式
- 调试工具
利用调试工具找到 URL 里中文对应的位置(如%E7%BC%96%E7%A0%81)。
方法一:使用工具 Atool、站长工具等工具解码出对应的中文是“编码”。
方法二:去掉%得到6个十六进制数,对比 Unicode 码表看出来是“编码”。
不要通过浏览器的地址栏看URL编码,很多浏览器的地址栏会对 URL 解码显示。
使用与编码一致的解码方式
- 找出浏览器对 URL 的编码方式;
- 使用一致的解码方式解码;
【解决】里的三种方法任选其一。
扩展
编码:ISO8859-1(别名Latin1,有时写Latin-1)
单字节编码,其编码范围(0x00-0xFF)使用了单字节内的所有空间。向下兼容ASCII(0x00-0x7F 之间与 ASCII 完全一致,0x80-0x9F 之间是控制字符,0xA0-0xFF 之间是文字符号。ASCII 编码是一个 7 位的容器,ISO-8859-1 编码是一个 8 位的容器。)
正因为 ISO-8859-1 编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。
ISO-8859-1 收录的字符除 ASCII 收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在 ISO-8859-1 当中。
HTML 4.01 支持 ISO 8859-1 (Latin-1) 字符集。
ISO-8859-1 的较低部分(从 1 到 127 之间的代码)是最初的 7 bit ASCII。
ISO-8859-1 的较高部分(从 160 到 255 之间的代码)全都有实体名称的(实体名称对大小写敏感)。
JavaScript 编码解码的三种方式
- escape 和 unescape
对字符串编码(区别与另外两种对 URL 编码的函数),作用是让字符串能在所有电脑上可读。 - encodeURI 和 decodeURI
Javascript 中真正用来对 URL 编码的函数。 - encodeURIComponent 和 decodeURIComponent
对 URL 的组成部分进行个别编码。常用于对 URL 参数所有部分进行编码。
encodeURI 和 encodeURIComponent 的比较
encodeURI 和 encodeURIComponent 都是 ECMA-262 标准中定义的函数,所有兼容这个标准的语言(如JavaScript, ActionScript)都会实现这两个函数。它们都是用来对URI (RFC-2396)字符串进行编码的全局函数,但是它们的处理方式和使用场景有所不同。为了解释它们的不同,我们首先需要理解RFC-2396中对于URI中的字符分类:
- 保留字符(reserved characters):URI 中的保留关键字符,用于分割 URI 各部分。
“;” | “/” | “?” | “:” | “@” | “&” | “=” | “+” | “$” | “,” - Mark字符(mark characters):在RFC-2396中特别定义,但没有特别说明用途,可能是和别的RFC标准相关。
“-” | “_” | “.” | “!” | “~” | “*” | “’” | “(” | “)” - 基本字符(alphanum characters):URI 中的主体部分,包括所有的大写字母、小写字母和数字。
encodeURI:该函数对传入字符串中的所有非(基本字符、Mark字符和保留字符)进行转义编码。所有的需要转义的字符都按照UTF-8编码转化成为一个、两个或者三个字节的十六进制转义字符(%xx)。例如,字符空格”“转换成为”%20”。在这种编码模式下面,需要编码的ASCII字符用一个字节转义字符代替,在\u0080和\u007ff之间的字符用两个字节转义字符代替,其他16为Unicode字符用三个字节转义字符代替。
encodeURIComponent: 该函数处理方式和encodeURI只有一个不同点,那就是对于保留字符同样做转义编码。例如,字符”:”被转义字符”%3A”代替。
CSDN 网站搜索“学习”效果对比:
escape("http://so.csdn.net/so/search/s.do?q=学习");
"http%3A//so.csdn.net/so/search/s.do%3Fq%3D%u5B66%u4E60"
encodeURI("http://so.csdn.net/so/search/s.do?q=学习")
"http://so.csdn.net/so/search/s.do?q=%E5%AD%A6%E4%B9%A0"
encodeURIComponent("http://so.csdn.net/so/search/s.do?q=学习")
"http%3A%2F%2Fso.csdn.net%2Fso%2Fsearch%2Fs.do%3Fq%3D%E5%AD%A6%E4%B9%A0"
之所以有这两个不同函数,完全是因为 JS 代码对 URI 的两种不同编码需求。
如“http://www.mysite.com/send-to-friend.aspx?url=http://www.mysite.com/product.html”
在这个 URI 字符串中。send-to-friend.aspx 页面会创建 HTML 格式的邮件内容,里面会包含一个链接,这个链接的地址就是上面 URI 字符串中的url值。显然上面的 url 值是 URI 中的一个部分,里面包含了 URI 保留关键字符。我们必须调用 encodeURIComponent对它进行编码后使用,否则上面的 URI 字符串会被浏览器认为是一个无效的 URI 。