REST pour les développeurs Java, partie 2: Restlet pour les fatigués

L'API Restlet open source réduit la charge de travail impliquée dans la création et la consommation d'API RESTful en Java. Dans ce deuxième article de la série REST pour développeurs Java , Brian Sletten vous présente Restlet et parcourt un exemple d'application en déployant ses interfaces dans les conteneurs de servlet que vous utilisez aujourd'hui, tout en vous préparant aux systèmes du futur. Brian présente également brièvement JSR 311: JAX-RS, l'effort de Sun pour intégrer les API RESTful à la pile Java EE.

Les développeurs Java s'intéressent depuis longtemps au style architectural REST, mais peu ont encore parcouru la distance entre le monde familier des objets et le monde des ressources RESTful. Bien que nous aimions le fait que les services RESTful puissent être produits ou consommés par d'autres langages, nous détestons avoir à convertir des données vers et à partir de flux d'octets. Nous détestons avoir à penser à HTTP lors de l'utilisation d'outils comme Apache HTTP Client. Nous regardons avec envie les objets créés par la wsdl2javacommande, ce qui nous permet de passer des arguments dans un service SOAP aussi facilement que n'importe quel autre appel de méthode, balayant les détails de l'appel d'un service distant sous le tapis. Et nous trouvons que le modèle de servlet est juste légèrement trop déconnecté des ressources produites. Qu'il suffise de dire que pendant que nous avons pu pour créer des services RESTful à partir de zéro, cela n'a pas été une expérience agréable.

REST pour les développeurs Java

Lisez la série:

  • Partie 1: Il s'agit de l'information
  • Partie 2: Restlet pour les fatigués
  • Partie 3: NetKernel

Les problèmes politiques ont parfois aggravé les obstacles techniques. De nombreux gestionnaires estiment que les services Web basés sur SOAP sont le moyen prescrit de créer des architectures orientées services (SOA) dans Java EE. Cela change avec l'émergence d'activités importantes telles que JSR 311, JAX-RS: l'API Java pour les services Web RESTful, que vous découvrirez dans cet article. Si rien d'autre, cet effort légitime le développement RESTful dans l'espace JEE.

Pendant ce temps, l'aide est arrivée. De manière élégante, le framework Restlet open source permet d'éviter facilement les problèmes épineux qui peuvent résulter de l'utilisation de la technologie JEE traditionnelle pour créer et consommer des services RESTful.

Les racines de Restlet

Dans un effort pour résoudre certains des problèmes techniques liés à la réalisation de REST avec Java, Jérome Louvel, un consultant logiciel français, a cherché à créer un cadre qui fournirait un ajustement plus naturel. Il a d'abord examiné l'environnement NetKernel comme point de départ. Autant qu'il l'aimait, cela ne correspondait pas parfaitement au cadre axé sur l'API qu'il cherchait à rendre disponible. L'expérience l'a aidé à influencer sa réflexion sur le genre de choses qu'un environnement orienté REST peut offrir, cependant. (Le prochain article de cette série explorera plus complètement NetKernel.)

Pendant que Louvel travaillait sur son cadre, il a développé trois objectifs:

  • Les actions simples doivent être simples pour une utilisation de base. Les valeurs par défaut doivent fonctionner avec un minimum d'effort, mais permettent également des configurations plus complexes.
  • Le code écrit dans cette API doit être portable entre les conteneurs. Bien que les systèmes basés sur des servlets puissent être déplacés entre des conteneurs tels que Tomcat, Jetty et IBM WebSphere, Louvel avait une vision plus large en tête. La spécification du servlet est liée à HTTP et à un modèle d'E / S bloquant. Il voulait que son API soit séparable des deux et déployable dans les conteneurs utilisés aujourd'hui. Il souhaitait également qu'ils soient utilisables avec peu d'effort dans des conteneurs alternatifs et émergents tels que Grizzly, AsyncWeb et Simple Framework.
  • Cela devrait enrichir non seulement le côté serveur de la production d'interfaces RESTful en Java, mais également le côté client. La HttpURLConnectionclasse et le client HTTP Apache sont de niveau trop bas pour s'intégrer proprement et directement dans la plupart des applications.

Avec ces objectifs à l'esprit, il a entrepris de produire l'API Restlet. Après quelques années de flux, l'API est devenue stable et une communauté s'est développée autour d'elle. Aujourd'hui, l'API principale dispose d'une base d'utilisateurs dynamique et une activité importante est en cours pour soutenir l'intégration avec d'autres boîtes à outils et initiatives telles que JAX-RS. (Louvel fait maintenant partie du groupe d'experts JAX-RS.)

Principes de base de Restlet

Un serveur de base avec l'API Restlet ne pourrait pas être plus facile, comme le montre le listing 1.

Liste 1. Un serveur de base avec Restlet

package net.bosatsu.restlet.basic; import org.restlet.Restlet; import org.restlet.Server; import org.restlet.data.MediaType; import org.restlet.data.Protocol; import org.restlet.data.Request; import org.restlet.data.Response; public class SimpleServer { public static void main(String[]args) throws Exception { Restlet restlet = new Restlet() { @Override public void handle(Request request, Response response) { response.setEntity("Hello, Java RESTafarians!", MediaType.TEXT_PLAIN); } }; // Avoid conflicts with other Java containers listening on 8080! new Server(Protocol.HTTP, 8182, restlet).start(); } }

Cette application ne fait pas grand-chose (sauf répandre la bonne humeur), mais elle montre deux des principes fondamentaux de Restlet. Premièrement, les choses simples sont simples. Des activités plus complexes sont certainement possibles, mais vous ne vous en souciez que lorsque vous en avez besoin. REST ne manque pas de capacité à appliquer la sécurité, les contraintes, la négociation de contenu ou d'autres tâches importantes. Celles-ci restent des activités largement orthogonales, bien distinctes du processus de satisfaction d'une API RESTful. Vous superposez la complexité au besoin.

Deuxièmement, le code du listing 1 est conçu pour être portable parmi les types de conteneurs. Notez qu'il ne spécifie pas de conteneur. Restlets sont les ressources réelles qui répondent finalement aux demandes. Il n'y a pas de distinction entre le conteneur traitant la demande et le répondeur de la ressource informationnelle, comme cela peut être le cas dans le modèle de servlet. Si vous tapez le code dans un IDE et ajoutez des dépendances sur les archives org.restlet.jaret com.noelios.restlet.jar, vous pouvez exécuter l'application et vous devriez voir un message de journal comme celui-ci:

Dec 7, 2008 11:37:32 PM com.noelios.restlet.http.StreamServerHelper start INFO: Starting the internal HTTP server

Pointez un navigateur vers //localhost:8182, et vous devriez voir le message d'accueil amical.

Dans les coulisses, le org.restlet.jarcontient toutes les principales interfaces de cette API. Le com.noelios.restlet.jarcontient une implémentation de base de ces interfaces et fournit une capacité de gestion HTTP par défaut. Vous ne voudrez pas entrer en production avec ce moteur HTTP, mais il est exceptionnellement pratique à des fins de développement et de test. Vous n'avez pas besoin de démarrer un conteneur majeur pour tester votre code RESTful. Les tests unitaires et d'intégration peuvent donc être beaucoup plus faciles.

L'exemple de la liste 1 utilise beaucoup de comportements par défaut pour créer une Applicationinstance par défaut (je parlerai Applicationdans l'exemple suivant) et écouter les requêtes de protocole HTTP sur le port 8182. La StreamServerHelperclasse commence à écouter sur ce port et envoie les requêtes à l' Restletinstance comme ils entrent.

L'objectif de Louvel de prendre en charge Java RESTful côté client est également atteint avec facilité, comme vous pouvez le voir dans le Listing 2.

Listing 2. Un client Restlet

package net.bosatsu.restlet.basic; import java.io.IOException; import org.restlet.Client; import org.restlet.data.Protocol; public class SimpleClient { public static void main(String [] args) throws IOException { String uri = (args.length > 0) ? args[0] : "//localhost:8182" ; Client client = new Client(Protocol.HTTP); client.get(uri).getEntity().write(System.out); } }

Avec le SimpleServertoujours en cours d'exécution, le lancement de ce nouveau code client avec les mêmes dépendances JAR devrait imprimer le message d'accueil convivial sur la console. Imprimer la sortie dans ce style ne fonctionnerait évidemment pas pour les types MIME orientés binaire mais, encore une fois, c'est un point de départ pratique.

Exemple non CRUD

La plupart des exemples pédagogiques REST montrent des services CRUDish (Créer, Récupérer, Mettre à jour, Supprimer) autour d'objets simples. Bien que ce style fonctionne certainement bien avec REST, ce n'est en aucun cas la seule approche qui ait du sens - et la plupart d'entre nous en ont assez des exemples CRUD, de toute façon. L'exemple suivant illustre les bases d'une application Restlet en encapsulant le vérificateur d'orthographe open source Jazzy.

REST consiste à gérer les informations, pas à invoquer un comportement arbitraire, vous devez donc faire preuve de prudence lorsque vous envisagez une API orientée comportement comme Jazzy. L'astuce consiste à traiter l'API RESTful comme un espace d'informations pour les mots qui existent et n'existent pas dans les dictionnaires utilisés. Le problème peut être résolu de différentes manières, mais cet article définira deux espaces d'informations. /dictionaryest utilisé pour gérer les mots du dictionnaire. /spellcheckerest utilisé pour trouver des suggestions de mots similaires à des mots mal orthographiés. Les deux se concentrent sur l'information en considérant l'absence ou la présence de mots dans les espaces d'information.

Dans une architecture RESTful, cette commande HTTP pourrait renvoyer une définition d'un mot dans le dictionnaire:

GET //localhost:8182/dictionary/word

Il renverrait probablement le code de réponse HTTP "Not Found" pour les mots qui ne sont pas dans le dictionnaire. Dans cet espace d'informations, il est bon d'indiquer que les mots n'existent pas. Jazzy ne fournit pas de définitions pour les mots, je vais donc laisser le retour du contenu comme exercice pour le lecteur.

Cette prochaine commande HTTP devrait ajouter un mot au dictionnaire:

PUT // localhost: 8182 / dictionnaire / mot

Cet exemple utilise PUTparce que vous pouvez déterminer à l' /dictionaryavance ce que l'URI dans l' espace d'informations doit être, et l'émission de plusieurs PUTs ne devrait pas faire de différence. ( PUTest une requête idempotente, comme GET. Lancer la même commande plusieurs fois ne devrait pas faire de différence.) Si vous souhaitez ajouter des définitions, vous pouvez les transmettre en tant que corps au PUTgestionnaire. Si vous souhaitez accepter plusieurs définitions au fil du temps, vous pouvez souhaiter POSTces définitions dans, car il PUTs'agit d'une opération d'écrasement.

Ne négligez pas la synchronisation

In the interest of keeping the examples focused, this article pays no special attention to synchronization issues. Do not treat your production code so nonchalantly! Consult a resource such as Java Concurrency in Practice for more information.

The Restlet instances that I'll create need to be bound to the appropriate information spaces, as shown in Listing 3.

Listing 3. A simple RESTful spell checker

package net.bosatsu.restlet.spell; import com.swabunga.spell.event.SpellChecker; import com.swabunga.spell.engine.GenericSpellDictionary; import com.swabunga.spell.engine.SpellDictionary; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import org.restlet.data.Protocol; import org.restlet.*; public class SpellCheckingServer extends Application { public static String dictionary = "Restlet/dict/english.0"; public static SpellDictionary spellingDict; public static SpellChecker spellChecker; public static Restlet spellCheckerRestlet; public static Restlet dictionaryRestlet; static { try { spellingDict = new GenericSpellDictionary(new File(dictionary)); spellChecker = new SpellChecker(spellingDict); spellCheckerRestlet = new SpellCheckerRestlet(spellChecker); dictionaryRestlet = new DictionaryRestlet(spellChecker); } catch (Exception e) { e.printStackTrace(); } } public static void main(String [] args) throws Exception { Component component = new Component(); component.getServers().add(Protocol.HTTP, 8182); SpellCheckingServer spellingService = new SpellCheckingServer(); component.getDefaultHost().attach("", spellingService); component.start(); } public Restlet createRoot() { Router router = new Router(getContext()); router.attach("/spellchecker/{word}", spellCheckerRestlet); router.attach("/dictionary/{word}", dictionaryRestlet); return router; } }

After it builds up the dictionary instance and the spell checker, the Restlet setup in Listing 3 is slightly more complicated than in the earlier basic example (but not much!). The SpellCheckingServer is an instance of a Restlet Application. An Application is an organizational class that coordinates deployment of functionally connected Restlet instances. The surrounding Component asks an Application for its root Restlet by calling the createRoot() method. The root Restlet returned indicates who should respond to the external requests. In this example, a class called Routerest utilisé pour envoyer aux espaces d'informations subordonnés. En plus d'effectuer cette liaison de contexte, il met en place un modèle d'URL qui permet à la partie «mot» de l'URL d'être disponible en tant qu'attribut sur la demande. Cela sera mis à profit dans les Restlets créés dans les listes 4 et 5.

Le DictionaryRestlet, illustré dans le Listing 4, est responsable du traitement des demandes de manipulation de l' /dictionaryespace d'informations.