显示通过Jetty 9.4发送到JSP的PUT请求的自定义错误页面

问题描述

背景

从Spring 4升级到5.3时,web.xml中指定的错误页面不再适用于某些HTTP谓词,其中之一是PUT。例如,当对Spring控制器的PUT请求导致意外错误时,则不会显示web.xml中定义的错误页面。取而代之的是,我们在405 Method Not Allowed处出现了错误。错误页面已正确显示GET个请求。错误页面在web.xml中已经(并且仍然)是这样定义的:

<error-page>
    <exception-type>java.lang.Exception</exception-type>
    <location>/uncaughtException</location>
</error-page>

“ uncaughtException”是这样定义的切片视图:

<definition extends="siteDefault" name="uncaughtException">
     <put-attribute name="body" value="/WEB-INF/views/uncaughtException.jspx"/>
</definition>

为了使此功能适用于所有HTTP动词,我们添加了以下Spring Controller:

@RequestMapping("/uncaughtException")
@Controller
public class UncaughtExceptionViewController {

    @RequestMapping
    public String uncaughtException() {
        return "uncaughtException";
    }

}

现在,即使对于PUT请求,错误页面也能正确显示。

以前,我们使用Jetty 9.2,但是在升级到Java 14时,我们还被迫升级到Jetty 9.4。这样做时,我们注意到即使我们仍然拥有PUT控制器,也不再为GET请求显示错误页面(它对UncaughtExceptionViewController有效)。相反,405 Method Not Allowed错误又回来了。在调试期间,我们注意到使用Jetty 9.4时uncaughtException中的UncaughtExceptionViewController没有被调用。

问题

我们如何配置Jetty或Spring MVC来显示错误页面,甚至对于PUT请求,不仅显示GET,而且不显示405 Method Not Allowed

更新

org.eclipse.jetty.server.Response.setStatus中设置了一个断点之后,我发现问题似乎在于“ JSP仅允许GET,POST或HEAD。Jasper也允许选择”。完整的堆栈跟踪为:

sendError:454,Response (org.eclipse.jetty.server)
sendError:158,HttpServletResponseWrapper (javax.servlet.http)
sendError:119,OnCommittedResponseWrapper (org.springframework.session.web.http)
sendError:158,HttpServletResponseWrapper (javax.servlet.http)
sendError:158,OnCommittedResponseWrapper (org.springframework.security.web.util)
sendError:158,OnCommittedResponseWrapper (org.springframework.security.web.util)
_jspService:1,site_005fdefault_jspx (org.apache.jsp.WEB_002dINF.layouts)
service:71,HttpJspBase (org.apache.jasper.runtime)
service:790,HttpServlet (javax.servlet.http)
service:476,JspServletWrapper (org.apache.jasper.servlet)
serviceJspFile:386,JspServlet (org.apache.jasper.servlet)
service:330,JspServlet (org.apache.jasper.servlet)
service:106,JettyJspServlet (org.eclipse.jetty.jsp)
service:790,HttpServlet (javax.servlet.http)
service:1402,ServletHolder$NotAsyncServlet (org.eclipse.jetty.servlet)
handle:763,ServletHolder (org.eclipse.jetty.servlet)
doHandle:569,ServletHandler (org.eclipse.jetty.servlet)
handle:143,ScopedHandler (org.eclipse.jetty.server.handler)
handle:620,SecurityHandler (org.eclipse.jetty.security)
handle:127,HandlerWrapper (org.eclipse.jetty.server.handler)
nextHandle:235,ScopedHandler (org.eclipse.jetty.server.handler)
doHandle:1610,SessionHandler (org.eclipse.jetty.server.session)
nextHandle:233,ScopedHandler (org.eclipse.jetty.server.handler)
doHandle:1377,ContextHandler (org.eclipse.jetty.server.handler)
nextScope:188,ScopedHandler (org.eclipse.jetty.server.handler)
doScope:507,ServletHandler (org.eclipse.jetty.servlet)
doScope:1580,SessionHandler (org.eclipse.jetty.server.session)
nextScope:186,ScopedHandler (org.eclipse.jetty.server.handler)
doScope:1292,ContextHandler (org.eclipse.jetty.server.handler)
handle:141,ScopedHandler (org.eclipse.jetty.server.handler)
forward:219,Dispatcher (org.eclipse.jetty.server)
forward:78,Dispatcher (org.eclipse.jetty.server)
forward:407,SessionRepositoryFilter$SessionRepositoryRequestWrapper$SessionCommittingRequestDispatcher (org.springframework.session.web.http)
forward:265,ServletRequest (org.apache.tiles.request.servlet)
doForward:228,ServletRequest (org.apache.tiles.request.servlet)
dispatch:57,AbstractClientRequest (org.apache.tiles.request)
render:47,DispatchRenderer (org.apache.tiles.request.render)
render:259,BasicTilesContainer (org.apache.tiles.impl)
render:397,BasicTilesContainer (org.apache.tiles.impl)
render:238,BasicTilesContainer (org.apache.tiles.impl)
render:221,BasicTilesContainer (org.apache.tiles.impl)
render:59,DefinitionRenderer (org.apache.tiles.renderer)
renderMergedOutputModel:147,TilesView (org.springframework.web.servlet.view.tiles3)
render:316,AbstractView (org.springframework.web.servlet.view)
render:1373,DispatcherServlet (org.springframework.web.servlet)
processDispatchResult:1118,DispatcherServlet (org.springframework.web.servlet)
doDispatch:1057,DispatcherServlet (org.springframework.web.servlet)
doService:943,DispatcherServlet (org.springframework.web.servlet)
processRequest:1006,FrameworkServlet (org.springframework.web.servlet)
doPut:920,FrameworkServlet (org.springframework.web.servlet)
service:710,HttpServlet (javax.servlet.http)
service:883,FrameworkServlet (org.springframework.web.servlet)
service:790,HttpServlet (javax.servlet.http)
handle:763,ServletHolder (org.eclipse.jetty.servlet)
doFilter:1651,ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doFilter:226,WebSocketUpgradeFilter (org.eclipse.jetty.websocket.server)
doFilter:1638,ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doFilterInternal:186,OpenEntityManagerInViewFilter (org.springframework.orm.jpa.support)
doFilter:119,OncePerRequestFilter (org.springframework.web.filter)
doFilter:1638,ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doFilter:317,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
invoke:127,FilterSecurityInterceptor (org.springframework.security.web.access.intercept)
doFilter:91,FilterSecurityInterceptor (org.springframework.security.web.access.intercept)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:114,ExceptionTranslationFilter (org.springframework.security.web.access)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:137,SessionManagementFilter (org.springframework.security.web.session)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:111,AnonymousAuthenticationFilter (org.springframework.security.web.authentication)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:158,RememberMeAuthenticationFilter (org.springframework.security.web.authentication.rememberme)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:170,SecurityContextHolderAwareRequestFilter (org.springframework.security.web.servletapi)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:63,RequestCacheAwareFilter (org.springframework.security.web.savedrequest)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:158,BasicAuthenticationFilter (org.springframework.security.web.authentication.www)
doFilter:119,OncePerRequestFilter (org.springframework.web.filter)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:200,AbstractAuthenticationProcessingFilter (org.springframework.security.web.authentication)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:116,LogoutFilter (org.springframework.security.web.authentication.logout)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:66,HeaderWriterFilter (org.springframework.security.web.header)
doFilter:119,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:56,WebAsyncManagerIntegrationFilter (org.springframework.security.web.context.request.async)
doFilter:119,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilter:105,SecurityContextPersistenceFilter (org.springframework.security.web.context)
doFilter:331,FilterChainProxy$VirtualFilterChain (org.springframework.security.web)
doFilterInternal:214,FilterChainProxy (org.springframework.security.web)
doFilter:177,FilterChainProxy (org.springframework.security.web)
invokeDelegate:358,DelegatingFilterProxy (org.springframework.web.filter)
doFilter:271,DelegatingFilterProxy (org.springframework.web.filter)
doFilter:1638,ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doFilterInternal:94,HiddenHttpMethodFilter (org.springframework.web.filter)
doFilter:119,ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doFilterInternal:201,CharacterEncodingFilter (org.springframework.web.filter)
doFilter:119,ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doFilterInternal:40,CorsFilter (com.mycompany.spring)
doFilter:119,ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doFilterInternal:141,SessionRepositoryFilter (org.springframework.session.web.http)
doFilter:82,OncePerRequestFilter (org.springframework.session.web.http)
invokeDelegate:358,ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doHandle:567,ScopedHandler (org.eclipse.jetty.server.handler)
handle:578,ScopedHandler (org.eclipse.jetty.server.handler)
handle:191,ContextHandlerCollection (org.eclipse.jetty.server.handler)
handle:146,HandlerCollection (org.eclipse.jetty.server.handler)
handle:127,HandlerWrapper (org.eclipse.jetty.server.handler)
handle:501,Server (org.eclipse.jetty.server)
lambda$handle$1:383,HttpChannel (org.eclipse.jetty.server)
dispatch:-1,435181768 (org.eclipse.jetty.server.HttpChannel$$Lambda$1064)
dispatch:556,HttpChannel (org.eclipse.jetty.server)
handle:375,HttpChannel (org.eclipse.jetty.server)
onFillable:273,HttpConnection (org.eclipse.jetty.server)
succeeded:311,AbstractConnection$ReadCallback (org.eclipse.jetty.io)
fillable:105,FillInterest (org.eclipse.jetty.io)
run:104,ChannelEndPoint$1 (org.eclipse.jetty.io)
runTask:336,EatWhatYouKill (org.eclipse.jetty.util.thread.strategy)
doProduce:313,EatWhatYouKill (org.eclipse.jetty.util.thread.strategy)
tryProduce:171,EatWhatYouKill (org.eclipse.jetty.util.thread.strategy)
produce:135,EatWhatYouKill (org.eclipse.jetty.util.thread.strategy)
run:-1,976266910 (org.eclipse.jetty.io.ManagedSelector$$Lambda$1056)
runJob:806,QueuedThreadPool (org.eclipse.jetty.util.thread)
run:938,QueuedThreadPool$Runner (org.eclipse.jetty.util.thread)
run:832,Thread (java.lang)

解决方法

由于这是JSP制作的。

可以使用标准的Servlet错误页面处理。

可以将WEB-INF/web.xml定义为响应状态码405。

  <error-page>
    <error-code>405</error-code>
    <location>/myMethodNotAllowedPath</location>
  </error-page>

您还可以选择定义一个全局错误页面错误处理程序,例如..

  <error-page>
    <location>/myGlobalErrorHandler</location>
  </error-page>

从Servlet 3.0开始,<error-page>可以基于状态码<error-code>,异常<exception-type>或什么都不定义(这意味着所有错误均未被更具体的定义捕获)来定义响应。

Servlet的实现将使用<location>将请求重新分发到已定义的DispatcherType.ERROR,原始请求的详细信息可以在HttpServletRequest.getAttribute(String)值中的各种已定义键/名称下找到。在RequestDispatcher.ERROR_*常量中。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...