mardi 10 mai 2011

REST avec Jersey!

Les architectures REST sont une nouvelle forme d'architecture exprimant un certain retour aux sources. En effet, ce type d'architecture tire sa puissance des standards éprouvés du Web, en particulier du protocole HTTP.

Rappel HTTP


HTTP est un protocole orienté ressource, c'est-à-dire qu'il est dédié à la manipulation de ressources à travers un réseau et constitue un fondement de l'Internet et du World Wide Web. Il repose sur TCP et se traduit sous 2 formes :

La requête

Connexion effectuée depuis un client vers un serveur, elle identifie une ressource via son URL et donne un ordre à effectuer au serveur. Exemple :

GET http://www.google.com/

Les différents ordres sont :

  • (C) POST : pour créer une ressource,

  • (R) GET : pour lire une ressource,

  • (U) PUT : pour modifier une ressource,

  • (D) DELETE : pour supprimer une ressource,

  • ..., et bien d'autres encore, ...


Le format que nous utilisons tous (y compris en consultant ce billet) avec notre navigateur est codifié de la manière suivante : text/html. D'autre format sont également codifiés par la RFC 2046, on y retrouve notamment application/xml pour le format XML, application/json pour un flux JSON, ... Exemple :

Accept: application/xml, application/json, text/plain


La réponse

Connexion effectuée par le serveur vers le client ayant effectuée une requête afin de retourner le resultat de celle-ci. Elle est caractérisée par son code de retour, et eventuellement un contenu dont le format est précisé. Exemple :

HTTP/1.1 200 OK
<html>
...
</html>

Les codes de retour varient selon le résultat de l'exécution de la requête :

  • 200 : retour normal car tout va bien,

  • 201 : retour indiquant que la ressource a bien été créée,

  • 404 : la ressource n'a pu être trouvée

  • ..., et bien plus encore, ...



De la même manière qu'un client indique les représentations qu'il est en mesure de comprendre, le serveur indique dans l'entête de la réponse la représentation qu'il a choisit. Exemple :

Content-Type: text/html


Un premier pas en JEE


Maintenant que nous avons révisé les bases, allons plus loin avec un exemple d'implémentation en Java. Commençons par jetter un oeil sur la classe HttpServlet: avez vous remarqué ces méthodes, elles ne vous rappellent rien ?

Il s'agit des méthodes qu'il convient d'implémenter pour écrire une servlet orienté REST. Les codes de retour sont quant à eux disponibles sous forme de constantes dans la classe HttpServletResponse, c'est bien fait non ?

On passe la 6ième avec Jersey


L'écriture d'un servlet faisant du REST est une belle aventure à vivre mais on se heurte à une difficulté majeure : celle de savoir facilement interpréter une URL. C'est-à-dire comment savoir que

http://people.host.ext/user/guillaume.wallet/messages/20110101

est une URL listant les messages datés du premier janvier de l'année deux mille onze de l'utilisateur enregistré sous le nom 'guillaume.wallet', galère non?
JAX-RS est une spéficiation Java EE, incluse depuis la verion 6, ayant pour objet la standardisation de cette démarche REST au sein d'une application Java. L'implémentation de référence est fournit par le projet Jersey. Un tutorial complet est disponible sur le site.

Pom Pom Pom pommmm


Voici le pom qui m'a servi pour cette démarche:

<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.sample.jersy</groupId>
<artifactId>jersey-sample</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<inceptionYear>2011</inceptionYear>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.8</version>
<configuration>
<classpathContainers>
<classpathContainer>org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6</classpathContainer>
</classpathContainers>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
<wtpversion>2.0</wtpversion>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.6</version>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-servlet-api</artifactId>
<version>${tomcat.version}</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
</dependencies>
<properties>
<tomcat.version>7.0.12</tomcat.version>
</properties>
<repositories>
<repository>
<id>maven2-repository.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/2/</url>
<layout>default</layout>
</repository>
</repositories>
</project>

Bonjour toi!


Notre première resource:

package org.sample.jersey.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

/**
* Une ressource polie.
*/
@Path( "/hello/{world}" )
public class HelloWorldResource
{
@GET
@Produces("text/plain")
public String greet(@PathParam("world") String world)
{
return "Hello " + world;
}
}

Et un peu de magie : pour tester notre application, il faut ... une application. Nous allons créer la classe suivante qui suffira à lancer notre premier test:

package org.sample.jersey.rest;

import javax.ws.rs.ApplicationPath;

import com.sun.jersey.api.core.PackagesResourceConfig;

/**
* La racine de l'application REST.
*/
@ApplicationPath( "/resources" )
public class SampleRESTApplication
extends PackagesResourceConfig
{
/**
* On explique à JERSEY que les Resources sont dans le paquet org.sample.jersey.rest.
*/
public SampleRESTApplication()
{
super( "org.sample.jersey.rest" );
}
}

Pour tester, une fois déployée dans le serveur de votre choix (tant qu'il s'agit de Tomcat dans sa version 7), rendez à l'URL suivante :

http://localhost:8080/<nom>/resources/hello/World

où <nom> remplace le nom de votre WebApp. Maintenant, remplacer "World" par ce qui vous chante et constater la magie: si peu de ligne pour un si bel effet.

«Comment tu le veux ta ressource ?»


L'exemple ci-dessous illustre la négociation de contenu.

package org.sample.jersey.rest;

import java.util.HashMap;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;

import org.json.simple.JSONObject;

/**
* Gestion des gens.
*/
@Path( "/users/{username}" )
public class PeopleResource
{
@GET
@Produces( "application/json" )
public String showUserAsJSON( @PathParam( "username" ) String username )
{
User user = findUserByUsername( username );
return user.toJSON();
}

@GET
@Produces( "text/plain" )
public String showUserAsTextPlain( @PathParam("username") String username )
{
User user = findUserByUsername( username );
return user.toString();
}

public PeopleResource()
{
users.put( "guillaume.wallet", new User( "guillaume.wallet", "Guillaume", "Wallet" ) );
users.put( "linus.torvalds", new User( "linus.torvalds", "Linus", "Torvalds" ) );
}

private User findUserByUsername( String username )
{
User user = users.get( username );
return user;
}

/* Une vraie BDD. */
private HashMap users = new HashMap();

/**
* Euh, ... un utilisateur quoi!
*/
private static class User
{
private String username;

private String firstname;

private String lastname;

public User( String username, String firstname, String lastname )
{
super();
this.username = username;
this.firstname = firstname;
this.lastname = lastname;
}

@SuppressWarnings( "unchecked" )
public String toJSON()
{
JSONObject me = new JSONObject();
me.put( "username", username );
me.put( "firstname", firstname );
me.put( "lastname", lastname );
return me.toJSONString();
}

@Override
public String toString()
{
return String.format( "%1s %2s", firstname, lastname );
}
}
}

Selon que vous fassiez :

$> wget --header "Accept: application/json" http://localhost:8080/<nom>/resources/users/guillaume.wallet

ou

$> wget --header "Accept: text/plain" http://localhost:8080/<nom>/resources/users/guillaume.wallet

Le rendu de la ressource change.

Conclusion


Cet article ne constitue qu'un petit pas, c'est maintenant à vous de faire le REST.

Aucun commentaire:

Enregistrer un commentaire