Capita
spesso, nella realizzazione di applet, di avere la necessità di
memorizzare degli oggetti presenti nell'applet sul server. Ci sono principalmente
due ottime ragioni per fare ciò: è necessario memorizzare
lo stato dell'applet sul server per poi recuperarlo in un secondo momento;
si vuole inviare un oggetto al server su cui verrà passato, in qualche
modo, ad una JVM che lo utilizzerà in qualche modo.
La
prima soluzione che viene in mente quando si pone questo problema è
quella di usare un Socket TCP/IP in cui l'Applet serializza gli oggetti
necessari che vengono quindi ricostruiti sul server.
Questa
soluzione, seppur percorribile, risulta spesso impraticabile a causa dei
Firewall che cercano di controllare le connessioni da e verso una rete
locale. Diventa quindi necessario trovare un modo per aggirare questa restrizione
tenendo conto che le connesioni HTTP sono le uniche su cui normalmente
si può fare affidamento.
In
questo articolo vedremo innanzitutto come inviare un oggetto Java da un
applet al server utilizzando una connessione dedicata e successivamente
sostituiremo questa connessione con una connessione HTTP standard.
Discuteremo
infine possibili applicazioni di questa tecnica e le implicazioni in termini
di sicurezza nel suo impiego.
Serializzazione
di oggetti in un Socket TCP/IP
La
prima soluzione che viene in mente per inviare un oggetto Java per la rete
si basa sull'apertura di un Socket al server e si utilizza un
ObjectOutputStreamper
serializzare l'oggetto (che deve implementare l'interfaccia java.io.Serializable)
nel canale che viene poi riscostruito sul server utilizzando un ObjectInputStream.
Ovviamente
sia il server che il client devono possedere la definizione della classe
a cui appartiene l'oggetto che viene inviato attraverso il Socket.
Il
codice che invia l'oggetto sarà quindi simile al seguente:
...
public void send(InetAddress s, int p, Serializable obj)
throws IOException {
Socket s = new Socket(s, p);
ObjectOutputStream out;
out = new ObjectOutputStream(s.getOutputStream());
out.writeObject(obj);
s.close();
}
...
Il
codice presentato è ovviamente minimale nel senso che non viene
effettuato alcun controllo sugli errori, né avviene altro scambio
tra server e client per controllare la consistenza della comunicazione.
Sul
server ci sarà un thread responsabile della ricezione degli oggetti
che vengono memorizzati in un buffer in attesa che qualcuno li recuperi
utilizzando il metodo receive:
...
private Vector buffer;
...
public void run() {
ServerSocket ss = new ServerSocket(PORT);
while (acceptClients) {
Socket s = ss.accept();
ObjectInputStream in;
in = new ObjectInputStream(s.getInputStream());
buffer.addElement(in.readObject());
s.close();
}
}
...
public Object receive() {
if (buffer.size() == 0)
return null;
Object ret = buffer.elementAt(0);
buffer.removeElementAt(0);
return ret;
}
...
In
questo caso il metodo run() è
eseguito da un thread che accetta le connessioni in ingresso. Gli oggetti
ricevuti vengono memorizzati in un vettore che viene consultato quando
viene invocato il metodo receive().
Come
abbiamo già detto questa soluzione non presenta alcun problema in
linea di principio (anzi è molto flessibile poiché l'applicazione
può decidere cosa comunicare secondo le proprie necessità).
L'unico problema che presenta è legato all'impiego, sempre più
diffuso, di firewall per
proteggere le reti locali. Un firewall permette
tipicamente l'uso solo di un numero controllato di porte TCP in modo da
controllare il traffico e i protocolli usati alla ricerca di possibili
intrusioni in una rete da parte di malintenzionati.
L'obiettivo
diviene quindi ora quello di ottenere la stesso risultato ma utilizzando
il protocollo HTTP per inviare i dati. Si usa questo protocollo poicé
è lo stesso utilizzato per trasferire il contenuto delle pagine
Web e quindi è normalmente consentito il suo impiego daifirewall.
Preparare
ed inviare un oggetto con HTTP
Poichè
dovremo utilizzare il protocollo HTTP per inviare l'oggetto preoccupiamoci
innanzitutto di memorizzare in un buffer la sua forma serializzata. Successivamente
ci occuperemo di inviare i dati al server.
Per
memorizzare l'oggetto da inviare in un array utilizziamo unByteArrayOutputStream,
un
oggetto che si comporta come un OutputStreamanche
se memorizza in un array di byte i dati che riceve.
La
serializzazione avviene come segue:
...
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buffer);
out.writeObject(obj);
...
L'oggetto
serializzato è contenuto in buffer pronto per essere utilizzato
nell'invio vero e proprio.
Una
volta preparato l'oggetto possiamo inviarlo utilizzando il metodo POST
del protocollo HTTP. Questa richiesta è formata da un'intestazione
seguita dai dati che abbiamo memorizzato temporaneamente nella variabile
buffer.
La
richiesta HTTP sarà simile alla seguente:
POST /someurl HTTP/1.0
User-Agent: JavaObjectTunnel
Content-Type: application/java-object
Content-Length: 253
// Binary content starts here
...
In
pratica il codice che scriveremo differirà non molto da quello scritto
in precedenza: dovremo soltanto premettere l'intestazione HTTP e la parte
del cliente che invia l'oggetto rimarrà pressoché invariata.
Bisogna però dedicare un po' di attenzione alla prima riga: viene
infatti riferita l'URL che riceverà l'oggetto serializzato. Per
il momento trascuriamo quale debba essere il suo valore tenendo però
conto del fatto che è un parametro importante: indica infatti a
chi, sul server, sarà consegnato l'oggetto.
Vediamo
innanzitutto come si può realizzare il metodo send che invia l'oggetto
utilizzando però il protocollo HTTP:
...
public void send(InetAddress s, int p, String path,
Serializable obj) throws IOException {
// Save the object's instance
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buffer);
out.writeObject(obj);
// Prepare the header
byte[] serobj = buffer.toByteArray();
StringBuffer outb = new StringBuffer();
outb.append("POST ");
outb.append(path);
outb.append("HTTP/1.0\r\nUser-Agent: JavaObjectTunnel\r\n");
outb.append("Content-Type: application/java-object\r\n");
outb.append("Content-Length: ");
outb.append(serobj.length);
outb.append("\r\n\r\n");
// Open the connection and send the data
Socket s = new Socket(s, p);
OutputStream out = s.getOutputStream();
out.write(outb.toString().getBytes());
out.write(serobj);
out.write("\r\n".getBytes());
s.close();
}
...
Il
codice presentato riassume quanto detto finora. Si noti l'uso della stringa
"\r\n" come separatore di linea nell'intestazione.
Poiché
si fa uso del protocollo HTTP il metodo va chiamato con il parametro p
che vale 80, la porta standard per HTTP. L'indirizzo s del server sarà,
nel caso di un applet, quello del server da cui è stato caricato
l'applet.
Ricevere
un oggetto sul server
L'impiego
di HTTP sul client non ha comportato grossi cambiamenti nel codice originale;
lo stesso non si può dire per quanto riguarda il server. In questo
caso infatti la connessione avviene sulla porta 80 che è normalmente
controllata dal Web Server e quindi non può essere agganciata da
un programma Java.
Per
ricevere l'oggetto sul server dobbiamo quindi prima convincere il Web Server
a passare i dati ricevuti alla JVM su cui verrà eseguita la ricostruzione
dell'oggetto utilizzando il codice seguente che già conosciamo:
...
ObjectInputStream in =
new ObjectInputStream(new ByteArrayInputStream(buffer));
Object ret = in.readObject();
...
Ciò
che invocherà in qualche modo questo codice Java è rappresentato
da uno script/applicazione/CGI lato server. In pratica un programma eseguito
dal Web Server raccoglie i dati inviati dal cliente e si occuperà
di inoltrarli alla JVM come desiderato. Il parametropathspecificato
nel metodo send visto nel paragrafo precedente indica lo script da invocare
sul server destinato a ricevere i dati con la POST.
Non
essendo questo un articolo sulle tecnologie lato server ci limiteremo a
descrivere possibili alternative per far giungere l'istanza dell'oggetto
a destinazione. La distinzione fondamentale è tra l'uso diJava
Servlet o
meno sul server. Se infatti si usano servlet sul server l'oggetto può
essere ricostruito direttamente dal programma che viene eseguito dal Web
Server; in caso contrario sarà necessario salvare l'oggetto ricevuto
in un file che verrà aperto poi da un'applicazione Java invocata
dallo script stesso.
È
infine possibile sfruttare dei meccanismi per la comunicazione interprocesso
(IPC) per passare l'oggetto ricevuto ad una JVM in esecuzione. Si potrebbe
ad esempio utilizzare il codice visto inizialmente che usa le Socket in
quanto sulla macchina locale (il server) non esiste limitazione alle Socket
che possono essere utilizzate come visto in precedenza.
Uso e considerazioni
sulla sicurezza
Ci
sono molti impieghi possibili della tecnica illustrata in questo articolo.
La sottomissione di dati ad esempio da un applet al server può risultare
semplificata dall'invio di un oggetto piuttosto che simulando l'invocazione
di una form. Un altro uso interessante può essere quello di salvare
lo stato di un applet in modo che quando un utente riaccede alla stessa
pagina trova l'applicazione al punto in cui l'aveva lasciata; in questo
caso l'applicazione lato server salverà l'oggetto in un file o in
una base di dati per poi recuperarlo e rispedirlo quando richiesto dall'applet.
Il
fatto che si sposta l'istanza di una classe non significa che si sposti
la sua definizione: la tecnica che abbiamo appreso non permette quindi
la mobilità del codice. Poiché il server deve conoscere la
definizione di una classe per poter ricostruire un oggetto ricevuto non
c'è il rischio che possa essere eseguito codice non autorizzato
sul server. A meno che, ovviamente, la classe non esegua comandi pericolosi
per l'integrità del sistema utilizzando senza controllare i valori
dei propri attributi che sono ricevuti in rete.
Conclusioni
In
questo articolo abbiamo visto come inviare oggetti Java da un cliente ad
un server utilizzando il protocollo HTTP. Nell'introduzione abbiamo presentato
il problema come legato ad un applet che vuole comunicare col server. Dovrebbe
essere evidente, ora che abbiamo visto il codice, che questa tecnica può
essere impiegata anche in un'applicazione standalone. In questo caso si
possono realizzare applicazioni capaci di attraversare firewall per comunicare
tra loro. |