in spring api restful restemplate ~ de lecture.

Consommation d'une API RESTful avec Spring RestTemplate

La mise en place d'une API RESTful n'est pas une mince affaire, entre les bonnes et les mauvaises pratiques il est facile de ne faire finalement qu'une vulgaire API RPC (ou JSON-RPC).

Le modèle de maturité de Richardson est à mon avis une bonne base, histoire de poser les bonnes pratiques dès le départ, pour rappel il énonce les 3 principes suivants :

  • Ressources
  • Verbes et Codes retours HTTP
  • Contrôles Hypermedia

Spring MVC 4.0 RestTemplate

Liens intéressants, voire indispensables :

Générer les entêtes HTTP

L'encapsulation de la requête HTTP par RestTemplate nous permet de personnaliser les informations envoyées dans le corps ainsi que dans les entêtes de celle-ci, voici un exemple concret :

public HttpEntity generateHttpEntity(Credentials credentials, Object reqBody) {
        HttpHeaders reqHeaders = new HttpHeaders();
        reqHeaders.add(HttpHeaders.COOKIE, String.format("JSESSIONID=%s", credentials.getJsessionId()));
        reqHeaders.set(HttpHeaders.AUTHORIZATION, String.format("Basic %s", credentials.getBasicAuthorizationToken()));

        return new HttpEntity<>(reqBody, reqHeaders);
    }

Cet objet "HttpEntity" est utilisé ensuite lors de l'appel REST, nous voyons ici comment transmettre l'identifiant de session Tomcat par exemple, un jeton d'authentification et un body à partir d'un objet particulier.

Appel HTTP pour récupérer l'information

Plusieurs méthodes existent avec RestTemplate : getForEntity, getForObject, postForEntity, postForObject, exchange, delete, execute, etc.
Voici une méthode générique qui devrait vous permettre de faire pratiquement tout ce dont vous avez besoin.

protected <T> Optional<T> callForEntity(URI targetUrl, HttpMethod httpMethod, Class<T> responseType, HttpEntity httpEntity) {
        Optional<T> optionalEntity = Optional.empty();

        try {
            RestTemplate restTemplate = new RestTemplate();
            ResponseEntity<T> entity = restTemplate.exchange(targetUrl, httpMethod, httpEntity, responseType);

            optionalEntity = Optional.ofNullable(entity.getBody());

        } catch (RestClientException e) {
            log.error("An error occurred while calling " + httpMethod + " " + targetUrl + " " + httpEntity, e);
        }

        return optionalEntity;
    }

Exemples concrets

  • Récupération d'un compte utilisateur
public Optional<User> get(String userId, Credentials credentials) {
        // request to api
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString("http://..../api").path("/v1/users/{user_id}");
        URI targetUrl = uriBuilder.buildAndExpand(userId).toUri();

        // call
        return callForEntity(targetUrl, User.class, generateHttpEntity(credentials));
    }
  • Récupération d'une liste de départements
public List<StoreDepartment> getAll(Credentials credentials) {
        // request to api
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString("http://..../api").path("/v1/departments");
        URI targetUrl = uriBuilder.build().toUri();

        // call
        return callForEntity(targetUrl, Department[].class, generateHttpEntity(credentials))
                .map(departments -> Arrays.stream(departments).collect(Collectors.toList()))
                .orElse(Lists.newArrayList());
    }

Personnaliser l'instance RestTemplate

Il est possible de personnaliser la façon dont la requête HTTP va s'effectuer vers l'API cible, en paramétrant par exemple la durée de la requête avant de déclencher une TimeoutException, le façon dont les objets vont être sérialiser avec Jackson, etc.
Voici un exemple de Factory qui permet de récupérer une instance de RestTemplate personnalisé :

public class ApiRestTemplateFactory {

    private ObjectMapper objectMapper;

    /**
     * Crée une nouvelle instance de RestTemplate
     */
    public RestTemplate getRestTemplate() {
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
        requestFactory.setReadTimeout(0);
        requestFactory.setConnectTimeout(0);
        requestFactory.setConnectionRequestTimeout(0);

        RestTemplate restTemplate = new RestTemplate(requestFactory);

        // custom json
        List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
        messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper()));
        restTemplate.setMessageConverters(messageConverters);

        return restTemplate;
    }

    public ObjectMapper objectMapper() {
        if (objectMapper == null) {
            objectMapper = new ObjectMapper();
            objectMapper.findAndRegisterModules();
            objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
            objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
            objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
        }
        return objectMapper;
    }

}