Les services REST sont un ensemble de règle afin de construire des services.
Il a fallu attendre longtemps avant d'avoir une spécification jee pour les services REST.
Il sont plus simple que les services SOAP.
L'approche REST est radicale :
=> Ils sont liés au protocole HTTP.
Ils sont simples à mettre en place mais on fait souvent quelques entorses à la pureté du principe.
On crée la branche jax-rs à partir de la branche master.
$ git checkout master Switched to branch 'master' $ git checkout -b jax-rs Switched to a new branch 'jax-rs'
Jersey est l'implémentation standard java pour les services REST.
Coté serveur, il suffit d'annoter les méthodes avec quelques annotations :
@Path
: le chemin de la ressource (ex : /rest/tasks)@GET
, @PUT
, @POST
, @DELETE
: les actions de l'interface uniforme@Consumes
: routage en fonction du type mime de la requête@Produces
: type de données de la réponseIl existe d'autres framework comme Resteasy et restlet.
Il est même possible (et assez simple) de faire du REST avec spring MVC.
Nous allons partir avec Jersey
<dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey-server</artifactId> <version>2.13</version> </dependency> <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-servlet</artifactId> <version>2.13</version> </dependency>
Nous allons construire un service factice qui dit bonjour.
La première classe à mettre en place est la ressource, dans le package fr.todooz.rest.
import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import org.apache.commons.lang.StringUtils; @Path("/hi/{username}") public class HelloWorldResource { @GET @Produces(MediaType.TEXT_PLAIN) public String hi(@PathParam("username") String username) { return "Hello " + StringUtils.defaultIfEmpty(username, "unknown"); } }
Ensuite, il faut déclarer une application REST dans le même package.
import org.glassfish.jersey.server.ResourceConfig; public class TodoozApplication extends ResourceConfig { public TodoozApplication() { // scan des ressources dans fr.todooz.rest packages("fr.todooz.rest"); } }
Cette application enregistre toutes les ressources de notre service en faisant un scan de package.
Le dernier point est la déclaration de l'application REST dans le web.xml
<!-- Jersey servlet --> <servlet> <servlet-name>rest</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>fr.todooz.rest.TodoozApplication</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>rest</servlet-name> <url-pattern>/rest/*</url-pattern> </servlet-mapping>
Avec le serveur tomcat démarré, le service répond sur http://localhost:8080/rest/hi/you
Nous allons mettre en place un test d'intégration qui va valider le bon fonctionnement de notre service.
Afin que jenkins n'éxécute pas ces tests avec les tests unitaires, nous pouvons :
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.16</version> <configuration> <excludes> <exclude>**/integration/*Test.java</exclude> </excludes> </configuration> </plugin>
On crée donc la classe HelloWorldResourceTest dans le package fr.todooz.integration de src/test/java.
Il n'y a pas de génération de client comme pour les services SOAP mais juste un jar.
<dependency> <groupId>org.glassfish.jersey.core</groupId> <artifactId>jersey-client</artifactId> <version>2.13</version> </dependency>
Après quoi on peut écrire.
import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.MediaType; import org.junit.Assert; import org.junit.Test; public class HelloWorldResourceTest { @Test public void hi() { Client client = ClientBuilder.newClient(); WebTarget target = client.target("http://localhost:8080").path("/rest/hi/jax-rs"); Assert.assertEquals("Hello jax-rs", target.request(MediaType.TEXT_PLAIN_TYPE).get(String.class)); } }
La mise en place d'un web service REST est relativement aisée.
D'une implémentation à l'autre, il peut y avoir quelques différences pour la mise en place des ressources.
Appeler un service REST est simple.
L'api des services REST est uniforme (toujours la même) : GET, POST, PUT et DELETE.
L'implémentation de REST est exigeante si l'on respecte les 3 niveaux du pattern. Mais même si on ne le fait pas, on peut se servir de bonnes pratiques liées à REST afin construire son service.
Comme pour le service SOAP, nous allons exposer nos tâches.
Afin de réutiliser notre service de tâches, nous aimerions pouvoir écrire :
@Path("/tasks") public class TasksResource { @Inject private TaskService taskService; @GET @Produces(MediaType.APPLICATION_JSON) public List<Task> tasks() { return taskService.findAll(); } }
On rajoute la librairy (MOXy) pour la transformation en json.
<dependency> <groupId>org.glassfish.jersey.media</groupId> <artifactId>jersey-media-moxy</artifactId> <version>2.13</version> </dependency>
JSON binding support via MOXy is a default and preferred way of supporting JSON binding in your Jersey applications since Jersey 2.0. When JSON MOXy module is on the class-path, Jersey will automatically discover the module and seamlessly enable JSON binding support via MOXy in your applications.
Et comme pour les services web, il existe un pont entre jersey et spring.
Il faut utiliser l'extension spring de jersey.
<dependency> <groupId>org.glassfish.jersey.ext</groupId> <artifactId>jersey-spring3</artifactId> <version>2.13</version> </dependency>
Jersey provides an extension to support Spring DI. This enables Jersey to use Spring beans as JAX-RS components (e.g. resources and providers) and also allows Spring to inject into Jersey managed components.
Ce qui donne sur http://localhost:8080/rest/tasks
[ {"id":42,"createdAt":1352199458514,"title":"Read Test Driven Development","text":null,"date":1352199458514, "tags":"java,test driven,junit","tagArray":["java","test driven","junit"]}, {"id":41,"createdAt":1352199458511,"title":"Read Effective Java","text":null,"date":1352199458511, "tags":"java","tagArray":["java"]}, {"id":40,"createdAt":1352199458496,"title":"Read Pro Git","text":null,"date":1352199458496, "tags":"scm,git","tagArray":["scm","git"]} ]
En se basant sur la documentation de Jersey et ce post sur les listes d'entités, ecrire un test d'intégration très simple pour ce service.