2013-02-05 6 views
12

globalmente errori maniglia (come HTTP 404), che possono verificarsi al di fuori di un controllore, devo voci simili al seguente nel mio web.xml:Spring MVC 3.2 - Negoziazione del contenuto per le pagine di errore?

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

Nella mia ErrorController ho corrispondenti metodi simili al seguente:

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

Il problema che sto affrontando è che i ContentNegotiationManager e messaggi convertitori ho configurato non vengono utilizzati in questo caso. Sospetto che dal momento che la richiesta viene reindirizzata alla pagina di errore, gli attributi della richiesta originale utilizzati nella negoziazione del contenuto vengono persi e questa viene considerata come una richiesta completamente separata. (es. richiesta originale per /mycontroller/badresource.json ->/errors/404 (senza estensione file))

C'è qualche modo in un gestore di errori come questo determinare e/o rispondere con il tipo di contenuto appropriato come richiesto nella richiesta originale?

risposta

3

Mi sono inventato un po 'di trucco per questo, ma sembra funzionare. Fondamentalmente implica un ulteriore avanzamento nella gestione degli errori per determinare l'estensione del file della richiesta originale.

Nel mio web.xml ho l'errore inoltrato ad un'azione intermedia:

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

Poi, prima di inoltrare l'azione che genererà la risposta di errore, viene fatto un controllo per vedere se ci fosse un estensione del file sulla richiesta originale. Se c'era, assicura che venga aggiunto all'URI in avanti. Le intestazioni HTTP vengono automaticamente inoltrate, quindi se la negoziazione del contenuto che hai impostato riguarda solo le estensioni di file o l'intestazione HTTP, questo consentirà effettivamente alla "pagina degli errori" di restituire l'errore nel tipo di contenuto appropriato.

@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

Sì, infatti, le eccezioni dell'applicazione e i codici di errore di risposta HTTP sono due cose diverse.

È possibile adattare il codice come di seguito, in modo da avere accesso allo requestUri. Immagino che tu possa trovare il tipo di contenuto basato su di esso. So che la sua cruda, ma non credo che abbiamo soluzione alternativa:

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

Dall'esempio presumo l'applicazione è il servizio REST, allora probabilmente si potrebbe fare riferimento a this link on how 404 is handled in un servizio completo REST.

+0

Grazie per l'input. Ho visto quell'articolo, ma a meno che manchi qualcosa, gestisce solo le eccezioni dai controller e non gli errori generali della servlet. Inoltre, in questo contesto 'request.getRequestURI()' ti fornisce l'URI della pagina di errore. Sembra che tu debba usare 'req.getAttribute (" javax.servlet.error.request_uri ")' per ottenere l'URI della richiesta originale.Mi chiedo se c'è un modo per ignorare le informazioni che utilizza ContentNegotiationManager e impostarlo sulle informazioni dalla richiesta originale anziché sulla pagina degli errori? – WayneC

2

Spring MVC 3.2 ora include un'annotazione utile chiamato @ControllerAdvice. Puoi aggiungere un metodo ExceptionHandler che gestirà globalmente qualsiasi eccezione che hai definito.

Per me, mi interessa solo due possibili tipi di contenuto da restituire al client - application/json o text/html.

Ecco come vorrei configurarlo -

@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

È meglio restituire ResponseEntity anziché String. E dovrei avere 404 error-page in web.xml con questo approccio? Per favore controlla la mia domanda: http://stackoverflow.com/questions/17322855/how-to-handle-404-exception-invoked-by-ajax-spring-mvc-3-2-controlleradvice – Alex