|
Introduzione
La
codifica del client suddivide la sua complessità
in due aree distinte: l'interfaccia utente e la parte
di protocollo SOAP. Per quanto riguarda la prima problematica,
essendo questa un'applicazione Java2, l'implementazione
sarà una mera applicazione delle API di Swing.
Per quanto attiene invece alle problematiche di protocollo,
sarà necessario ancora una volta fare uso delle
JAXM, anche se mediate dalla classe di utilità
Utils sviluppata nella seconda parte di questa
miniserie.
Il client
Il codice client vero e proprio é racchiuso all'interno
della classe ChatClient (listato 1). Questa classe rappresenta
dunque il controller lato client che trasforma le richieste
applicative in opportuni messaggi SOAP. Per fare questo
si appoggia alla classe Utils realizzata il mese scorso
sfruttando i metodi di generazione dei messaggi e di
invio. In particolare i metodi implementati sono quelli
applicativi della Chat:
- login();
- logout();
- send();
- get();
Per eseguire l'invio del messaggio, il codice utilizza
un oggetto SOAPConnection, sul quale chiama il metodo
call(). Come noto, la chiamata va fatta ad un Endpoint
che nel nostro caso é un oggetto di classe URLEndpoint
fornito all'oggetto ChatClient dall'esterno tramite
opportuno metodo "setter".
In applicazioni standalone (non contenute all'interno
di Web Container) é necessario ottenere l'oggetto
SOAPConnection dalla factory SOAPConnectionFactory (vedi
Listato 2).
Listato 1
//...
public class ChatClient {
//...
protected void login() throws SOAPException {
SOAPMessage msg = Utils.getLoginMessage(
user, token );
SOAPMessage reply = Utils.send( msg, serverEndpoint
);
}
protected void logout() throws SOAPException {
SOAPMessage msg = Utils.getLogoutMessage(
user, token );
SOAPMessage reply = Utils.send( msg, serverEndpoint
);
}
protected void send( String message ) throws SOAPException
{
SOAPMessage msg = Utils.getSendMessage(
user, token, message );
SOAPMessage reply = Utils.send( msg, serverEndpoint
);
}
protected String[] get() throws SOAPException {
SOAPMessage msg = Utils.getGetMessage( user,
token );
SOAPMessage reply = Utils.send( msg, serverEndpoint
);
if( reply != null )
return Utils.extractMessages(
reply );
else
return null;
}
}
Listato 2
//...
public class Utils {
//...
public static SOAPConnection getSOAPConnection()
throws SOAPException {
if(
scf == null )
scf
= SOAPConnectionFactory.newInstance();
return scf.createConnection();
}
public static SOAPMessage send(SOAPMessage msg, Endpoint
ep )
throws
SOAPException {
return getSOAPConnection().call( msg, ep
);
}
//...
}
Sebbene ChatClient contenga un metodo main(), non é
questa la classe da lanciare per eseguire la Chat. Da
qui infatti non é possibile accedere all'interfaccia
utente Swing in quanto il metodo main() contiene solo
del codice di test per verificare il funzionamento del
solo layer di comunicazione. Questo ci da però
modo di provare il server, almeno dalla console.
Sempre nel metodo main() é possibile vedere il
passaggio dell'endpoint alla classe ChatClient: in particolare
nell'esempio é presente la stringa "http://127.0.0.1:8080/soapchat/chat".
Questo significa che la web application é stata
chiamata "soapchat" e che il tutto é
in test sulla macchina locale.
Questo parametro dovrà riflettere l'installazione
specifica che sarà adottata, solo nel caso si
intenda provare questa parte di test a basso livello.
L'interfaccia utente
Per supportare le due diverse tipologie di client, applicazione
ed applet, il codice Swing deve essere realizzato in
modo svincolato da queste due entità. ChatPanel
estende dunque JPanel: questo componente visuale potrà
poi essere utilizzato, sia all'interno di una Applet,
sia in una applicazione standalone basata, ad esempio,
su JFrame.
La stessa classe ChatPanel, comunque, contiene anche
l'implementazione dell'applicazione standalone, tramite
il metodo main() (listato 3).
Listato 3
//...
public class ChatPanel extends JPanel {
//...
public
static void main(String[] args) {
JFrame
f = new JFrame( "Chat Client - utente: " +
args[0] );
f.getContentPane().add(new
ChatPanel(args[0],
"max/127.0.0.1"));
f.pack();
f.setVisible(
true );
}
}
Come si evince dalla chiamata al costruttore di JFrame,
il metodo main() si aspetta almeno un parametro che
rappresenta il nome dell'utente. Questo é il
nickname che verrà utilizzato all'interno dell'applicazione.
La costruzione dell'interfaccia utente é banale
e prevede un'area di testo per i messaggi, un campo
di testo per il messaggio da inviare e due pulsanti:
invia ed esci. L'intera operazione di creazione della
UI avviene nel metodo createUI(). Per i componenti visuali
che dovranno essere oggetto di modifiche successive,
verrà tenuto un riferimento in opportuni attributi
di classe. Ad esempio l'area messaggi, che dovrà
essere riempita con i messaggi scaricati dal server,
oppure il campo di immissione, che dovrà fornire
il testo da inviare agli altri utenti connessi, dovranno
avere un riferimento accessibile dagli altri metodi
della classe (listato 4).
Listato 4
//...
public class ChatPanel extends JPanel {
//...
protected void createUI() {
messages
= new JTextArea(25,40);
messages.setEditable(
false );
input
= new JTextField(40);
submit
= new JButton( "Invia" );
submit.addActionListener(
new ActionListener() {
public
void actionPerformed( ActionEvent e ) {
send();}});
fine
= new JButton( "Fine" );
fine.addActionListener(
new ActionListener() {
public
void actionPerformed( ActionEvent e ) {
logout();}});
JPanel
right = new JPanel( new FlowLayout() );
right.add(
submit );
right.add(
fine );
JPanel
left = new JPanel( new FlowLayout() );
left.add(
input );
JPanel
bottom = new JPanel( new BorderLayout() );
bottom.add(
left, BorderLayout.WEST );
bottom.add(
right, BorderLayout.EAST );
setLayout(
new BorderLayout() );
add(
new JScrollPane(messages), BorderLayout.CENTER );
add(
bottom, BorderLayout.SOUTH );
}
}
Il costruttore della classe si occuperà dunque
di creare l'interfaccia utente, ma anche di inizializzare
l'oggetto ChatClient per la comunicazione con il server
di chat ed anche il thread client che si occuperà
di rinfrescare la finestra dei messaggi.
Nel listato 5 é presente la porzione di codice
relativa. Si può notare come, nel caso la connessione
al server non avvenga con successo, l'errore venga presentato
all'interno della finestra dei messaggi. Inoltre si
noti la presenza dell'endpoint codificato all'interno
del codice. Ovviamente una soluzione più corretta
é quella di esternalizzare questa informazione,
magari tramite un parametro di un file di proprietà.
Listato 5
//...
public class ChatPanel extends JPanel {
//...
public ChatPanel( String user, String token ) {
super();
this.user = user;
this.token = token;
createUI();
try {
cc
= new ChatClient( user, token );
cc.setEndpoint(
"http://127.0.0.1:8080/soapchat/chat" );
cc.login();
}
catch( SOAPException ex ) {
addMessage(
"Impossibile connettersi al server della chat"
);
addMessage(
ex.toString() );
input.setEnabled(
false );
}
readingThread
= new Thread( new Runnable() {
public
void run() {
while(
doReading ) {
getMessages();
try
{
Thread.sleep(1000);
}
catch( InterruptedException e ) {
System.err.println(
e );
}
}
}
});
readingThread.start();
}
}
Il thread readingThread, come detto, si occupa di leggere
ogni secondo la lista dei messaggi ancora da leggere.
La frequenza di lettura può essere ovviamente
modificata e comporterà un maggior carico al
server, minore sarà il tempo di attesa del thread.
Questo esegue il ciclo fino a che non riceve ordine
di concludere, evento rilevato interrogando la variabile
doReading.
Implementare l'operatività
Una volta costruita l'involucro entro cui avverranno
le operazioni dell'utente, é necessario collegare
l'interfaccia utente alle chiamate SOAP implementate
da ChatClient.
Nel listato 6 é possibile vedere il metodo getMessages().
Questo viene richiamato dal thread per rinfrescare la
finestra dei messaggi ricevuti dall'utente. Il codice
non fa altro che invocare l'oggetto ChatClient, recuperare
l'array dei messaggi ricevuti e metterli a video tramite
il metodo addMessage().
Listato 6
protected
void getMessages() {
try
{
String[]
m = cc.get();
for(
int i=0; i<m.length; i++ )
addMessage(
m[i] );
}
catch(
SOAPException ex ) {
addMessage(
"Impossibile leggere i messaggi dal server"
);
addMessage(
ex.toString() );
}
}
Quando l'utente desidera inviare un messaggio, digita
il testo nel campo relativo e fa clic sul pulsante di
invio. Questo invoca il metodo send() che a sua volta
estrae il testo digitato e lo passa all'omonimo metodo
di ChatClient. Gli eventuali errori sono come di consueto
inviati nella finestra dei messaggi. (Listato 7)
Listato 7
protected
void send() {
try {
cc.send( input.getText() );
} catch( SOAPException ex ) {
addMessage(
"Impossibile inviare il messaggio" );
addMessage(
ex.toString() );
}
getMessages();
}
Quando l'utente desidera concludere la sessione di Chat,
fa clic sul pulsante di disconnessione che richiama
il metodo logout(). Questo, oltre a chiamare logout()
su ChatClient, pone doReading a false ed esce dalla
Virtual Machine.
Listato 8
protected
void logout() {
try {
cc.logout();
} catch( SOAPException ex ) {
System.err.println("Errore in fase di disconnessione
dal server di chat");
}
doReading = false;
System.exit(0);
}
Il client standalone è visibile in Figura 1.

Figura
1
L'Applet
Implementando l'applet client (listato 9), è
stato riscontrato un problema con l'implementazione
del runtime di JAXM.
Listato 9
import
java.applet.*;
import javax.swing.*;
public class ChatApplet extends JApplet {
ChatPanel
cp;
public
void init() {
String
user = getParameter("user");
String
passwd = getParameter("passwd");
user
= (user == null) ? "guest" : user;
passwd
= (passwd == null ) ? "guest" : passwd;
cp
= new ChatPanel( user, user+passwd);
getContentPane().add(
cp );
cp.suspend();
}
public
void start() {
cp.resume();
}
public
void stop() {
cp.suspend();
}
}
Il codice che si occupa di eseguire la POST del messaggio
SOAP, fa uso del metodo setRequestMethod() sulla classe
java.net.HttpURLConnection, il cui utilizzo è
vietato alle Applet. Il risultato è che il codice
solleva un'eccezione di sicurezza se eseguito nella
sand-box.
Nota: questa informazione è stata rilevata decompilando
i sorgenti di jaxm-api.jar e dunque potrebbe non essere
più vera in future release del software.
Per poter utilizzare JAXM, dunque, è stato necessario
firmare l'applet. Una guida che spiega come fare è
disponibile sul sito Javasoft al link [8], mentre altre
informazioni sono disponibili sui forum relativi ([9]).
In particolare sono stati utilizzati i seguenti comandi:
keytool
-genkey -alias signFiles -keystore jaxmstore -keypass
cic1995 -dname "cn=max" -storepass spcic95
jarsigner
-keystore jaxmstore -storepass spcic95 -keypass cic1995
-signedjar ssp_client.jar sp_client.jar signFiles
keytool
-export -keystore jaxmstore -storepass spcic95 -alias
signFiles -file CompanyCer.cer
Per importare il certificato sul client, sono stati
modificati i file di sistema cacerts e java.policy presenti
sotto jre/lib/security.
In particolare, per importare un certificato è
possibile utilizzare il seguente comando:
keytool
-import -alias company -file CompanyCer.cer -keystore
clientstore -storepass changeit
In figura 2 è possibile vedere l'esecuzione di
due copie dell'applet, utilizzando i file client.html
e client1.html vengono infatti inizializzate due copie
della stessa Applet, passando nomi utente diversi.

Figura
2
Conclusioni
In questa miniserie composta da tre articoli abbiamo
affrontato la problematica di costruire una semplice
applicazione client/server basata su SOAP. Nella realizzazione
del progetto sono state utilizzate le JAXM. Queste API
di SUN, sebbene ancora giovani, hanno consentito di
implementare con successo l'applicazione, sebbene sia
servita una certa quantità di codice. Questo
fatto é anche da imputare alla complessità:
non é stato interfacciato un solo messaggio SOAP
ma quattro diverse tipologie, più altre quattro
per le rispettive risposte. Quando il livello di complessità
sale, e il dominio applicativo si sposta da semplici
problematiche di messaging a vere e proprie applicazioni
distribuite (sebbene semplici), può essere il
caso di rivolgersi ad altri strumenti, come le JAX-RPC
che consentono un maggior livello di astrazione rispetto
al messaggio nudo e crudo.
Un altro problema è il meccanismo di request/response
utilizzato nell'applicazione: al crescere del numero
di client, le richieste al web server possono aumentare
notevolmente e sottoporre questo componente ad un carico
consistente. In questo caso sarebbe necessario utilizzare
un approccio di tipo "notify", dove il server
invia gli aggiornamenti ai diversi client, solo se questi
sono effettivamente presenti. Purtroppo questa modalità
non è disponibile nè in JAXM, nè
in JAX-RPC ed inoltre non sarebbe attuabile utilizzando
il protocollo HTTP che è di sua natura di tipo
request/response. Per implementare un meccanismo di
tipo "notify", sarebbe necessario utilizzare
qualche tecnologia di push che però reintrodurrebbe
quei problemi che risolti invece da HTTP.
Bibliografia
[1] Michele Sciabarrà - "Java Relay Chat",
Mokabyte, febbraio 1998
[2] Michele Sciabarrà - "Java Relay Chat,
parte 2", Mokabyte, aprile 1998
[3] Michele Sciabarrà - "Java Relay Chat,
parte 3", Mokabyte, maggio 1998
[4] Andrea Giovannini - "SOAP e Java: Integrazione
di applicazioni Java con il nuovo protocollo SOAP",
Mokabyte, gennaio 2001
[5] Massimiliano Bigatti - "Web Services (6)
- Java API for XML Messaging (JAXM)", Mokabyte,
Marzo 2002
[6] Massimiliano Bigatti - "Una chat basata
su SOAP - parte prima", Mokabyte, Maggio 2002
[7] Massimiliano Bigatti - "Una chat basata
su SOAP - parte seconda", Mokabyte, Giugno
2002
[8] Advanced Programming for the Java2 Platform. Chapter
10: Signed Applets http://developer.java.sun.com/developer/onlineTraining/Programming/JDCBook/signed.html
[9] http://forum.java.sun.com/thread.jsp?forum=63&thread=174214
Massimiliano Bigatti è SUN Certified Enterprise
Architect for Java Platform, Enterprise Edition Technology.
Si occupa di architetture applicative basate su Java
ed Internet, technical writing e di ... www.javawebservices.it.
|