Feign, encore un client HTTP ?
Depuis les prémices de Java, il est possible de requêter sur le protocole HTTP, soit de manière native avec le package java.net, soit avec l’un des nombreux clients qui ont vu le jour. Certaines librairies sont éprouvées depuis quelques temps déjà. On peut ainsi parler de :
- Apache HttpClient,
- Jersey,
- RestTemplate (Spring),
- CXF,
- JAXRS,
- Google Http Client,
- Unirest,
- Restlet,
- etc.
À l’origine développé et utilisé par Netflix OSS depuis quelques années, Feign est aujourd’hui proposé par OpenFeign comme librairie open-source. Quelles sont ses possibilités et comment l’utiliser ?
Spring Cloud propose une définition complète du client HTTP :
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka to provide a load balanced http client when using Feign.
Le but de cette libraire est donc très clairement de simplifier et minimiser l’écriture du requêtage HTTP tout en s’inscrivant dans la lignée des “Convention Over Configuration”.
Un premier exemple : GET
N.B. : une des agences Ippon étant basée à Bordeaux, la thématique de notre Api s’organise autour du vin 🙂
Plutôt simple, non ? Une interface est déclarée avec une méthode annotée @RequestLine qui définit le verbe HTTP et le contexte à utiliser. Il suffit ensuite de créer le client Feign en ciblant la précédente interface et l’URL à appeler. Comme nous pouvons le voir, la déclaration du client repose sur une [fluent interface](https://en.wikipedia.org/wiki/Fluent_interface) afin de favoriser la déclaration/lecture/…POST et paramètre de contexte
L’exemple propose de récupérer un vin par son identifiant renseigné dans le contexte, puis d’enregistrer un nouveau vin via une requête POST. Trois nouvelles choses :- l’utilisation de l’annotation @Param pour renseigner un paramètre dans le contexte,
- @Header pour définir le content-type,
- l’affectation d’un “encoder” au niveau du client Feign afin de spécifier la sérialisation à effectuer.
Le principe est identique pour tous les verbes HTTP.
Encodeurs et décodeurs
Si votre interface retourne autre chose que Response, String, byte[] ou void, il est obligatoire de configurer un décodeur ou encodeur. De façon native, Feign en propose plusieurs couvrant majoritairement les usages actuels. On peut citer :
- Gson : GsonEncoder/GsonDecoder
ErrorDecoder
Dans le cas d’erreurs, Feign envoie une unique exception de type FeignException. Il est cependant toujours conseillé de gérer les différents types d’erreur. Il suffit simplement ici de créer une classe implémentant ErrorDecoder et de l’affecter au builder :
L’affectation s’effectue au niveau du client Feign : ## HeadersFeign propose différentes méthodes pour spécifier les headers HTTP :
- Au niveau de l’API avec l’annotation @Headers sur l’interface.
- Sur une méthode spécifique avec là encore l’annotation @Headers.
- De manière dynamique en ajoutant l’annotation @HeaderMap sur le paramètre typé Map<String, Object> de la méthode.
Par défaut, les logs HTTP ne sont pas récupérés. Il est toutefois possible de les logger. Avec SLF4J (à importer comme module complémentaire) :
Ou avec JavaLogger : Il existe quatre niveaux de logs HTTP :- Logger.Level.BASIC
feign.Logger - [WineAPI#getWineDesignations] ---> GET http://api.wine.bordeaux.ippon.fr/wines HTTP/1.1 feign.Logger - [WineAPI#getWineDesignations] <--- HTTP/1.1 200 OK (59ms)
- Logger.Level.HEADERS (BASIC + Headers)
feign.Logger - [WineAPI#getWineDesignations] ---> GET http://api.wine.bordeaux.ippon.fr/wines HTTP/1.1 feign.Logger - [WineAPI#getWineDesignations] ---> END HTTP (0-byte body) feign.Logger - [WineAPI#getWineDesignations] <--- HTTP/1.1 200 OK (49ms) feign.Logger - [WineAPI#getWineDesignations] Server: Jetty(8.y.z-SNAPSHOT) feign.Logger - [WineAPI#getWineDesignations] Content-Length: 6431 feign.Logger - [WineAPI#getWineDesignations] Content-Type: application/json feign.Logger - [WineAPI#getWineDesignations] <--- END HTTP (6431-byte body)
- Logger.Level.FULL (HEADERS + contenu de la réponse)
feign.Logger - [WineAPI#getWineDesignations] ---> GET http://api.wine.bordeaux.ippon.fr/wines HTTP/1.1 feign.Logger - [WineAPI#getWineDesignations] ---> END HTTP (0-byte body) feign.Logger - [WineAPI#getWineDesignations] <--- HTTP/1.1 200 OK (48ms) feign.Logger - [WineAPI#getWineDesignations] Server: Jetty(8.y.z-SNAPSHOT) feign.Logger - [WineAPI#getWineDesignations] Content-Length: 6431 feign.Logger - [WineAPI#getWineDesignations] Content-Type: application/json feign.Logger - [WineAPI#getWineDesignations] feign.Logger - [WineAPI#getWineDesignations] ["bordeaux", "bourgogne", "graves", "jurançon"] feign.Logger - [WineAPI#getWineDesignations] <--- END HTTP (6431-byte body)
- Logger.Level.NONE
Aucun log HTTP n’est tracé...
Intercepteur de requêtes
Dans le cas où toutes les requêtes doivent être modifiées, peu importe la cible, Feign propose d’utiliser des intercepteurs de requêtes. L’exemple ci-dessous ajoute la date du jour comme Header à chaque requête :
Un autre exemple courant est l’authentification avec la classe feign.auth.BasicAuthRequestInterceptor : ## L’intégration avec …Un autre but de Feign, tout aussi important, est de s’intégrer facilement avec Netflix OSS et d’autres librairies Open Source (Spring Source en est un exemple).
Hystrix
Cette librairie Netflix OSS permet d’utiliser le pattern Circuit Breaker. Le module HystrixFeign englobe ainsi les requêtes HTTP dans le Hystrix et configure ainsi le Circuit Breaker. Après avoir intégré le module dans le classpath, il reste à configurer Feign pour utiliser feign.hystrix.HystrixInvocationHandler :
### RibbonRibbon sert de Load Balancer et intervient comme Routeur Dynamique. Il est généralement couplé avec Eureka pour la découverte des services. Son intégration, en tant que module Feign, permet de surcharger la résolution d’URL et de profiter des caractéristiques de Ribbon.
Routage dynamique
#### Load balancing ### Spring CloudSpring Cloud Netflix est basé, comme son nom l’indique, sur les composants Netflix OSS. Reposant sur les principes d’Auto Configuration et Convention over Configuration, l’utilisation de Feign est d’autant plus simplifiée. Plus besoin de définir le client, il est configuré directement par Spring.
## Les autres paramètres du client FeignOptions
Le builder Feign propose de spécifier des options. Actuellement, les options par défaut se focalisent sur les timeout de connexion ou de lecture. L’implémentation par défaut des options est la suivante :
Il est ainsi possible de surcharger cette classe pour modifier les valeurs par défaut. Il suffit ensuite de les fixer dans le builder : ### ContractIl est possible de définir un contrat sur le client. Des nouvelles annotations ainsi que d’autres comportements peuvent être définis à travers ce dernier. Par défaut, le contrat utilisé est la classe feign.contract.Default. Cette classe s'occupe de parser et valider les différents paramètres, interfaces et annotations définis sur la classe cible (paramètre de la méthode target). Dans notre exemple, l’interface WineAPI sera vérifiée ainsi que toutes les méthodes définies.
Il est encore une fois possible de définir le comportement d’un nouveau contrat ou alors d'utiliser le module JAXRS et sa classe feign.jaxrs.JAXRSContract.
Retryer
Un retryer par défaut est configuré dans le client Feign. Couplé avec l’ErrorDecoder par défaut, il est utilisé uniquement si le header de la réponse possède la propriété “retry-after”.
Au final
Vous connaissez à présent les principales caractéristiques de ce client HTTP qu'est Feign. Simple d’utilisation, sa maîtrise en est d’autant plus rapide. Son intégration avec d’autres librairies est grandement facilitée. Bref, Feign possède les principales caractéristiques pour en faire l’un des principaux clients HTTP.
“You can’t FEIGN ignorance now”.
Sources
https://github.com/OpenFeign/feign
http://cloud.spring.io/spring-cloud-netflix/spring-cloud-netflix.html#spring-cloud-feign
https://dzone.com/articles/the-netflix-stack-using-spring-boot-part-3-feign