描述问题现象
环境
基于 SpringMVC 的 web 项目,视图技术选择的 JSP,Servlet 规范的版本为 3.1,web 容器为 Tomcat 9.0.13。
现象
在使用 @PutMapping
注解的 handler method 中返回逻辑视图名后,客户端接收到的并不是对应的 JSP 页面中的内容,而是一个状态码为 405 的错误页面:
分析问题根源
调试
首先是进行跟踪调试。
视图解析正常:
内部转发正常:
推测问题应该与 JSP container 有关。
搜索
在 JavaServer Pages 2.3 Specification 中,有如下描述:
JSP.11.1 JSP Page Model
The JSP container must render a JSP page for the HTTP methods GET, POST, and HEAD, with identical responses. The behavior of the JSP container is undefined for other methods.
也就是说,从 JSP 规范 2.3 版本开始,JSP container 只处理 Http method 为 GET、POST 和 HEAD 的请求,对于其他类型的请求,会返回一个 405 的状态码(Tomcat 9.x)。
提供解决方案
参考 SpringMVC 中的 HiddenHttpMethodFilter
,用过滤器拦截内部转发到 JSP 的请求。
Filter 实现:
package com.practice.filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
/**
* @description 因为从 servlet 2.3 版本开始,JSP 只能接受 GET、POST 和 HEAD 请求。该拦截器会处理所有转发目标为
* JSP 的请求,更改请求方法为 POST。
*
* @author allen
* @date 2018/12/29
*/
public class ForwardedHttpMethodFilter implements Filter {
private static final List<String> ALLOWED_METHODS = Collections
.unmodifiableList(Arrays.asList("PUT", "DELETE", "PATCH"));
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) {
throw new ServletException("HttpMethodFilter just supports HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (ALLOWED_METHODS.contains(httpRequest.getMethod())
&& httpRequest.getAttribute("javax.servlet.error.exception") == null) {
httpRequest = new ForwradedHttpMethodRequestWrapper(httpRequest);
}
chain.doFilter(httpRequest, httpResponse);
}
private static class ForwradedHttpMethodRequestWrapper extends HttpServletRequestWrapper {
public ForwradedHttpMethodRequestWrapper(HttpServletRequest request) {
super(request);
}
@Override
public String getMethod() {
return "POST";
}
}
}
Filter 注册:
<filter>
<filter-name>forwardedHttpMethodFilter</filter-name>
<filter-class>com.practice.filter.ForwardedHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>forwardedHttpMethodFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>