Mettre en oeuvre RMI
RMI est un protocole qui vous permet de vous amuser à volonté avec votre réseau.... une fois que vous en avez déjoué les pièges, chausse-trappes et traquenards.
RMI, ou Remote Method Invocation est l'architecture Java qui permet d'invoquer des procédures distantes, ou plus clairement dit, d'appeler des méthodes d'un objet Java existant sur une machine A depuis une machine B. Il existe d'autres architectures permettant de faire cela. CORBA par exemple. RMI présente l'avantage d'être (beaucoup) plus simple et l'inconvénient de limiter le langage à Java uniquement. Si vous êtes un fan de java, cet inconvénient n'en est pas un pour vous et vous salivez à l'avance de pouvoir faire de la programmation réseau avec votre langage favori. Contrôler, administrer une machine à distance, voir à travers Internet, dans de très bonnes conditions de sécurité, voilà qui est excitant et finalement à la portée de tous. Le tout est de prendre RMI en main, car pour autant que les choses soient simples, la prise de contact est parfaitement rebutante. N'ayez aucune illusion, vos premiers essais se solderont par une levée d'exceptions de la part de la JVM, agrémentées de messages d'erreurs remarquablement incompréhensibles qui vous plongeront dans des difficultés à faire dresser les cheveux sur la tête. Ce tutoriel n'a donc pas pour but d'exposer RMI dans toute sa théorie. Les ouvrages spécialisés ou les sites dédiés le font fort bien. Au contraire notre propos est résolument pratique. Nous voulons donner une marche à suivre et les moyens de résoudre les problèmes.
Un petit réseau personnel
Il est parfaitement possible de travailler localement avec RMI sur une seule machine, mais l'intérêt est évidement limité. Alors nous travaillerons directement sur le réseau. Nous supposons celui-ci constitué de deux machines baptisée 'lune' pour le serveur et 'soleil' pour le client. Les systèmes d'exploitation sont des Linux et le réseau TCP/IP doit évidemment être opérationnel (MEME pour une utilisation en local). Vous pouvez bien sûr être sous Windows, ça marche aussi, en faisant attention. Nous supposons également qu'un serveur Web, ici Apache, est installé et tourne. En effet RMI permet de passer par Apache pour charger les classes ce qui est parfait pour la sécurité. Si vous ne voulez pas utiliser de serveur Web, employez une URL qui pointe sur un répertoire partagé de votre réseau.
L'interface RMI
Pour que la communication entre des objets puisse s'établir, ceux-ci doivent avoir quelque chose en commun. Ce quelque chose est une interface RMI, dans laquelle sont déclarées les méthodes de l'objet serveur qui seront accessibles à l'objet client. Voici une interface RMI:
import java.rmi.*;
public interface CoucouDistant extends Remote
{
String getCoucou() throws RemoteException;
}
Avec une originalité folle nous déclarons une méthode qui renvoie une chaîne de type 'coucou' à travers le réseau. Une chaîne entière ? Comment est-ce possible ? Parce que RMI se charge de tout conditionner et empaqueter (marshalling), y compris des objets, dans un sens comme dans l'autre sur le réseau grâce à une classe de stub. Ce que nous devons savoir est qu'une interface RMI dérive toujours de l'interface Remote, pour laquelle nous n'avons aucun travail d'implémentation à faire. En revanche toute méthode doit être déclarée comme susceptible de lever l'exception RemoteException. Travailler avec RMI c'est gérer systématiquement les exceptions car rien ne permet de prédire à l'avance une défaillance matérielle du réseau par exemple.
Le serveur et le client
Voici l'implémentation côté serveur de notre interface. Une telle implémentation dérive en outre de la classe UnicastRemoteObject. L'implémentation d'une interface RMI ne présente aucune difficulté. Il suffit de ne pas oublier que nous sommes dans un contexte réseau avec les problèmes de défaillances qui peuvent en découler. Il faut donc que toutes les méthodes propagent l'exception RemoteException. Ainsi, sous peine d'être très sévèrement rappelé à l'ordre par le compilateur, le serveur doit avoir un constructeur et celui-ci doit propager la fameuse exception. Si votre constructeur ne reçoit pas de paramètres (c'est le cas de notre exemple), le constructeur de la classe de base est automatiquement invoqué. Dans le cas contraire, à vous d'ajouter un appel à 'super'. Ensuite le serveur DOIT mettre en place un gestionnaire de sécurité puis s'exposer sur le réseau via la méthode Naming.rebind. L'URL contient le nom de domaine. Au lieu de:
//lune/MonServeurRMI
Vous pouvez très bien mettre
//localhost/MonServeurRMI
ou encore
//www.mondomaine.com/MonServeurRMI
Sun déconseille d'indiquer le protocole, bien que cela soit possible:
rmi://lune/MonServeurRMI
S'enregistrer sur le réseau implique que la base de registre RMI, ou rmiregistry soit active. Nous y reviendrons. Quant au client, le schéma du code est exactement le même à ceci près que la référence sur un objet exposé est récupérée par l'invocation de Naming.lookup.
// Exemple de serveur RMI
import java.net.*;
import java.rmi.*;
import java.rmi.registry.*;
import java.rmi.server.*;
public class MonServeurRMI extends UnicastRemoteObject implements CoucouDistant
{
// Il faut un constructeur et que celui-ci
// propage l'exception RemoteException
public MonServeurRMI() throws RemoteException {}
public String getCoucou() throws RemoteException
{
System.out.println("getCoucou a été appelée dans le serveur\n");
return "Coucou distant";
}
public static void main(String[] args)
{
if(System.getSecurityManager() == null)
System.setSecurityManager(new RMISecurityManager());
try
{
MonServeurRMI cs = new MonServeurRMI();
Naming.rebind("//lune/MonServeurRMI", cs);
System.out.println("Objet MonServeur lié");
}
catch(Exception e)
{
System.out.println("\nProblème !!\n\n " + e.toString());
e.printStackTrace();
}
}
}
// Exemple de client RMI
import java.rmi.*;
import java.rmi.Naming;
import java.rmi.registry.*;
public class MonClientRMI
{
public static void main(String[] args)
{
System.setSecurityManager(new RMISecurityManager());
try
{
CoucouDistant cd =
(CoucouDistant)Naming.lookup("//lune/MonServeurRMI");
System.out.println("Client connecté");
System.out.println("******\n"+cd.getCoucou()+"\n******\n");
System.exit(0);
}
catch(Exception e)
{
System.out.println(e.toString());
e.printStackTrace();
}
}
La sécurité
Les fichiers de configuration de sécurité de Java permettent un contrôle fin des permissions. Toutefois le mieux est de toutes les accorder pour les premiers essais, par exemple avec ce fichier allpolicy.txt :
grant
{
permission java.security.AllPermission;
};
Puis lorsque cela aura fonctionné une première fois, essayez ces fichiers policy.txt, côté serveur et client respectivement :
grant
{
permission java.net.SocketPermission
"*:1024-65535", "connect,accept,resolve";
permission java.net.SocketPermission
"*:80", "connect";
};
grant
{
permission java.net.SocketPermission
"lune:1024-65535", "connect";
permission java.net.SocketPermission
"lune:80", "connect";
};
Vous aurez soin de remplacer 'lune' par le nom de votre serveur sur le réseau ou son nom de domaine.
Compilation
RMI ou pas, les fichiers sources Java doivent être compilés comme nous le savons. Dans le cas de RMI, la classe serveur doit en outre être compilée par le compilateur rmic qui générera la classe de stub. Ainsi
rmic -v1.2 MonServeurRMI
Les essais
Dans votre répertoire Apache, (c'est à dire /srv/www/htdocs/ sur ma SuSE. Reportez vous à la configuration de votre Apache) déposer les fichiers:
MonServeurRMI.class
MonServeurRMI_Stub.class
CoucouDistant.class
policy.txt (ou allpolicy.txt)
Fiche de dépannage pour programmeur au RMI.
Q: Pour la classe MonServeurRMI ou MonClientRMI, vous recevez l'exception ClassNotFoundException et vous êtes pourtant bien certain que les classes sont là puisque vous venez de vous positionner dans leur répertoire.
R: Une variable d'environnement CLASSPATH traîne dans votre système et ne pointe pas le répertoire courant. En effet la JVM ne recherche les classes dans le répertoire courant qu'en l'absence de CLASSPATH. Donc modifier le CLASSPATH ou bien lancer le client et/ou le serveur avec l'option -cp .
java -cp . -Detc... MonServeurRMI
Q: Vous recevez l'exception ClassNotFoundException pour la classe CoucouDistant.
R: Vérifier que la classe Coucoudistant se trouve à la fois dans le répertoire du serveur ET dans le répertoire du client.
Q: Vous recevez l'exception ClassNotFoundException pour la classe MonServeurRMI_Stub.
R: la classe doit être placée dans le répertoire du serveur
Q: Vous avez méticuleusement vérifié le point précédent, mais vous obtenez QUAND MEME ClassNotFoundException pour MonServeurRMI_Stub.
R: Vous avez lancé la 'rmiregistry' dans de mauvaises conditions. Soit vous étiez dans le répertoire du serveur, soit le CLASSPATH pointe sur les classes du serveur. En aucun cas la rmiregistry ne doit 'voir' les classes lorsqu'elle est lancée, sinon elle ne les charge plus lors des invocations distantes.
Q: La redoutable exception ClassNotFoundException continue d'être levée.
R: Le répertoire des classes est mal spécifié. Sans doute parce que vous avez oublié le slash (/) final, soit vous avez oublié que sous Windows il faut deux antislash (\\), y compris à la fin:
-Djava.rmi.server.codebase=http://lune/
ou
-Djava.rmi.server.codebase=file:/c:\\repertoire\\ (Windows)
Q: Rien n'y fait le problème est toujours là.
R: Vous n'utilisez pas de serveur Web et vous spécifiez un répertoire avec le protocole file. Dans ce cas utilisez le fichier allpolicy.txt du Cd-Rom qui autorise tout et lisez un Programmez! à venir pour savoir comment restreindre les permissions sur les fichiers.
Q: Mais moi j'ai une autre exception et ça me désespère. Il s'agit de java.security.AccessControlException suivi de access denied (java.net.SocketPermission xxx resolve)
R: Ne croyez pas ce que racontent parfois les ouvrages spécialisés:
· Le serveur ne peut pas être lancé sans fichier 'policy', pas plus que le client. Un gestionnaire de sécurité doit être défini dans le code du serveur, comme l'indique la documentation, sinon RMI ne charge aucune classe sur le réseau, mais seulement localement ce qui a un intérêt nul. Et bien sûr la présence de ce gestionnaire impose celle des fichiers 'policy'.
· Côté serveur, la permission 'connect' n'est pas suffisante. Il faut également accorder 'accept' et 'resolve'
· Les fichiers 'policy' ne sont pas les mêmes côtés serveur et côté client, sauf dans le cas où toutes les permission sont accordés.
Q: Ca ne marche toujours pas.
R: Essayez Python ;-)
Ajouter un commentaire