2013-02-05 5 views
12

Um Griff-Fehler (wie HTTP 404 ist) global, die außerhalb eines Controllers auftreten, kann ich Einträge haben ähnlich der folgenden in meiner web.xml:Spring MVC 3.2 - Content Negotiation für Fehlerseiten?

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

In meinem Errorcontroller ich ähnliche entsprechende Verfahren müssen die folgende:

@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); 
    } 
} 

Das Problem, das ich bin Bewurf ist, dass die ContentNegotiationManager und Nachrichtenwandler I konfiguriert haben, werden in diesem Fall nicht verwendet werden. Ich vermute, dass, da die Anfrage auf die Fehlerseite umgeleitet wird, die Attribute der ursprünglichen Anfrage verloren gehen, die bei der Inhaltsverhandlung verwendet werden, und dies wird als eine vollständig separate Anfrage behandelt. (dh ursprüngliche Anfrage für /mycontroller/badresource.json ->/errors/404 (mit keiner Dateierweiterung))

Gibt es einen Weg in einem Fehlerhandler wie diesem festzustellen und/oder mit dem entsprechenden Inhaltstyp zu antworten wie in der ursprünglichen Anfrage gefordert?

Antwort

3

Ich kam ein bisschen von einem Hack für diese, aber es scheint zu funktionieren. Es beinhaltet grundsätzlich eine zusätzliche Weiterleitung in der Fehlerbehandlung, um die Dateierweiterung der ursprünglichen Anfrage zu bestimmen.

In meinem web.xml Ich habe das auf eine Zwischen Aktion weitergeleitet Fehler:

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

Dann, bevor die Aktion Weiterleitung, die die Fehlerreaktion erzeugen wird, wird eine Prüfung durchgeführt wird, um zu sehen, ob es eine war Dateiendung auf der ursprünglichen Anfrage. Wenn dies der Fall war, wird sichergestellt, dass es an den Vorwärts-URI angehängt wird. Die HTTP-Header werden automatisch weitergeleitet. Wenn die Inhaltsverhandlung, die Sie eingerichtet haben, nur Dateierweiterungen oder HTTP-Header umfasst, kann die "Fehlerseite" den Fehler im entsprechenden Inhaltstyp zurückgeben.

@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); 
    } 
} 
2

Ja, die Anwendung Ausnahmen und HTTP-Antwort Fehlercodes sind zwei verschiedene Dinge.

Sie können den Code wie folgt anpassen, so dass Sie Zugriff auf die requestUri haben. Ich denke, Sie können den Inhaltstyp basierend darauf finden. Ich weiß, seine rohe, aber ich glaube nicht, dass wir alternative Lösung haben:

@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); 
} 

Aus dem Beispiel nehme ich an Ihre Anwendung REST-Dienst ist, dann haben Sie wahrscheinlich zu this link on how 404 is handled in einer REST-Full-Service beziehen können.

+0

Vielen Dank für die Eingabe. Ich habe diesen Artikel gesehen, aber wenn ich etwas nicht vermisse, behandelt es nur Ausnahmen von den Controllern und nicht allgemeine Servlet-Fehler. In diesem Kontext gibt 'request.getRequestURI()' außerdem den URI der Fehlerseite. Sieht so aus, als müssten Sie 'req.getAttribute (" javax.servlet.error.request_uri ")' verwenden, um den URI der ursprünglichen Anfrage zu erhalten.Ich frage mich, ob es eine Möglichkeit gibt, die Informationen, die der ContentNegotiationManager verwendet, zu überschreiben und sie auf die Informationen aus der ursprünglichen Anfrage anstatt auf der Fehlerseite zu setzen? – WayneC

2

Spring MVC 3.2 enthält nun eine nützliche Anmerkung @ControllerAdvice genannt. Sie können eine ExceptionHandler-Methode hinzufügen, die alle von Ihnen definierten Ausnahmen global behandelt.

Für mich sind mir nur zwei mögliche Inhaltstypen wichtig, die an den Client zurückgegeben werden: application/json oder text/html.

Hier ist, wie ich es einrichten würde -

@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; 
    } 

} 
+0

Ist es besser, ResponseEntity anstelle von String zurückzugeben. Und sollte ich 404 Fehlerseite in web.xml mit diesem Ansatz haben? Bitte überprüfen Sie meine Frage: http://stackoverflow.com/questions/17322855/how-to-handle-404-exception-invoked-by-ajax-spring-mvc-3-2-controllerradvice – Alex