services rest & jersey
DESCRIPTION
Introduction au paradigme REST et à son implémentation via le framework JerseyTRANSCRIPT
REST & Jersey
REST ?De diou - Qu'es aquò ?
REST ?
Style d'architecture
HTTP
SOA : REST vs SOAP
Principes RESTLes 5 commandements
Aucun état tu n'aurasStateless
Historiquement avec le web, maintien de la session côté client
Aujourd'hui les clients sont capables de gérer un état de session
Adressables tes ressources serontAdressabilité
Utiliser la spécification HTTP
Une ressource = une URI
Uniforme l'accès à tes ressources sera - 1Accès via un sous ensemble des verbes HTTP
GETPUT
DELETEPOSTHEAD
Uniforme l'accès à tes ressources sera - 22 notions permettant de distinguer PUT et POST
Sûr : L’invocation d’une méthode sûre ne change pas l’état du serveur
Idempotent : signifie que le résultat d’une action sera le même quel que soit le nombre d’exécutions de cette action.
Uniforme l'accès à tes ressources sera - 3GETSur & Idempotent
Obtenir la représentation d’une ressource
Uniforme l'accès à tes ressources sera - 4PUTIdempotent
Correspond à un Create ou bien un Update
Uniforme l'accès à tes ressources sera - 5DELETEIdempotent
Supprimer une ressource du serveur
Uniforme l'accès à tes ressources sera - 6POSTNON Idempotent
Modifier une ressource du serveur. Seule opération non idempotente de la spécification HTTP
Correspond à un Create ou bien un Update
Uniforme l'accès à tes ressources sera - 7HEADSur et idempotent
Similaire à un GET, mais ne retourne pas le corps de la réponse (seulement code retour + éventuels en-têtes)
Orienté représentation tu seras
exemple :
GET sur une URI => représentation de la ressource en XML ou JSON (ou autre) suivant la phase de Content Negotiation
HATEOAS tu respecterasHypermedia As The Engine Of Application State
Navigation via des liens inclus dans la représentation des ressources
Spécification d'une ressourceMiam des specs
Contrat d'interface
Choix des ressources+
Choix des verbes+
Choix des codes retour=
Description du contrat
Et en Java ?
JSR 311JAX-RS
@Path@GET @PUT @POST @DELETE @HEAD@Produces@Consumes
Frameworks sur le marchéJersey *RESTEasyRESTLetCXF
Donc, Jersey
Jersey
Implémentation de référence de la JSR
Des fonctionnalités supplémentaires
(une prez encore mieux...)
http://matthewturland.com/slides/jersey/
Si j'avais vu ça avant j'aurai pas fait ces slides...
JerseyMise en place du projet
Dépendances maven (v1.17.1)
Attention à asm.jar (collisions dépendances Hibernate ou tout autre projet utilisant CGLIB)
http://mathieuhicauber-java.blogspot.fr/2012/12/jax-rs-jersey-on-was-61-asm-classreader.html
Jersey Contrôleurweb.xmlDéclaration dans le web.xml du package à scanner<servlet> <description>Jersey Servlet</description> <display-name>ServletContainer</display-name> <servlet-name>ServletContainer</servlet-name> <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class> <!-- le package scanné par Jersey à la recherche d'annotations --> <init-param> <param-name>com.sun.jersey.config.property.packages</param-name> <param-value>fr.laposte.intra.courrier.recif.rest</param-value> </init-param> ...</servlet> <!-- Le mapping d'URL voulu--><servlet-mapping> <servlet-name>ServletContainer</servlet-name> <url-pattern>/resources/*</url-pattern></servlet-mapping>
Jersey Contrôleur @Path
@Path("/artists")
public class ArtistResource {…}
... Et notre ressource est exposée !
(Comparer ça à un cycle de développement SOAP...)
@Path : Définir le chemin d'accès@[VERB] : Définir le verbe autorisé
@Path("helloWorld")@GETpublic String helloMyFriend() {...}
Jersey Contrôleur @Path @[VERB]
@PathParam : injecter un paramètre dans la signature de la méthode défini dans @Path
@Path("hello/{aName}")@GETpublic String getCustomizedHelloWorld(
@PathParam("aName") String name) {...}
Jersey Contrôleur@PathParam - 1
Possibilité d'injecter plusieurs paramètres ou de spécifier une regexp pour la partie dynamique
@Path("date/{year}-{month}-{date}") // plusieurs params
@Path("{id : \\d+}") // numériques seulement
@Path("username/{username : [a-z]+}") //alpha minuscules seulement
Jersey Contrôleur@PathParam - 2
Permet d'injecter les valeurs provenant des paramètres de requête
@Path("hello")@GETpublic String getResponseWithQueryParam(
@QueryParam("name") String nameQueryParameter) {...}
URI : http://[JAX-RS-URI]/hello?name=Mathieu
Jersey Contrôleur@QueryParam
Permet d'injecter les valeurs provenant des en-têtes de la requête HTTP
@Path("hello")@GETpublic String getResponseWithHeaderParam(
@HeaderParam("headerParam") String someHeaderParam) {...}
Jersey Contrôleur@HeaderParam
Permet d'injecter les valeurs extraites des cookies
@Path("hello")@GETpublic String getResponseWithCookieParam(
@CookieParam("cookieParam") String aParamInjectedFromCookies) {...}
Jersey Contrôleur@CookieParam
Permet d'injecter les valeurs de champs de formulaire
@Path("handleForm")@POSTpublic String handleForm(
@FormParam("username") String userName,@FormParam("password") String password) {...}
Jersey Contrôleur@FormParam - 1
Problème : avec un gros formulaire, les signatures deviennent illisibles
@Path("handleForm")@POSTpublic String handleForm(
@FormParam("value1") String value1,@FormParam("value2") String value2,@FormParam("value3") String value3,@FormParam("value4") String value4,...) {...}
Jersey Contrôleur@FormParam - 2
Jersey permet d'injecter un bean
@Path("handleForm")@POSTpublic String handleForm(
@InjectParam CustomFormValuesHandlerType form) {...}
Le type CustomFormValuesHandlerType est un POJO reprenant les valeurs du form en attribut.
Les attributs sont annotés avec @FormParam
Jersey Contrôleur@InjectParam* - 1
@Produces@Consumes
Indique le type de représentation qu'est capable de consommer ou de produire la méthode
Utilisé par le moteur Jersey en conjonction avec le header Accept envoyé par le client pour choisir la bonne méthode et le bon type de retour
Jersey ContrôleurContent Negotiation - 1
Header client : @Accept
Accept: application/json; q=1.0, application/xml; q=0.8, text/html; q=0.6, text/plain; q=0.4, text/*; q=0.2, */*; q=0.1
Jersey ContrôleurContent Negotiation - 2
Retourner une javax.ws.core.Response
@Path("/id/{artistId}") @GET @Produces({ MediaType.APPLICATION_JSON }) public Response getArtistByIdJavaxResponse (
@PathParam("artistId") Integer artistId)*throws ArtistDoesNotExistException {
Artist artist = service.getArtistById(artistId); return Response.ok().entity(artist).build(); }
Jersey ContrôleurRetourner la ressource - 1
... ou bien une entité ou liste d'entités
@Path("/id/{artistId}") @GET @Produces({ MediaType.APPLICATION_JSON }) public Artist getArtistByIdEntityResponse(
@PathParam("artistId") Integer artistId) throws ArtistDoesNotExistException {
Artist artist = service.getArtistById(artistId);
return artist; }
Jersey ContrôleurRetourner la ressource - 2
Fluent Interface idiom (ou EDL)
return Response.ok().entity(artist).header("HeaderName","headerValue").lastModified(lastModified).expires(expiresDate).build();
Jersey ContrôleurRetourner la ressource - 3
Déclaration dans le web.xml
<init-param><param-name>com.sun.jersey.spi.container.ContainerRequestFilters</param-name><param-value>fr.laposte.intra.courrier.recif.rest.filters.AccreditationFilter;com.sun.jersey.api.container.filter.LoggingFilter</param-value>
</init-param>
<init-param><param-name>com.sun.jersey.spi.container.ContainerResponseFilters</param-name><param-value>com.sun.jersey.api.container.filter.LoggingFilter ; fr.laposte.intra.courrier.recif.rest.filters.CORSFilter</param-value>
</init-param>
Jersey Filters - 1
Filtres de Request ou Response
Existence de filtres prédéfinis
Possibilité de créer ses filtres
Jersey Filters - 2
Permet de lancer des codes erreurs HTTP spécifique en fonction d'un type d'exception
public class ArtistDoesNotExistExceptionMapper implements ExceptionMapper<ArtistDoesNotExistException> { @Override public Response toResponse(ArtistDoesNotExistException exception) { return Response.status(Response.Status. NOT_FOUND) .entity(exception.getMessage()).type(MediaType. TEXT_PLAIN) .build(); } }
Jersey Exception mapping
Classe de support : étendre JerseyTest
@Override public WebAppDescriptor configure() { return new WebAppDescriptor.Builder("com.foo.workshops.jersey").build(); } @Override public TestContainerFactory getTestContainerFactory() { return new GrizzlyWebTestContainerFactory(); }
JerseyTUAs
Cross Origin Resource Sharing
Ajax XDomain => Interdit, mais cas fréquent dans le cadre d'une API REST
Solutions : jsonP, proxy, CORS
CORS (mode simple) : permettre au client de récupérer des ressources en Ajax provenant d'un autre domaine
JerseyCORS - 1
Requête simple (GET)
Client POST /cors HTTP/1.1Origin: http://api.bob.comHost: api.bob.com
Serveur Access-Control-Allow-Origin: http://api.bob.comAccess-Control-Expose-Headers: FooBarContent-Type: text/html; charset=utf-8
JerseyCORS - 2
Requête complexes (PUT - DELETE)
http://www.html5rocks.com/en/tutorials/cors/?redirect_from_locale=fr
JerseyCORS - 3
Evolutions => exige une montée de version a minima si perte de compatibilité ascendente. 3 courants se dégagent :
Versioning dans l'URL
GET http://....../v1/resource/id/12345GET http://....../v2/resource/id/12345
++ Simple !- casse le principe d'unicité d'URI pour une ressource- expose un paramètre technique dans l'URI
RESTVersioning -1
Versioning via Custom Header
client Header :Version : 1.0
++ Simple-- peut poser problème derrière des proxies
RESTVersioning - 2
Versioning via Content Negotiation
http://barelyenough.org/blog/2008/05/versioning-rest-web-services
++ HATEOAS compliant-- plus complexe à mettre en oeuvre, nécessite des vendor MIME Media Types
RESTVersioning - 3