Distribution de conneries

J'ai décidé aujourd'hui de faire un petit tuto sympa pour réaliser le top du top en matière de scalabilité[1], de haute distribution ainsi que haute reliabilité[2] tout en restant multi-plateforme, multi-langage et potentiellement multi-paradigme... Enfin, les deux premiers, c'est sûr, le troisième, j'en sais rien et je m'en tape :D
Normalement, là, avec le nombre de buzzwords utilisés, vous devriez déjà avoir le trollomètre au taquet !! :D
Mais je ne vous en dirai pas plus, il faut aller voir, LA SUITE !!!

Notes

[1] J'adore cet anglicisme tellement il est foireux

[2] Bien aussi, celui-là

Quoi de mieux pour réaliser tout ça que les web services ?
Et bien, c'est simple, autre chose que des web services !! :)
Cette autre chose est un vieux truc que plus personne ne veut utiliser car trop compliqué, trop lourd à mettre en place et à coder, j'ai nommé CORBA[1] !!

Bon, après réflexion, ce ne sera pas un vrai tutoriel CORBA, car il y en a déjà sur Internet, mais plutôt un exemple documenté d'une petite application faite avec PolyORB pour ce qui est du serveur et OmniORB pour la partie C++ cliente.

Le distributeur

C'est beau comme un titre de films mais que distribuons-nous ?
N'ayant pas envie de recoder toute une application pour la distribuer, j'ai décidé de repartir d'une application existante et rendant régulièrement service. J'ai nommé le Corporate Bullshit Generator de mon ami adaïste Gautier.
Pour ceux qui ne connaîtraient pas encore, cette application fournit d'excellentes phrases à placer dans toute bonne réunion se déroulant en présence de commerciaux, grands statéguères etc.
Rendez-vous donc sur la page du projet afin de télécharger le code source[2].

Petite analyse des bullshits

Pour faire du CORBA, il faut d'abord savoir ce que l'on va distribuer. Le paquetage central du CSBG est le Corporate_Bullshit. Il s'agit d'un paquetage générique dont le but est de définir les caractères de saut de lignes ainsi que la phrase à utiliser pour simuler un dialogue[3].
Quoi qu'on voit ?

function Sentence return String;
 
function Workshop return String;
 
function Short_Workshop return String;
 
function Financial_Report return String;

Quatre fonctions permettant de créer une chaîne de caractères contenant l'ensemble des bullshits. Soyons fous, nous fournirons donc une interface vers les quatre.
En CORBA, c'est assez simple, cela donnera un fichier IDL comme montrer ci-dessous :

module CorbaCBSG {
 
  interface CBSG{
    string createSentence();
    string createWorkshop();
    string createShortWorkshop();
    string createFinancialReport();
  };
};

Ici, j'ai préféré respecter les conventions d'écriture en vigueur en C++, Java et consorts en lieu et place de celles d'Ada pour ne pas effrayer le programmeur qui voudrait écrire un client dans ces langages ;)
Au final, c'est bête comme chou ! :D

Mais ce n'est qu'un premier exemple...

Génération

Passons maintenant à la génération et à l'implémentation de la partie serveur.
Pour cela, je conseille la création d'un répertoire corba contenant un répertoire idl ainsi que des répertoires Ada et C++, le tout dans le répertoire du cbsg. Le fichier cbsg.idl définit au-dessus trouvera aisément sa place dans le répertoire idl.
Passons aux choses sérieuses :

fred@coruscant :~/cbsg/corba/Ada $ iac -i ../idl/cbsg.idl

Onze fichiers sont normalement créés. Seul les fichiers corbacbsg-cbsg-impl.ads et corbacbsg-cbsg-impl.adb vont nous intéresser, le reste étant de la tuyauterie CORBA. Un point toutefois important, l'option i écrasera votre implémentation si celle-ci existe déjà ainsi que sa spécification si elle contient des champs spécifiques.
Dans la spécification (fichier ads), il n'y a rien à changer. En effet, notre objet CORBA n'a pas d'état en soi tout comme le CBSG.
Par contre, il est intéressant de lire ce fichier car le lecteur attentif aura remarqué que les méthodes générées n'utilisent pas le type String standard d'Ada mais un type propre à CORBA. Notre objet CORBA fera donc la translation.
Comme vous êtes très forts, je ne donnerai que l'implémentation de la fonction createSentence ainsi que la déclaration du paquetage générique Corporate_Bullshit , vous généraliserez par vous-mêmes :

package Simple_Generator is new  Corporate_Bullshit(Paragraph => "",
						       Dialog_Mark => "");
 
   --------------------
   -- createSentence --
   --------------------
 
   function createSentence
     (Self : not null access Object)
     return CORBA.String
   is
      Generated_Sentence : String := Simple_Generator.Sentence;
   begin
      return CORBA.To_CORBA_String(Generated_Sentence);
   end createSentence;

Maintenant que nous avons défini le comportement de notre objet, il nous faut démarrer le bus CORBA et exposer une instance de notre toute nouvelle classe.
Pour le moment, nous n'utiliserons pas l'un des services primordiaux de CORBA, à savoir le Name Service. Notre serveur exposera donc seulement une IOR, référence vers notre objet.
Pour ce serveur, l'exemple echo fournit avec PolyORB suffit amplement :

with Ada.Exceptions;
with Ada.Text_IO; use Ada.Text_IO;
 
with CORBA.Impl;
with CORBA.Object;
with CORBA.ORB;
 
with PortableServer.POA.Helper;
with PortableServer.POAManager;
 
with CorbaCBSG.CBSG.Impl;
 
with PolyORB.CORBA_P.CORBALOC;
 
-- Permet de spécifier à PolyORB son type de fonctionnement
with PolyORB.Setup.No_Tasking_Server;
pragma Warnings (Off, PolyORB.Setup.No_Tasking_Server);
 
procedure Server is
begin
 
   declare
      -- Permet de récupérer les paramètres tels que définis dans le norme CORBA comme InitialRef
      Argv : CORBA.ORB.Arg_List := CORBA.ORB.Command_Line_Arguments;
 
   begin
      -- Initialisation de notre bus sous le nom ORB
      CORBA.ORB.Init (CORBA.ORB.To_CORBA_String ("ORB"), Argv);
 
      declare
	 -- Le PortableObjectAdapter est l'endroit où sont stockés nons objets
         Root_POA : PortableServer.POA.Local_Ref;
 
	 -- On déclare une référence pour notre objet
         Ref : CORBA.Object.Ref;
 
	 -- Et son implémentation
         Obj : constant CORBA.Impl.Object_Ptr := new CorbaCBSG.CBSG.Impl.Object;
 
      begin
 
         -- Récupération du POA racine du bus
	 -- Il s'agit d'une interface CORBA et utilise donc des chaines de caractères CORBA
	 -- que l'on traduit en référence d'objet
         Root_POA := PortableServer.POA.Helper.To_Local_Ref
           (CORBA.ORB.Resolve_Initial_References
	      (CORBA.ORB.To_CORBA_String ("RootPOA")));
 
	 -- On démarre notre POA
         PortableServer.POAManager.Activate
           (PortableServer.POA.Get_The_POAManager (Root_POA));
 
	 -- On crée une référence sur notre objet pour l'exposer
         Ref := PortableServer.POA.Servant_To_Reference
           (Root_POA, PortableServer.Servant (Obj));
 
	 -- Et on affiche un IOR
         Put_Line
           ("'"
	      & CORBA.To_Standard_String (CORBA.Object.Object_To_String (Ref))
	      & "'");
         New_Line;
 
         --  et une corbaloc
         Put_Line
           ("'"
	      & CORBA.To_Standard_String
	      (PolyORB.CORBA_P.CORBALOC.Object_To_Corbaloc (Ref))
	      & "'");
 
         --  Launch the server. CORBA.ORB.Run is supposed to never return,
         --  print a message if it does.
 
         CORBA.ORB.Run;
 
         Put_Line ("ORB main loop terminated!");
      end;
   end;
exception
   when E : others =>
      Put_Line
        ("CBSG server raised " & Ada.Exceptions.Exception_Information (E));
      raise;
end Server;

Même si ce code a l'air complexe, il est quasi identique pour toutes les fois où l'on doit exposer un objet sur le bus.
Il ne reste plus que la compilation :

fred@coruscant :~/cbsg/corba/Ada $ mkdir obj
fred@coruscant :~/cbsg/corba/Ada $ gnatmake server.adb -D obj -aI../.. `polyorb-config`

Si tout c'est bien passé, il ne reste qu'à lancer notre serveur

fred@coruscant :~/cbsg/corba/Ada $ ./server
'IOR:010000001700000049444c3a436f726261434253472f434253473a312e30000002000000000000005c000000010102000a0000003132372e302e302e3100f1761b0000002f30303030303030313154643637306239663031326565316538300001000000010000001c0000000100000001000100000000000001010002000000010101000201010003004f503c000000010100000c0000003139322e3136382e332e32007a3300001b0000002f30303030303030313154643637306239663031326565316538300000000000'

'corbaloc:iiop:1.2@127.0.0.1:30449//000000011Td670b9f012ee1e80'

vous voulez que le serveur écoute sur autre chose que 127.0.0.1, il suffit de le préciser dans le fichier polyorb.conf qui devra se trouver à l'endroit d'où vous lancez le serveur.

Serveur, s'il vous plaît

Maintenant que notre serveur attend le client, codons-le. D'abord un petit client tout simple en Ada vu que tout est prêt pour cela. Encore une fois, l'exemple echo fourni nous suffira pour créer notre client

with Ada.Command_Line;
with Ada.Text_IO;
with CORBA.ORB;
 
with CorbaCBSG.CBSG;
 
with PolyORB.Setup.Client;
pragma Warnings (Off, PolyORB.Setup.Client);
 
procedure Client is
   use Ada.Command_Line;
   use Ada.Text_IO;
   use type CORBA.String;
 
   Rcvd_Bullshits : CORBA.String;
   Bullshit_Generator : CorbaCBSG.CBSG.Ref;
 
begin
   CORBA.ORB.Initialize ("ORB");
   if Argument_Count not in 1 .. 2 then
      Put_Line
        ("usage: client <IOR_string_from_server>");
      return;
   end if;
 
   --  Récupération d'une référence sur l'objet distribué par son IOR ou corbaloc
   CORBA.ORB.String_To_Object
     (CORBA.To_CORBA_String (Ada.Command_Line.Argument (1)), Bullshit_Generator);
 
   -- On vérifie que la référence est correcte
   if CorbaCBSG.CBSG.Is_Nil(Bullshit_Generator) then
      Put_Line ("main : cannot invoke on a nil reference");
      return;
   end if;
 
   Rcvd_Bullshits := CorbaCBSG.CBSG.createSentence(Bullshit_Generator);
 
   Put_Line ("The generator said : " & CORBA.To_Standard_String (Rcvd_Bullshits));
 
exception
   when E : CORBA.Transient =>
      declare
         Memb : CORBA.System_Exception_Members;
      begin
         CORBA.Get_Members (E, Memb);
         Put ("received exception transient, minor");
         Put (CORBA.Unsigned_Long'Image (Memb.Minor));
         Put (", completion status: ");
         Put_Line (CORBA.Completion_Status'Image (Memb.Completed));
      end;
end Client;

Comme d'habitude, compilation :

fred@coruscant :~/cbsg/corba/Ada $ gnatmake client -D obj -aI../.. `polyorb-config `
fred@coruscant :~/cbsg/corba/Ada $ ./client 'corbaloc:iiop:1.2@127.0.0.1:30449//000000011Td670b9f012ee1e80'
The generator said : The business leaders embrace a growing execution by thinking outside of the box, whereas the steering committee delivers a top-down interoperability. 

Que dire de plus que magnifique !!

Ok, j'avais promis un client C++ avec OmniORB. Comme je tiens toujours mes promesses, le voici...
Dans le répertoire C++, on commence donc par générer les fichiers nécessaires avec le compilateur d'IDL d'OmniORB.

fred@coruscant :~/cbsg/corba/C++ $ omniidl -bcxx ../idl/cbsg.idl

Les fichiers cbsg.hh et cbsg.cc sont créés. Ils contiennent à la fois les stubs et les skeletons. Dans le cadre du client, on n'utilisera que la partie stub.
on crée donc le client en s'inspirant des exemples fournis avec OmniORB :

#include <cbsg.hh>
#ifdef HAVE_STD 
#include <iostream> 
#include <fstream> 
using namespace std; 
#else 
#include <iostream.h>
#endif 
 
////////////////////////////////////////////////////////////////////// 
int main(int argc, char** argv) 
{ 
  try 
    { 
      //Initialisation de l'ORB
      CORBA::ORB_var orb = CORBA::ORB_init(argc, argv); 
      if( argc != 2 ) 
	{ 
	  cerr << "usage: client <object reference>" << endl; 
	  return 1; 
	} 
 
      //On crée un objet CORBA à partir de la chaine fournie
      CORBA::Object_var obj = orb->string_to_object(argv[1]); 
 
      //Et le castons en CBSG
      CorbaCBSG::CBSG_var cbsgRef = CorbaCBSG::CBSG::_narrow(obj);
 
      //On vérifie que l'objet existe
      if( CORBA::is_nil(cbsgRef) ) 
	{ 
	  cerr << "Can't narrow reference to type CBSG (or it was nil)." << endl; 
	  return 1; 
	} 
 
      //Et hop, on appelle
      CORBA::String_var bullshit = cbsgRef->createSentence();
      cout << "The generator said : " << bullshit << endl;
 
      //On arrête l'ORB
      orb->destroy(); 
    } 
  catch(CORBA::TRANSIENT&) 
    {
      cerr << "Caught system exception TRANSIENT -- unable to contact the " << "server." << endl; 
    } 
  catch(CORBA::SystemException& ex) 
    { 
      cerr << "Caught a CORBA::" << ex._name() << endl;
    }
  catch(CORBA::Exception& ex) 
    { 
      cerr << "Caught CORBA::Exception: " << ex._name() << endl;
    } 
  catch(omniORB::fatalException& fe) 
    {
      cerr << "Caught omniORB::fatalException:" << endl; 
      cerr << " file: " << fe.file() << endl; 
      cerr << " line: " << fe.line() << endl; 
      cerr << " mesg: " << fe.errmsg() << endl; 
    } 
  return 0; 
}

On compile et on teste

fred@coruscant :~/cbsg/corba/C++ $ g++ -o client client.cpp cbsgSK.cc -I. -lomniORB4
fred@coruscant :~/cbsg/corba/C++ $ ./client 'corbaloc:iiop:1.2@127.0.0.1:30449//000000011Td670b9f012ee1e80'
The generator said : The granular low hanging fruit incentivises the steering committee.

Suivant la version d'OmniORB, il peut être nécessaire de compléter la ligne de compilation avec -lomnithread.
Et voilà !
Bon, j'avais promis de la haute disponibilité et tutti quanti mais finalement, on va garder ça pour la prochaine fois :)
En espérant que cela vous a plu.

Notes

[1] Désolé mais la page en français est beaucoup trop incomplête :(

[2] Mais ne lisez que les interfaces, vous risqueriez de choper une overdose de bullshits

[3] Ce second paramètre assez obscure n'est pas utilisé par Gautier donc prudence :D

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.

La discussion continue ailleurs