JSP没有为HTML表单POST显示正确的UTF-8内容

Gar*_*son 5 java forms jsp tomcat servlets

我正在将Java 11与带有最新JSP / JSTL的Tomcat 9配合使用。我正在Windows 10的Chrome 71和Firefox 64.0中进行测试。我有以下测试文档:

<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en-US">
<head>
  <meta charset="UTF-8"/>
  <title>Hello</title>
</head>
<body>
  <c:if test="${not empty param.fullName}">
    <p>Hello, ${param.fullName}.</p>
  </c:if>

  <form>
    <div>
      <label>Full name: <input name="fullName" /></label>
    </div>
    <button>Say Hello</button>
  </form>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

这也许是最简单的形式。如您所知,表单method默认为get,表单action默认为""(提交到同一页面),表单enctype默认为application/x-www-form-urlencoded

如果我在字段中输入名称“FlávioJosé”(巴西著名的法罗歌手和音乐家)并提交,则通过将该表单通过HTTP提交GET到同一页面hello.jsp?fullName=Fl%C3%A1vio+Jos%C3%A9。这是正确的,并且页面上显示:

Hello, Flávio José.
Run Code Online (Sandbox Code Playgroud)

如果将表单更改methodpost并输入相同的名称“FlávioJosé”,则表单内容将通过提交POST,并带有HTTP请求内容:

fullName=Fl%C3%A1vio+Jos%C3%A9
Run Code Online (Sandbox Code Playgroud)

这似乎也是正确的。但是这次页面显示:

Hello, Flávio José.
Run Code Online (Sandbox Code Playgroud)

而不是看到 %C3%AJSP似乎视为UTF-8八位字节的序列,认为它们是一系列ISO-8859-1八位字节(或代码页1252八位字节),因此将它们解码为错误的字符序列。

但是它在哪里获得ISO-8859-1?我的JSP页面缺少什么指示正确的编码?

我还要注意,WHATWG规范指出,application/x-www-form-urlencoded默认情况下,八位字节应解析为UTF-8。Java servlet规范是否被简单破坏?我该如何解决?

Gar*_*son 6

这是Tomcat造成的,但根本问题是Java Servlet 4规范,该规范不正确且过时。

最初 HTML 4.0.1 说application/x-www-form-urlencoded编码的八位字节应该被解码为 US-ASCII。servlet 规范将此更改为,如果未指定请求编码,则八位字节应解码为 ISO-8859-1。Tomcat 只是遵循 servlet 规范。

Java servlet 规范有两个问题。首先是现代解释application/x-www-form-urlencoded是编码的八位字节应该使用 UTF-8 解码。第二个问题是将八位字节解码绑定到资源字符集会混淆两个级别的解码。

再看看这个POST内容:

fullName=Fl%C3%A1vio+Jos%C3%A9
Run Code Online (Sandbox Code Playgroud)

你会注意到它是 ASCII 码!!如果您将POSTHTTP 请求字符集视为ISO-8859-1, UTF-8, 或 -解码八位字节之前US-ASCII您仍然会得到完全相同的 Unicode 字符这并不重要!什么编码用于解码编码八位组是完全独立的。

再举一个例子,假设我下载了一个instructions.txt明确标记为 ISO-8859-1的文本文件,它包含 URI https://example.com/example.jsp?fullName=Fl%C3%A1vio+Jos%C3%A9。仅仅因为文本文件的字符集为ISO-8859-1,这是否意味着我需要%C3%A使用 ISO-8859-1进行解码?当然不是!用于解码 URI 字符的字符集是在资源内容类型字符集之上的单独解码级别!类似地application/x-www-form-urlencoded,无论资源的底层字符集如何,都应使用 UTF-8对编码的八位字节进行解码。

有几种解决方法,其中一些可以通过查看Tomcat 字符编码常见问题找到“到处使用 UTF-8”

在您的web.xml文件中设置请求字符编码。

将以下内容添加到您的WEB-INF/web.xml文件中:

<request-character-encoding>UTF-8</request-character-encoding>
Run Code Online (Sandbox Code Playgroud)

此设置与 servlet 容器实现无关,并在 servlet 规范中进行了定义。(conf/web.xml如果需要全局设置并且不介意更改 Tomcat 配置,您应该可以将其放入 Tomcat 的文件中。)

SetCharacterEncodingFilter在您的web.xml文件中设置。

Tomcat 有一个专有的等价物:org.apache.catalina.filters.SetCharacterEncodingFilterWEB-INF/web.xml文件中使用 ,如上面提到的 Tomcat 常见问题解答,以及如/sf/answers/2648378421/ 所示,摘录如下:

<filter>
  <filter-name>setCharacterEncodingFilter</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>setCharacterEncodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
Run Code Online (Sandbox Code Playgroud)

这将使您的 Web 应用程序只能在 Tomcat 上运行,因此最好将其放在 Tomcat 安装conf/web.xml文件中,如上面的帖子所述。其实Tomcat的conf/web.xml安装有这两个部分,只是注释掉了;只需取消注释它们就可以了。

在 JSP 或 servlet 中强制请求字符编码为 UTF-8。

您可以在 JSP 早期的某个地方将 servlet 请求的字符编码强制为 UTF-8:

<% request.setCharacterEncoding("UTF-8"); %>
Run Code Online (Sandbox Code Playgroud)

但这很丑陋、笨拙、容易出错,并且与现代最佳实践背道而驰——不应再使用 JSP 脚本。

希望我们可以得到一个更新的 Java servlet 规范来消除资源字符集和application/x-www-form-urlencoded八位字节解码之间的任何关系,并简单地声明application/x-www-form-urlencoded八位字节必须解码为 UTF-8,正如最新的 W3C 和 WHATWG 规范所阐明的现代实践一样。

更新:我已使用此信息更新了有关字符编码问题的 Tomcat 常见问题解答。