MokaByte 47 - Dicembre 2000

di
Andrea Cisternino
Inviare oggetti Java 
via HTTP 
Attraversare i firewall per 
salvare oggetti Java
In questo articolo vedremo come sia possibile inviare l'instanza di un oggetto Java al server utilizzando il protocollo HTTP. 

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.

Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


MokaByte®  è un marchio registrato da MokaByte s.r.l.
Java® è un marchio registrato da Sun Microsystems; tutti i diritti riservati
E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it