我有一个在Tomcat9下运行的Java servlet,作为正常流的一部分,它会多次调用HttpServletResponse#setStatus()
。
当在带有Java 8(1.8.0u144,Tomcat报告为1.8.0_144-b01
(的Tomcat 9.0.0.M26上运行时,这可以很好地工作。
当在带有Java 10.0.1的Tomcat 9.0.8.0上运行时(Tomcat报告为10.0.1+10
(,在响应对象上调用setStatus((似乎实际上只会导致响应状态设置一次,之后HTTP状态就不能再更改了。然而,通过HttpServlet响应#setHeader((发送到客户端的其他标头似乎不受此影响;即使在setStatus((不再执行任何操作之后,setHeader((也能成功添加标头。没有发送可能导致HTTP标头终止的中间输出数据。
下面是一个最小的工作示例:
package org.example;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/HttpResponseStatusTestServlet")
public class HttpResponseStatusTestServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().append("Testing ");
response.setStatus(505);
response.setStatus(506);
response.getWriter()
.append("Served at: ")
.append(request.getContextPath())
.append(" with: ")
.append(Integer.toString(response.getStatus()));
}
}
在调用这个servlet时,我希望返回字符串Testing Served at: ... with: 506
,因为在getStatus()
调用之前设置的最后一个HTTP状态是506
。返回给客户端的HTTP状态代码也应该是506
。
然而,我最终得到的是Testing Served at: ... with: 505
和505
HTTP状态。就好像第二个setStatus((调用根本不存在。
无论是否包括setStatus((调用之前的response.getWriter().append("Testing ");
,结果都是相同的(除了输出开始时存在Testing
(,因此它似乎不是关于提前终止HTTP响应标头。
在任何地方都看不到第二个setStatus((调用以任何方式失败的迹象,甚至没有迹象表明它曾经存在过;似乎在第一次调用setStatus((之后的任何地方,对响应对象调用setStatus((都完全没有任何作用。
在问题服务器上的上述servlet中,response.isCommitted()
的返回值为false
:在getWriter().append("Testing ");
调用之后、在setStatus(505)
调用之后和在setStatus(506)
调用之后。
我意识到对同一个请求多次调用setStatus((可能有点不正统,但是:
- 考虑到它与Tomcat 9.0.0和Java 8完美配合,这真的不应该与Tomcat 9.0.8和Java 10配合使用吗
- 什么是适用于较新版本的等效版本
使用一个通用的网络搜索引擎让我对发生的事情一无所知,我能找到的文档并没有表明setStatus((只能调用一次,也不能多次调用。
多次调用setStatus()
并不是被禁止的,如果你查看Tomcat的内部,你会发现有些地方的状态可以多次更改(当然,如果被禁止,你会得到一个异常(。
这是由Tomcat 9.0.10和9.0.9中修复的回归错误引起的,但不是9.0.8中修复的(可能在9.0.8中没有发现该错误(
本质上,如果状态代码已经设置为超过399的值,那么尝试更改状态代码并没有任何效果,因为
if (this.status > 399) {
// Don't overwrite first recorded error status
return;
}