web-dev-qa-db-fra.com

Réponse d'erreur personnalisée lors du démarrage du printemps 404 ReST

J'utilise Spring Boot pour héberger une API REST. Au lieu d'avoir la réponse d'erreur standard, j'aimerais toujours envoyer une réponse JSON, même si un navigateur accède à l'URL et à une structure de données personnalisée. 

Je peux le faire avec @ControllerAdvice et @ExceptionHandler pour les exceptions personnalisées. Mais je ne trouve aucun moyen de le faire pour les erreurs standard et gérées telles que 404 et 401.

Existe-t-il de bons modèles pour ce faire?

9
Markus

404 erreur est gérée par DispatcherServlet. il existe une propriété throwExceptionIfNoHandlerFound, que vous pouvez remplacer.

Dans la classe d'application, vous pouvez créer un nouveau bean:

@Bean
DispatcherServlet dispatcherServlet () {
    DispatcherServlet ds = new DispatcherServlet();
    ds.setThrowExceptionIfNoHandlerFound(true);
    return ds;
}

... puis intercepter l'exception NoHandlerFoundException dans 

@EnableWebMvc
@ControllerAdvice
public class GlobalControllerExceptionHandler {
    @ExceptionHandler
    @ResponseStatus(value=HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorMessageResponse requestHandlingNoHandlerFound(final NoHandlerFoundException ex) {
        doSomething(LOG.debug("text to log"));
    }
}
3
maxie

J'ai fourni l'exemple de solution sur la façon de remplacer la réponse pour le cas 404. La solution est assez simple et je poste un exemple de code mais vous pouvez trouver plus de détails sur le fil de discussion original: Spring Boot Rest - Comment configurer 404 - ressource non trouvée

First : définir un contrôleur qui traitera les erreurs et remplacera la réponse:

@ControllerAdvice
public class ExceptionHandlerController {

    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(value= HttpStatus.NOT_FOUND)
    @ResponseBody
    public ErrorResponse requestHandlingNoHandlerFound() {
        return new ErrorResponse("custom_404", "message for 404 error code");
    }
}

Second : vous devez dire à Spring de lever une exception en cas de 404 (impossible de résoudre le gestionnaire):

@SpringBootApplication
@EnableWebMvc
public class Application {

    public static void main(String[] args) {
        ApplicationContext ctx = SpringApplication.run(Application.class, args);

        DispatcherServlet dispatcherServlet = (DispatcherServlet)ctx.getBean("dispatcherServlet");
        dispatcherServlet.setThrowExceptionIfNoHandlerFound(true);
    }
}
3
Ilya Ovesnov

Vous pouvez ajouter des objets ErrorPage personnalisés en corrélation avec la définition de page d'erreur dans le fichier web.xml. Spring Boot fournit un exemple ...

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer(){
    return new MyCustomizer();
}

// ...

private static class MyCustomizer implements EmbeddedServletContainerCustomizer {

    @Override
    public void customize(ConfigurableEmbeddedServletContainer container) {
        container.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, "/unauthorized.html"));
        container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/not-found.html"));
    }

}

EDIT: Bien que je pense que la méthode ci-dessus fonctionnera si vous configurez les contrôleurs des pages d'erreur, une méthode encore plus simple consisterait à inclure un ErrorController personnalisé comme celui ci-dessous ...

@Bean
public ErrorController errorController(ErrorAttributes errorAttributes) {
    return new CustomErrorController(errorAttributes);
}

// ...

public class CustomErrorController extends BasicErrorController {

    public CustomErrorController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

    @Override
    @RequestMapping(value = "${error.path:/error}")
    @ResponseBody
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        ResponseEntity<Map<String, Object>> error = super.error(request);
        HttpStatus statusCode = error.getStatusCode();

        switch (statusCode) {
        case NOT_FOUND:
            return getMyCustomNotFoundResponseEntity(request);
        case UNAUTHORIZED:
            return getMyCustomUnauthorizedResponseEntity(request);
        default:
            return error;
        }
    }
}
1
hyness

Résumant toutes les réponses et commentaires, je pense que la meilleure façon de faire est-

Tout d’abord, dites à spring boot de lancer une exception si aucun gestionnaire n’est trouvé dans application.properties

spring.mvc.throw-exception-if-no-handler-found=true

Puis manipulez NoHandlerFoundException dans votre application. Je gère cela en suivant la voie

@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(NoHandlerFoundException.class)
    public void handleNotFoundError(HttpServletResponse response, NoHandlerFoundException ex) {
        ErrorDto errorDto = Errors.URL_NOT_FOUND.getErrorDto();
        logger.error("URL not found exception: " + ex.getRequestURL());
        prepareErrorResponse(response, HttpStatus.NOT_FOUND, errorDto);
    }
}
1
Emdadul Sawon

J'avais le même problème mais je l'ai corrigé en utilisant une méthode différente. Pour renvoyer 404 , 401 et un autre statut dans une réponse personnalisée, vous pouvez maintenant ajouter le statut de la réponse à la personnalisation. classe d'exception et appelez-le à partir de votre gestionnaire d'exceptions.

Avec Spring utility class AnnotationUtils , vous pouvez obtenir le statut de n'importe quelle exception personnalisée définie avec la méthode findAnnotation. Le statut approprié sera renvoyé en utilisant l'annotation que vous avez définie pour les exceptions, y compris non trouvé.

Voici mon @RestControllerAdvice

@RestControllerAdvice
public class MainExceptionHandler extends Throwable{

 @ExceptionHandler(BaseException.class)
 ResponseEntity<ExceptionErrorResponse> exceptionHandler(GeneralMainException  e)
 {
  ResponseStatus status = AnnotationUtils.findAnnotation(e.getClass(),ResponseStatus.class);
  if(status != null)
  {
    return new ResponseEntity<>(new ExceptionErrorResponse(e.getCode(),e.getMessage()),status.code());
  }
 }

CustomParamsException pour renvoyer le statut de requête incorrecte

@ResponseStatus(value= HttpStatus.BAD_REQUEST)
public class CustomParamsException extends BaseException {
    private static final String CODE = "400";
    public CustomParamsException(String message) {
        super(CODE, message);
    }
}

Détails non trouvés pour retourner Statut introuvable

@ResponseStatus(value= HttpStatus.NOT_FOUND)
public class DetailsNotException extends BaseException {
    private static final String CODE = "400";
    public DetailsNotException(String message) {
        super(CODE, message);
    }
}

Une exception GeneralMainException pour prolonger Excetion

public class GeneralMainException extends Exception {
private String code;
private String message;

public GeneralMainException (String message) {
    super(message);
}

public GeneralMainException (String code, String message) {
    this.code = code;
    this.message = message;
}

public String getCode() {
    return code;
}

@Override
public String getMessage() {
    return message;
}
}

Vous pouvez décider de gérer d'autres exceptions système en l'incluant dans l'avis du contrôleur. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) ExceptionErrorResponse sysError(Exception e) { return new ExceptionErrorResponse(""1002", e.getMessage()); }

0
Adewagold

Il semble que vous deviez introduire une méthode annotée appropriée, par exemple. pour le type de support non pris en charge (415), il sera:

  @ExceptionHandler(MethodArgumentNotValidException)
  public ResponseEntity handleMethodArgumentNotValidException(HttpServletRequest req, MethodArgumentNotValidException e) {
    logger.error('Caught exception', e)
    def response = new ExceptionResponse(
            error: 'Validation error',
            exception: e.class.name,
            message: e.bindingResult.fieldErrors.collect { "'$it.field' $it.defaultMessage" }.join(', '),
            path: req.servletPath,
            status: BAD_REQUEST.value(),
            timestamp: currentTimeMillis()
    )
    new ResponseEntity<>(response, BAD_REQUEST)
  }

Cependant, il se peut que cela ne soit pas possible puisque les 401 et 404 peuvent être lancés avant d’atteindre DispatcherServlet - dans ce cas, ControllerAdvice ne fonctionnera pas.

0
Opal

Veuillez consulter Spring Boot REST traitement des exceptions de services . Il montre comment indiquer au dispatcherservlet d'émettre des exceptions pour "aucune route trouvée", puis comment intercepter ces exceptions. Nous (l’endroit où je travaille) l’utilisons actuellement en production pour nos REST services.

0
ogradyjd