web-dev-qa-db-fra.com

Spring MVC - Exception de lancement RestTemplate lorsque http 404 survient

J'ai un service de repos qui envoie une erreur 404 lorsque les ressources ne sont pas trouvées ..__ Voici ici la source de mon contrôleur et l'exception qui envoie Http 404.

@Controller
@RequestMapping("/site")
public class SiteController
{

    @Autowired
    private IStoreManager storeManager;

    @RequestMapping(value = "/stores/{pkStore}", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public StoreDto getStoreByPk(@PathVariable long pkStore) {       
        Store s = storeManager.getStore(pkStore);
        if (null == s) {
            throw new ResourceNotFoundException("no store with pkStore : " + pkStore);
        }
        return StoreDto.entityToDto(s);       

    }
}

@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException
{       
    private static final long serialVersionUID = -6252766749487342137L;    
    public ResourceNotFoundException(String message) {
        super(message);
    }    
}

Quand j'essaie de l'appeler avec RestTemplate avec ce code:

ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
 System.out.println(r.getStatusCode());
 System.out.println(r.getBody());

Je reçois cette exception: 

org.springframework.web.client.RestTemplate handleResponseError
ATTENTION: GET request for "http://........./stores/99" resulted in 404 (Introuvable); invoking error handler
org.springframework.web.client.HttpClientErrorException: 404 Introuvable

Je pensais pouvoir explorer mon objet responseEntity et effectuer certaines tâches avec le code de statut. Mais l'exception est le lancement et mon application tombe en panne.

Existe-t-il une configuration spécifique pour que restTemplate n'envoie pas d'exception mais remplisse mon ResponseEntity.

Merci beaucoup pour l'aide.

-

Loïc

22
loic

Autant que je sache, vous ne pouvez pas obtenir une ResponseEntity réelle, mais le code d'état et le corps (le cas échéant) peuvent être obtenus à partir de l'exception:

    try {
        ResponseEntity<StoreDto> r = restTemplate.getForEntity(url, StoreDto.class, m);
    }
    catch (final HttpClientErrorException e) {
        System.out.println(e.getStatusCode());
        System.out.println(e.getResponseBodyAsString());
    }
29
Squatting Bear

RESTTemplate est assez déficient dans ce domaine IMO. Il y a un bon article de blog ici sur la façon dont vous pourriez éventuellement extraire le corps de la réponse quand vous avez reçu une erreur:

http://springinpractice.com/2013/10/07/handling-json-error-object-responses-with-springs-resttemplate

À ce jour, il existe une demande JIRA en suspens selon laquelle le modèle offre la possibilité d'extraire le corps de la réponse:

https://jira.spring.io/browse/SPR-10961

Le problème avec Squatting Bear, c'est qu'il vous faudrait interroger le code d'état à l'intérieur du bloc de capture, par exemple si vous ne voulez que traiter avec le 404 

Voici comment j'ai contourné cela lors de mon dernier projet. Il existe peut-être de meilleures méthodes et ma solution n’extrait pas du tout le ResponseBody.

public class ClientErrorHandler implements ResponseErrorHandler
{
   @Override
   public void handleError(ClientHttpResponse response) throws IOException 
   {
       if (response.getStatusCode() == HttpStatus.NOT_FOUND)
       {
           throw new ResourceNotFoundException();
       }

       // handle other possibilities, then use the catch all... 

       throw new UnexpectedHttpException(response.getStatusCode());
   }

   @Override
   public boolean hasError(ClientHttpResponse response) throws IOException 
   {
       return response.getStatusCode().series() == HttpStatus.Series.CLIENT_ERROR
         || response.getStatusCode().series() == HttpStatus.Series.SERVER_ERROR;
   }

Les exceptions ResourceNotFoundException et UnexpectedHttpException sont mes propres exceptions non vérifiées. 

Lors de la création du modèle de repos:

    RestTemplate template = new RestTemplate();
    template.setErrorHandler(new ClientErrorHandler());

Maintenant, nous obtenons la construction légèrement plus claire lors d'une requête:

    try
    {
        HttpEntity response = template.exchange("http://localhost:8080/mywebapp/customer/100029",
                                        HttpMethod.GET, requestEntity, String.class);
        System.out.println(response.getBody());
    }
    catch (ResourceNotFoundException e)
    {
        System.out.println("Customer not found");
    }
13
Dick Chesterwood

Comme nous sommes en 2018 et j'espère que lorsque les gens disent "Printemps", ils veulent en fait dire au moins "Printemps", je voulais élargir les réponses fournies avec une approche moins dépoussiérée.

Tout ce qui est mentionné dans les réponses précédentes est correct - vous devez utiliser une ResponseErrorHandler..__ personnalisée. Maintenant, dans le monde Spring Boot, la façon de la configurer est un peu plus simple qu'auparavant . Il existe une classe pratique appelée RestTemplateBuilder. Si vous lisez la toute première ligne de son document Java, vous remarquerez:

Générateur pouvant être utilisé pour configurer et créer un RestTemplate . Fournit des méthodes pratiques pour enregistrer les convertisseurs, gestionnaires d’erreur et UriTemplateHandlers.

Il a en fait une méthode juste pour ça:

new RestTemplateBuilder().errorHandler(new DefaultResponseErrorHandler()).build();

En plus de cela, les gars de Spring ont compris les inconvénients d'une RestTemplate conventionnelle il y a longtemps, et comment cela peut être particulièrement pénible lors des tests. Ils ont créé une classe pratique, TestRestTemplate, qui sert de wrapper autour de RestTemplate et a défini le paramètre errorHandler sur une implémentation vide:

private static class NoOpResponseErrorHandler extends 
       DefaultResponseErrorHandler {

    @Override
    public void handleError(ClientHttpResponse response) throws IOException {
    }

}
6
yuranos87

Vous pouvez créer votre propre wrapper RestTemplate qui ne lève pas d'exceptions, mais renvoie une réponse avec le code d'état reçu. (Vous pouvez également renvoyer le corps, mais cela cesserait d'être sûr pour le type, donc dans le code ci-dessous, le corps reste simplement null.)

/**
 * A Rest Template that doesn't throw exceptions if a method returns something other than 2xx
 */
public class GracefulRestTemplate extends RestTemplate {
    private final RestTemplate restTemplate;

    public GracefulRestTemplate(RestTemplate restTemplate) {
        super(restTemplate.getMessageConverters());
        this.restTemplate = restTemplate;
    }

    @Override
    public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
        return withExceptionHandling(() -> restTemplate.getForEntity(url, responseType));
    }

    @Override
    public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
        return withExceptionHandling(() -> restTemplate.postForEntity(url, request, responseType));
    }

    private <T> ResponseEntity<T> withExceptionHandling(Supplier<ResponseEntity<T>> action) {
        try {
            return action.get();
        } catch (HttpClientErrorException ex) {
            return new ResponseEntity<>(ex.getStatusCode());
        }
    }
}
1
siledh

Récemment eu un cas d'utilisation pour cela. Ma solution:

public class MyErrorHandler implements ResponseErrorHandler {

@Override
public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
    return hasError(clientHttpResponse.getStatusCode());
}

@Override
public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
    HttpStatus statusCode = clientHttpResponse.getStatusCode();
    MediaType contentType = clientHttpResponse
        .getHeaders()
        .getContentType();
    Charset charset = contentType != null ? contentType.getCharset() : null;
    byte[] body = FileCopyUtils.copyToByteArray(clientHttpResponse.getBody());

    switch (statusCode.series()) {
        case CLIENT_ERROR:
            throw new HttpClientErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
        case SERVER_ERROR:
            throw new HttpServerErrorException(statusCode, clientHttpResponse.getStatusText(), body, charset);
        default:
            throw new RestClientException("Unknown status code [" + statusCode + "]");
    }

}

private boolean hasError(HttpStatus statusCode) {
    return (statusCode.series() == HttpStatus.Series.CLIENT_ERROR ||
        statusCode.series() == HttpStatus.Series.SERVER_ERROR);
}
0
eosimosu