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
|