MokaByte 77 - 7mbre 2003 
Integrazione di applicazioni Enterprise
V parte
di

Stefano Rossini
e
Alberto D'Angeli

Nei precedenti articoli si è presentata una panoramica sui possibili scenari d'integrazione, partendo dall'integrazione a livello di presentation[INT1], di ORB [INT2 e INT3] e mediante messaggi JMS[INT_4]. In quest'ultima parte si affronterà l'integrazione mediante Web Services al fine di integrare applicazioni sviluppate su piattaforme eterogenee.

Riprendendo il "cammino" degli scorsi articoli, si cercherà di integrare tra di loro i servizi di calcolo della lunghezza di una stringa sviluppati con tecnologie diverse (J2EE, .NET e CORBA) mediante lo scambio di messaggi SOAP.



Figura 1 - scenario integrazione mediante Web Services

Lo scorso articolo si è visto come esporre la logica degli EJB mediante Web Services utilizzando Axis, mentre ora si vedrà come è possibile fare l'analogo con i servizi .NET e CORBA.


Figura 2
- esposizione di un EJB mediante Web Services Axis

Iniziamo a capire come è possibile usufruire del servizio del calcolo della lunghezza della stringa dell'applicazione C sviluppata in C# con framework .NET su S.O Windows.

Il servizio di business dell'applicazione .NET è costituito dal seguente oggetto remoto .NET MokaServant

public class MokaServant : MarshalByRefObject {

public MokaServant(){ }

che mette a disposizione il metodo di business getLength() che restituisce la lunghezza del parametro ricevuto in ingresso (restituisce -1 nel caso in cui il parametro ricevuto è null):

public int getLength(String param) {
  int res=-1;
  if(param != null){
    res=param.Length;
  }
  return res;
}

L'oggetto remoto è messo a disposizione dalla seguente applicazione server

public class Server {
  public static int Main(string [] args) {
    System.Console.WriteLine("Starting ...");

che si pone in ascolto sulla porta TCP 9999

    TcpChannel chan = new TcpChannel(9999);
    ChannelServices.RegisterChannel(chan);

e provvede a registrare l'oggetto remoto con un endpoint (nome logico) MokaRemotingCalculator in modalità SingleCall (viene creata una nuova istanza dell'oggetto per ogni richiesta del cliente).

    RemotingConfiguration.RegisterWellKnownServiceType(
                         Type.GetType("RemotingSamples.MokaServant,
                                      object"),
                         "MokaRemotingCalculator",
                         WellKnownObjectMode.SingleCall);
    System.Console.WriteLine("Hit <enter> to exit...");
    System.Console.ReadLine();
    return 0;
  }
}

Si provvede quindi a compilare l'oggetto .NET

csc /out:..\bin\MokaServantCalculator.dll /debug+ /target:library object.cs

e a compilare l'applicazione server

csc /debug+ /r:object.dll,System.Runtime.Remoting.dll server.cs

ottenendo così l'eseguibile server.exe.

Eseguendo il server.exe si rende disponibile il servizio .NET calcolo della lunghezza.

L'accesso diretto all'oggetto .NET remoto non è possibile da piattaforme diverse da .NET (es: J2EE, CORBA,…) a causa dell'incompatibilità di protocollo di comunicazione.

Una prima possibile integrazione può essere effettuata (con tutti i pro e contro descritti in [INT2] ) a livello di Presentation rendendo accessibile il servizio grazie alla "mediazione" di una pagina ASPX.
Per usufruire del servizio a livello di presentation si è quindi "costretti" a navigare attraverso le pagine ASP dell'Applicazione C. La navigazione permette di "agganciare" il servizio dalla pagina wordlength.asp direttamente da browser mediante un semplice workflow a livello di presentation(wordlength.asp, wordlengthConfirm.asp e wordlengthExecuteRemoting.aspx ) al fine di invocare l'oggetto remoto e visualizzare il relativo risultato contenuto nella variabile result.
L'estratto saliente della pagina wordlengthExecuteRemoting.aspx è il seguente:

<script language="C#" runat="server">
  string result = "";
  void invokeWS(System.Object sender, System.EventArgs e) {
    MokaServant obj = (MokaServant)Activator.GetObject(
                    typeof(RemotingSamples.MokaServant),
                    "tcp://localhost:9999/MokaRemotingCalculator");
    string param = Request.QueryString["STRING_USER"];
    result = "" + obj.getLength(param);
  }
</script>

<html>
<body>
. . .
<%= result %>
. . .
</body>
</html>

Come è possibile permettere l'accesso diretto B2B da un client Java o Corba, dato che non è possibile tra l'applicazione J2EE e quella .NET per la mancanza di interoperabilità dei protocollo di comunicazione RMI/IIOP e Microsoft .NET?
La risposta è …. Web Services!

 

Web Services .NET
Per permettere l'interoperabilità tra le due diverse piattaforme è possibile ricorrere ai Web Services.(vedi [INT4]).
Per permettere l'interazione B2B dell'applicazione A (J2EE) con l'applicazione C si sviluppa un Web Service che accede all'oggetto RemotingSamples.MokaServant rendendolo disponibile a client SOAP. Di fatto si espone il servizio di business con una diversa tecnologia per permettere una diversa modalità di accesso piu' leggera (HTTP+XML).

Vediamo quindi come si può realizzare un Web Service .Net utilizzando C#, che svolga le mansioni di client dell'oggetto remoto C# dando la possibilità di esportare il servizio relativo con protocollo SOAP.



Figura 3
- esposizione di un oggetto .NET Remote mediante Web Services .NET

Il Web Service è una classe C# che usa il decoratore [WebMethod] per indicare i metodi da esportare come WebServices:

[WebService(Namespace="urn:Calculator.webservice.dotnet")]
  public class Calculator:WebService{
    [WebMethod]
    public int getLength(string parola){
      int result = 0;
      try{

Una volta invocato, il WebService provvede a recuperare l'oggetto remoto .NET

      MokaServant obj = (MokaServant)Activator.GetObject(
                        typeof(RemotingSamples.MokaServant),
                        "tcp://localhost:9999/
                           MokaRemotingCalculator");
      if (obj != null){

per poi invocare il suo metodo di business getLength()

        result=obj.getLength(parola);

e ritornare l'opportuno valore

        return result;
      }

Di fatto il servizio funziona da "wrapper" dell'oggetto remoto .NET.

E' possibile ottenere il WSDL di questo Web Service utilizzando l'url dell'endpoint del servizio e aggiungendo in coda ad esso l'appendice "?WSDL" analogamente a quanto avveniva con Axis (vedi[INT3]):

http://<< HOST_C>>/app_c/ws/Calculator2.asmx?wsdl

Per sviluppare un semplice client C# di test, bisogna come prima cosa ricavarsi la classe Proxy per accedere al Servizio. Così come con Axis [INT4] si usava il tool WSDL2Java, nel caso di Web Services .NET è disponibile il programma wsdl.exe che dato l'URL del servizio, genera la corrispondente classe Proxy.

 

Client C#
Il codice client C# che ci permette di testare questo WebService è semplice ed intuitivo ed è il seguente:

public class TestClient {

  public static void Main(string[] args) {
    TestClient client = new TestClient ();
    client.test(args[0]);
  }

  public void test(string word) {
    Console.WriteLine("TestClient: string["+word+"]...");
    Calculator calc = new Calculator();
    Console.WriteLine("Risultato:" + calc.getLength(word));
  }
}

dove Calculator è la classe C# ottenuta mediante il tool Microsoft wsdl.exe

wsdl.exe /l:cs /o:CalculatorService.cs
          http://localhost/app_c/ws/Calculator.asmx /n:moka

 

Client Java
Veniamo adesso alla parte piu' interessante e cioè come far comunicare un client Java con il Web Service .NET
Mediante il tool Axis WSDL2Java ci si ricava le classi stub puntando all'endpoint del Web Service .NET:

java org.apache.axis.wsdl.WSDL2Java -o ..\src - p it.mokabyte.eai.my.webservices.generated.dotnet http://localhost/app_c/ws/Calculator.asmx?wsdl

Questa operazione permette di ottenere (come descritto in [INT4]) i seguenti file Java: Calculator.java, CalculatorLocator.java, CalculatorSoap.java e CalculatorSoapStub.java.
Compilati i file ottenuti è possibile sviluppare il client Java:

public class CalculatorTestClient {
  public void test(String strEndpoint){
  try{
   URL endpoint = new java.net.URL(strEndpoint);
   CalculatorSoap calc = new    CalculatorLocator().getCalculatorSoap(endpoint);
   int res=calc.getLength("Ciao!");
   System.out.println(res);
  }
  catch(Exception e){ . . . }

  public static void main(String[] args) {
    new CalculatorTestClient().test(args[0]);
  }
}

e mandarlo in esecuzione

%JAVA_HOME%\bin\java -classpath %CLASSPATH%;classes CalculatorTestClient http://%HOST_C%/app_c/ws/Calculator.asmx

Il risultato però è … -1 … infatti all'oggetto remoto .NET non arriva la stringa da calcolare la lunghezza ("Ciao!") e di conseguenza il Web Service .NET ritorna come risultato il codice d'errore applicativo -1.
Il motivo del fallimento è dovuto al fatto che i Web Services permettono due possibili codifiche dei parametri del servizio all'interno del body del messaggio SOAP (encoded e literal) e anche lo stile del documento può essere di tue tipologie (RPC o Document) (per i dettagli vedere[SOAP_SPEC]).

Mentre il default per i Web Services .NET prevede di utilizzare il "document" style con parametri literals, il default per Axis è costituito dall'accoppiata RPC/encoded:

Pertanto, di default, un client Axis non è in grado di comunicare con un Web Serv ice .NET.
Per fare comunicare Axis con .NET bisogna forzare uno dei due tier affinché utilizzi lo stesso tipo di encoding dell'altro, bypassando di fattoo l'impostazione di default.
Vediamo ad esempio come è possibile forzare il servizio .NET a esporre il proprio Web Service in modo che utilizzi RPC style e Encoded Parameters.
Per fare questo, si può utilizzare o il tag [SoapRpcMethod()] che viene specificato a livello di singolo metodo

[WebMethod]
  [SoapRpcMethod()]
    public int getLength(string word){
      ...

o il tag [SoapRpcService()] che modifica lo style e il parameter encoding a livello dell'intero Web Service.

[WebService(Namespace="urn:Calculator2.webservice.dotnet")]
[SoapRpcService()]
  public class Calculator2:WebService{
   [WebMethod]
     public int getLength(string word){
      ...

Per enfatizzare le differenti modalità di esposizione del Web Service .NET, si crea un nuovo Web Service chiamandolo Calculator2 e che utilizza la combinazione RPC/Encoded, mentre il precedente Calculator continua a essere di tipo Document/Literal.



Ripetendo i passi precedentemente con il tool WSDL2Java, questa volta la comunicazione tra il client Java ed il servizio .NET avviene con successo.
Il WS ritorna infatti che la lunghezza della stringa "Ciao!" è 5 !

 

Client C/C++
Vediamo adesso di verificare l'interoperabilità dei Web Service Java (Axis) [INT_4] e .NET fino ad ora sviluppati tramite un client Soap scritto in Linguaggio C/C++.


Figura 4 - Interoperabilità client gSOAP C/C++ con Web Services Axis e .NET


Il toolkit scelto per sviluppare in linguaggio C/C++ applicazioni in grado di utilizzare il protocollo SOAP è gSOAP. Questo toolkit, realizzato alla Florida State University, permette sia di realizzare client in grado di invocare servizi web esposti anche attraverso differenti piattaforme, sia di esporre servizi web utilizzando il linguaggio C/C++. La scelta di tale linguaggio di implementazione, unito alle ottime performance che caratterizzano il toolkit, rendono questa combinazione vantaggiosa nei casi in cui siano particolarmente critici i tempi di esecuzione. Inoltre, si ha la possibilità di riutilizzare codice legacy che potrebbe essere complicato integrare con applicazioni Java o .NET utilizzando altri sistemi. Il toolkit gSOAP è stato prescelto oltre che per le sue caratteristiche tecniche, anche per il fatto di essere liberamente disponibile per diverse piattaforme. In particolare, per le piattaforme Win32 non è neppure necessario procedere alla compilazione dei sorgenti in quanto viene distribuito nel pacchetto anche l'eseguibile binario. Tuttavia, poiché il nostro argomento è l'integrazione di piattaforme il più possibile eterogenee, abbiamo assunto LINUX come piattaforma di riferimento per gli esempi con gSOAP.

Elenchiamo qui di seguito la sequenza delle operazioni previste per realizzare lo scenario di integrazione tra il client gSOAP ed il Web Service Axis:

  • creare e pubblicare il Web Service Axis MokaCalculator come descritto in [INT4].
  • compilare il client di esempio (ws_gSOAP/axis_client/mainClient.cpp). Il makefile dell'esempio recupera dinamicamente il WSDL del sevizio Axis dall'URL http://<HOST_E>:8080/axis/services/MokaCalculatorService?wsdl
  • eseguire lo script di lancio del client gSOAP. Questo interroga il webservice MokaCalculator per eseguire il calcolo della lunghezza di una stringa e ne stampa il risultato.

Una particolarità di gSOAP è il fatto di prevedere un proprio formato di descrizione dei servizi web realizzato attraverso un opportuno header file C/C++ che comprende la definizione dei 'metodi' del servizio web attraverso prototipi di funzioni C. Così pure le strutture dati coinvolte nel passaggio delle informazioni tra il client ed il provider del servizio vengono mappate in linguaggio C/C++. Informazioni addizionali, quali i namespace utilizzati ed il nome del servizio vengono tradotte in particolari commenti posti all'inizio del header file.
Si riporta qui di seguito il file header ottenuto dal wsdl del servizio esposto con Axis:

//gSOAP impl schema namespace: http://webservices.my.eai.mokabyte.it
//gSOAP apachesoap schema namespace: http://xml.apache.org/xml-soap
//gSOAP soapenc schema namespace: http://schemas.xmlsoap.org/soap/encoding/
//gSOAP impl service name: Calculator

/*start primitive data types*/
typedef char * xsd__string;
typedef int xsd__int;

/*end primitive data types*/

class impl__getLengthResponse {
public:
xsd__int _getLengthReturn;
};

//gSOAP impl service method-action: getLength ""
impl__getLength( xsd__string arg, impl__getLengthResponse * out );


Questo header file viene ottenuto attraverso un tool Java chiamato wsdlcpp fornito con gSOAP, che consente di tradurre la specifica WSDL di un servizio web nel corrispondente header file di gSOAP. L'utilizzo del tool wsdlcpp viene dimostrato nei Makefile relativi a GSOAP per compilare gli esempi allegati questo articolo. Il file header contenente la descrizione del servizio viene utilizzato come punto di partenza per generare le varie funzioni di marshalling/unmarshalling dei parametri, le classi Proxy che permettono ad un client di invocare il Web Service. Per operare tale trasformazione, gSOAP offre il tool soapcpp2.
La sequenza delle trasformazioni coinvolte è dunque la seguente: dal file AxisCalculator.wsdl si ottiene il wsdl.h mediante il tool wsdlcpp; dal file wsdl.h si ottengono i file soapC.c, soapC.h… mediante il tool soapcpp2.
Fra i files generati, ve ne è uno che contiene la definizione di una classe Proxy C++ che è possibile utilizzare per invocare il Web Service con quasi la stessa facilità con cui potremmo fare una chiamata ad un oggetto locale.

retVal = myProxy->getLength("....", out);

gSOAP prevede che ogni operazione di un servizio Web venga tradotta in un metodo di questa classe Proxy con le seguenti convenzioni:

  • il metodo deve ritornare un intero, che denota l'esito dell'operazione (successo, fallimento...)
  • il valore di ritorno del metodo del Web Service deve comparire come ultimo parametro del metodo della classe Proxy e viene passato "per riferimento" o puntatore.

Compilando i file ottenuti mediante soapcpp2 insieme ad un semplice client che provveda ad effettuare l'invocazione del metodo del Web Service, è così possibile testare l'invocazione del servizio esposto in Java tramite Axis da parte di un client scritto in C++ che gira sotto Linux.

In modo simile a quanto fatto prima per invocare il Web Service esportato tramite Axis, si passa ora ad esaminare il caso della invocazione dei due Web Services Calculator e Calculator2 realizzati tramite .NET.
Poiché gSOAP, analogamente ad Axis usa per default l'accoppiata RPC/Encoded, ci si aspetta che l'invocazione da client gSOAP verso Calculator2 avvenga senza problemi esattamente come nel caso precedente in cui si invocava il Web Service di Axis.
Viceversa, per invocare il Web Service Calculator, dobbiamo "forzare" il client gSOAP in modo che non utilizzi l'encoding RPC dei parametri; inoltre, bisogna specificare esplicitamente di utilizzare il namespace del servizio. Tutto questo si ottiene mediante le due istruzioni seguenti:

myProxy->soap->encodingStyle = NULL; // don't use SOAP encoding
myProxy->soap->defaultNamespace = "urn:Calculator.webservice.dotnet";

Si può notare che omettendo tali istruzioni nel client, il servizio Calculator .NET riceverebbe l'invocazione del metodo getLength, ma riscontrerebbe erroneamente che il parametro passato è NULL.

 

Web Services C/C++
Passiamo infine a realizzare un Web Service attraverso gSOAP e vediamo come esso possa essere invocato da client scritti in Java (Axis) C# (.Net) e perfino Perl.
Il punto di partenza sarà dato dalla descrizione del servizio che intendiamo esporre tramite un header file. Tale header file (CalculatorService.h) viene scritto replicando esattamente quello ottenuto mediante il tool wsdl2cpp a partire dalla descrizione wsdl del servizio. Processando tale file mediante il tool soapcpp2 si ottengono, oltre ai files necessari per realizzare un client del Web Service, di cui si è già trattato prima, anche i files che consentono di realizzare il Web Service stesso in C/C++. Tale servizio potrà essere realizzato o come programma cgi agganciabile da un Web Server esterno (ad esempio Apache) oppure come server stand-alone, utilizzando una implementazione, minimale ma sufficiente, di un server http fornita con gSOAP stesso. Per semplicità negli esempi forniti si è prescelta la seconda opzione.
Il main fornito insieme agli esempi, utilizza quindi le primitive offerte da gSOAP per mettersi in ascolto su una determinata porta e effettuare il dispatch delle richieste RPC al metodo di implementazione:

// struttura semplificata del main Server: è omessa la gestione degli errori; in argv[1] e argv[2]
// vengono attesi l'hostname e la porta su cui ascoltare.
struct soap soap;
soap_init(&soap);
soap_bind(&soap, argv[1], atoi(argv[2]), 100);
soap_accept(&soap);
soap_serve(&soap);


Per imitare quanto fatto in Java/Axis e .Net, nel caso presente si implementa il metodo del Web Service in modo che esso deleghi la funzione da realizzare (il solito calcolo della lunghezza della stringa in ingresso) ad un server Corba realizzato con Mico (vedere [INT3]).



Figura 5
- Esposizione di un oggetto CORBA mediante Web Services gSOAP

// Implementation of the "getLength" remote method:
int impl__getLength(struct soap *soap, xsd__string arg,
                    impl__getLengthResponse* out){
  try {
    CosNaming::NamingContext_var rootContext =
                CosNaming::NamingContext::_narrow(
                orb->resolve_initial_references("NameService"));

    CosNaming::Name name;
    name.length(2);
    name[0].id = (const char *) "eai";
    name[0].kind = (const char *) "";
    name[1].id = (const char *) "calcolatrice";
    name[1].kind = (const char *) "";

    CORBA::Object_var object = rootContext->resolve(name);
    eai::Calcolatrice_var manager =
         eai::Calcolatrice::_narrow(object.in());
    CORBA::String_var msg = CORBA::string_dup(arg);
    out->_getLengthReturn = manager->strlen_calc(msg.in());
  }
  catch(const CORBA::Exception& e) {
    cerr << e << endl;
    return 1;
  }
  return SOAP_OK;
}

Nelle prove realizzate, il server standalone è stato posto in ascolto sulla porta 1888.
Negli esempi allegati all'articolo vengono forniti i client che invocano tale Web Service realizzate mediante: GSOAP , Axis, .Net e Perl.

I primi tre casi rappresentano una variazione sul tema a quanto visto finora e non li discuteremo più in dettaglio. Per quanto riguarda invece il client Perl facciamo notare la estrema sinteticità e compattezza del client risultante:

use SOAP::Lite;
my $proxy = shift;
my $name = shift;
print "\n\nCalling the SOAP Server Calculator\n\n";
print "The SOAP Server says: ";
print SOAP::Lite
-> proxy($proxy)
-> getLength(SOAP::Data->name(arg => $name))
-> result . "\n\n";

In modo del tutto analogo sono stati realizzati in Perl i client per invocare i Web Services esposti tramite Axis e .Net.

 

Conclusioni
In questa serie di articoli si sono viste alcune possibili modalità d'integrazione punto-punto tra applicazioni Enterprise.
Gli esempi proposti in questi cinque articoli si riferiscono a esigenze puntuali di comunicazione tra applicazione e piattaforme eterogenee senza addentrarsi nel dominio dell'EAI (Enterprise Application Integration). E' infatti lo scopo dell'EAI permettere l'integrazione di applicazioni e sistemi esistenti con le nuove tecnologie ed i nuovi requirements in maniera organica e strutturata.

 

Bibliografia e riferimenti
[INT1] S.Rossini: Integrazione di applicazioni Enterprise (I) MokabyteN.71-Aprile 2003
[INT2] S.Rossini: Integrazione di applicazioni Enterprise (II) MokabyteN.72-Maggio 2003
[INT3] S.Rossini, A. D'Angeli: Integrazione di applicazioni Enterprise (III) Mokabyte N.73-Giugno 2003
[INT4] S.Rossini: Integrazione di applicazioni Enterprise (IV) MokabyteN.74-Luglio/Agosto 2003
[SOAP_SPEC] "Simple Object Access Protocol - http://www.w3.org/TR/soap
[WSDL] W3C - "Web Services Description Language" - http://www.w3.org/TR/wsdl

 

Risorse
Scarica gli esempi illustrati in questo articolo

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it