在上一篇章中谈论了表单以 get 提交时的编码与乱码问题,这一章中将讨论以 post 方式提交时的编码与乱码问题。
在前面也同时提到,表单有一个叫 enctype 的属性,它有两个值,application/x-www-form-urlencoded 和 multipart/form-data。这一属性实际只对 post 方式起作用,因为 get 方式实际只支持前一种类型,也就是 application/x-www-form-urlencoded,这是缺省的类型。
在使用 post 方式提交时,缺省的编码类型也依然是这个 application/x-www-form-urlencoded。在这一篇章中,先讨论这一类型;在下篇中,再讨论 multipart/form-data 类型。
post 方式,application/x-www-form-urlencoded 类型的编码
对于 application/x-www-form-urlencoded 类型,前面讨论 get 的方式已经详细讨论过了,post 方式用它,底层原理依旧是相同的,只不过它的数据此时不是放在 uri 中,而是在消息体中发送过去。
简单地讲,就是地址栏中是看不出来的。
发送形式有差别,但在这里关心的编码方式上,却与 get 是一样的。比如,非字母数字的字符要用转义表示,键值对用等号隔开,多个键值对用“&”符号拼接。
对于要转义的字符,在编码的使用上,也是一样的:
- 没有用 accept-charset 指定,就用文档本身的编码;
- 设置了 accept-charset 的值,就用它设置的值。
稍微要注意的反而是后台的接收方面。
一个具体的例子
依旧是构建一个简单的 post 方式提交的表单:
然后提交到一个后台的 jsp 上,依旧是简单地取出显示:
提交后会发现这样的方式地址栏将不会再有“查询字符串(query component)”了:
但能注意到接收页面的显示是乱码的。此时查看请求头的信息:
表单数据是正常的,点击 view source,发现确实是 utf-8 的转义编码:
整体上也是 urlencoded 的方式,跟上篇说到的 get 一样,因为还是缺省的 application/x-www-form-urlencoded 方式的编码。
后台用 setCharacterEncoding 设置解码字符集
所以,整个发送环节是没有问题的,问题出在后台接收处理上。因为数据不是在 uri 中,自然之前的所谓设置 URIEncoding 是没有用了,这时需要不同的策略。
post 情况下,在取出数据前,就 java servlet 平台而言,需要用 request.setCharacterEncoding("utf-8") 先设置一下,编码的具体值与浏览器所用的一致即可,因为表单用的是 utf-8,这里也就设置为 utf-8,像这样:
调整之后,再测试就正常了:
假如没有作这一步骤的设置,那么 servlet 中将使用缺省的 iso-8859-1 来解码。
在某些情况下,如果你无法在这一层面进行调整,也可以保持按 iso-8859-1 的方式来接收,然后使用一种 hack 的方式来倒腾出正确的 string 的值,具体见文本在内存中的编码(3) 中的介绍,这里不再详述。
一般来说,除非不得已,不太建议使用 hack 的方式。
使用 accept-charset 属性指定 post 时的编码
假如表单页面增加 accept-charset 字段,值设为与文档本身编码不一样的 GBK:
那么这样之后,提交的数据就是 GBK 编码了,不是 utf-8 编码的情况下调试工具中无法正常为字符:
不过点击 view source,还是可以看到原始的值:
四个字节确实为 gbk 编码。
而后台的解码此时也会再一次出现问题:
因为提交数据的实际编码是 gbk,而后台代码却还是按 utf-8 来解码,所以就出错了。而调整方式也很简单,就是把 request.setCharacterEncoding("utf-8") 改为 request.setCharacterEncoding("gbk") 即可,改完后的代码及具体测试这里就不再列出了,读者可以自行实验。
总之,一句话,编码与解码两端要在使用的字符集编码上保持一致即可。
配置 filter 统一解码时所用的字符集
如果不想在每次接收前都来调用 request.setCharacterEncoding 这么设置一下,可以统一配置一个过滤器,以 tomcat8 为例,可以这样在 web.xml 中去增加一个 filter:
<filter>
<filter-name>Character Encoding Filter</filter-name>
<filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Character Encoding Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这样就统一设置为使用 utf-8 来解码了。
如果你不是使用 tomcat,或者 tomcat 的版本太旧不包含上述 filter,那么也可以自行定义一个类似的 filter,原理上简单讲就是在 doFilter 方法里设置一下编码:
更复杂完善一点的可以先看看 request 中是否已经设置过了 charset。许多的 MVC 框架中关于编码的设置其实就是着力在这里。关于 Filter 方面不了解的读者可以自行阅读 servlet 相关规范以了解。
当然了,这样之后,表单就不能用 gbk 编码方式来提交了。如果你有一些遗留的 gbk 页面,那么就要在其表单上设置 accept-charset=”utf-8”,指示它用 utf-8 来编码提交的数据。
注意:如果你不太确定这种统一的调整会造成哪些冲击,那么就要慎重地去处理,特别是在运行已久的遗留系统上,可能还存在前面提到的各种混乱的 hack 的处理方式,统一的调整可能会使得这样的代码失效。
这也是不提倡使用 hack 的原因,它代表了一种不太正确的处理方式,并给后续的调整带来了挑战,后来者往往只能“将错就错”,继续地 hack 下去。
那么,关于 post 方式以 application/x-www-form-urlencoded 的类型来提交时的编码与乱码问题,因为与之前的 get 方式提交还是很相似的,这里就简单介绍到这里。