表单(form) post 方式提交时的编码与乱码(上)

探讨了表单以 post 方式,enctype 为 application/x-www-form-urlencoded 提交时数据所使用的字符集编码,具体介绍了缺省情况以及设置了 accept-charset 属性时的情况,同时介绍了后台在取出表单数据前如何使用 setCharacterEncoding 来设置正确的解码。

目录
[隐藏]

在上一篇章中谈论了表单以 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 是一样的。比如,非字母数字的字符要用转义表示,键值对用等号隔开,多个键值对用“&”符号拼接。

对于要转义的字符,在编码的使用上,也是一样的:

  1. 没有用 accept-charset 指定,就用文档本身的编码;
  2. 设置了 accept-charset 的值,就用它设置的值。

稍微要注意的反而是后台的接收方面。

一个具体的例子

依旧是构建一个简单的 post 方式提交的表单:

表单 post 代码

然后提交到一个后台的 jsp 上,依旧是简单地取出显示:

表单 post 目标 jsp

提交后会发现这样的方式地址栏将不会再有“查询字符串(query component)”了:

表单 post 地址栏 乱码

但能注意到接收页面的显示是乱码的。此时查看请求头的信息:

表单 post 请求头 Form data

表单数据是正常的,点击 view source,发现确实是 utf-8 的转义编码:

表单 post 请求头 Form data view source

整体上也是 urlencoded 的方式,跟上篇说到的 get 一样,因为还是缺省的 application/x-www-form-urlencoded 方式的编码。

后台用 setCharacterEncoding 设置解码字符集

所以,整个发送环节是没有问题的,问题出在后台接收处理上。因为数据不是在 uri 中,自然之前的所谓设置 URIEncoding 是没有用了,这时需要不同的策略。

post 情况下,在取出数据前,就 java servlet 平台而言,需要用 request.setCharacterEncoding(“utf-8”) 先设置一下,编码的具体值与浏览器所用的一致即可,因为表单用的是 utf-8,这里也就设置为 utf-8,像这样:

表单 post 接收 request.setCharacterEncoding

调整之后,再测试就正常了:

表单 post 浏览器 测试 正常

假如没有作这一步骤的设置,那么 servlet 中将使用缺省的 iso-8859-1 来解码。

在某些情况下,如果你无法在这一层面进行调整,也可以保持按 iso-8859-1 的方式来接收,然后使用一种 hack 的方式来倒腾出正确的 string 的值,具体见文本在内存中的编码(3) 中的介绍,这里不再详述。

一般来说,除非不得已,不太建议使用 hack 的方式。

使用 accept-charset 属性指定 post 时的编码

假如表单页面增加 accept-charset 字段,值设为与文档本身编码不一样的 GBK:

表单 post accept-charset 属性

那么这样之后,提交的数据就是 GBK 编码了,不是 utf-8 编码的情况下调试工具中无法正常为字符:

表单 post accept-charset gbk 请求头

不过点击 view source,还是可以看到原始的值:

表单 post accept-charset gbk 请求头 view source

四个字节确实为 gbk 编码。

而后台的解码此时也会再一次出现问题:

表单 post accept-charset 与 setCharacterEncoding 不一致 乱码

因为提交数据的实际编码是 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 方法里设置一下编码:

filter doFilter setCharacterEncoding

更复杂完善一点的可以先看看 request 中是否已经设置过了 charset。许多的 MVC 框架中关于编码的设置其实就是着力在这里。关于 Filter 方面不了解的读者可以自行阅读 servlet 相关规范以了解。

当然了,这样之后,表单就不能用 gbk 编码方式来提交了。如果你有一些遗留的 gbk 页面,那么就要在其表单上设置 accept-charset=”utf-8”,指示它用 utf-8 来编码提交的数据。

注意:如果你不太确定这种统一的调整会造成哪些冲击,那么就要慎重地去处理,特别是在运行已久的遗留系统上,可能还存在前面提到的各种混乱的 hack 的处理方式,统一的调整可能会使得这样的代码失效。

这也是不提倡使用 hack 的原因,它代表了一种不太正确的处理方式,并给后续的调整带来了挑战,后来者往往只能“将错就错”,继续地 hack 下去。

那么,关于 post 方式以 application/x-www-form-urlencoded 的类型来提交时的编码与乱码问题,因为与之前的 get 方式提交还是很相似的,这里就简单介绍到这里。

发表评论

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