【问题标题】:Spring MVC 3.2 - Content Negotiation for Error Pages?Spring MVC 3.2 - 错误页面的内容协商?
【发布时间】:2013-02-05 19:46:22
【问题描述】:

为了全局处理可能在控制器之外发生的错误(例如 HTTP 404),我的 web.xml 中有类似于以下内容的条目:

<error-page>
    <error-code>404</error-code>
    <location>/errors/404</location>
</error-page>

在我的 ErrorController 中,我有类似以下的相应方法:

@Controller
@RequestMapping("/errors")
public class ErrorController {

    @RequestMapping(value = "/404", method = RequestMethod.GET)
    @ResponseBody
    public ResponseEntity<ErrorResponse> error404() {

        ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");

        return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
    }
}

我面临的问题是我配置的ContentNegotiationManager 和消息转换器在这种情况下没有被使用。我怀疑由于请求被重定向到错误页面,内容协商中使用的原始请求属性丢失了,这被视为完全独立的请求。 (即对 /mycontroller/badresource.json 的原始请求 --> /errors/404(无文件扩展名))

在这样的错误处理程序中是否有任何方法可以确定和/或响应原始请求中请求的适当内容类型?

【问题讨论】:

    标签: java spring spring-mvc content-negotiation


    【解决方案1】:

    Spring MVC 3.2 现在包含一个有用的注释,称为@ControllerAdvice。您可以添加一个 ExceptionHandler 方法,该方法将全局处理您定义的任何异常。

    对我来说,我只关心返回给客户端的两种可能的内容类型 - application/jsontext/html

    这是我将如何设置它 -

    @ControllerAdvice
    public class ExceptionControllerAdvice {
    
        private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
        private static final MediaType JSON_MEDIA_TYPE = new MediaType("application", "json", DEFAULT_CHARSET);
    
        //I decided to handle all exceptions in this one method
        @ExceptionHandler(Throwable.class)
        public @ResponseBody String handleThrowable(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
    
            ...
    
            if(supportsJsonResponse(request.getHeader("Accept"))) {
    
                //return response as JSON
                response.setStatus(statusCode);
                response.setContentType(JSON_MEDIA_TYPE.toString());
    
                        //TODO serialize your error in a JSON format
                        //return ...
    
            } else {
    
                //return as HTML
                response.setContentType("text/html");
                response.sendError(statusCode, exceptionMessage);
                return null;
            }
        }
    
        private boolean supportsJsonResponse(String acceptHeader) {
    
            List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader);
    
            for(MediaType mediaType : mediaTypes) {
                if(JSON_MEDIA_TYPE.includes(mediaType)) {
                    return true;
                }
            }
    
            return false;
        }
    
    }
    

    【讨论】:

    【解决方案2】:

    我为此想出了一些技巧,但它似乎有效。它基本上在错误处理中涉及额外的转发以确定原始请求的文件扩展名。

    在我的 web.xml 中,我将错误转发给了中间操作:

    <error-page>
        <error-code>404</error-code>
        <location>/errors/redirect</location>
    </error-page>
    

    然后,在转发到将生成错误响应的操作之前,会检查原始请求是否有文件扩展名。如果有,它确保将其附加到转发 URI。 HTTP 标头是自动转发的,因此如果您设置的内容协商仅涉及文件扩展名或 HTTP 标头,这将有效地允许“错误页面”以适当的内容类型返回错误。

    @Controller
    @RequestMapping("/errors")
    public class ErrorController {
    
        @RequestMapping(value = "/redirect", method = RequestMethod.GET)
        public void errorRedirect(HttpServletRequest request, HttpServletResponse response) {
    
            // Get original request URI
            String uri = (String)request.getAttribute(WebUtils.ERROR_REQUEST_URI_ATTRIBUTE);
    
            // Try to determine file extension
            String filename = WebUtils.extractFullFilenameFromUrlPath(uri);
            String extension = StringUtils.getFilenameExtension(filename);
            extension = StringUtils.hasText(extension) ? "." + extension : "";
    
            // Forward request to appropriate handler with original request's file extension (i.e. /errors/404.json)
            String forwardUri = "/errors/404" + extension); 
            request.getRequestDispatcher(forwardUri).forward(request, response);
        }
    
        @RequestMapping(value = "/404", method = RequestMethod.GET)
        @ResponseBody
        public ResponseEntity<ErrorResponse> error404() {
    
            ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");
    
            return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
        }
    }
    

    【讨论】:

      【解决方案3】:

      确实,应用程序异常和 HTTP 响应错误代码是两个不同的东西。

      您可以修改如下代码,以便您可以访问requestUri。 我想你可以根据它找到内容类型。我知道它很粗糙,但我认为我们没有其他解决方案:

      @RequestMapping(value = "/404", method = RequestMethod.GET)
      @ResponseBody
      public ResponseEntity<ErrorResponse> error404(HttpServletRequest request) {
      
          ErrorResponse errorBody = new ErrorResponse(404, "Resource Not Found!");
      
          String requestUri = request.getRequestURI();
      
          return new ResponseEntity<ErrorResponse>(errorBody, HttpStatus.NOT_FOUND);
      }
      

      从示例中我假设您的应用程序是 REST 服务,那么您可能可以在 REST 完整服务中引用 this link on how 404 is handled

      【讨论】:

      • 感谢您的意见。我看过那篇文章,但除非我遗漏了什么,否则它只处理来自控制器的异常,而不是一般的 servlet 错误。此外,在这种情况下,request.getRequestURI() 会为您提供错误页面的 URI。看起来您必须使用 req.getAttribute("javax.servlet.error.request_uri") 来获取原始请求的 URI。我想知道是否有某种方法可以覆盖 ContentNegotiationManager 使用的信息并将其设置为来自原始请求而不是错误页面的信息?
      猜你喜欢
      • 1970-01-01
      • 2014-06-15
      • 2011-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-01
      相关资源
      最近更新 更多