Scrivere un servlet
che riceve un oggetto
Per
chi non sia familiare con la tecnologia Servlet facciamo un piccolo riepilogo.
Un servlet HTTP è semplicemente una classe che estende la classe
HttpServlet, parte del package javax.servlet.http.
Per
gestire le varie azioni del protocollo HTTP è sufficiente ridefinire
il metodo doXXX, dove XXX indica l'azione del protocollo. Ad esempio per
gestire le richieste di tipo GET basta ridefinire il metodo doGet. Lo stesso
si può dire del metodo doPost. Questi metodi hanno due parametri:
un oggetto di tipo HttpServletRequest e uno di tipo HttpServletResponse
il cui ruolo dovrebbe essere chiaro dal nome.
In
sostanza un servlet HTTP analizza la richiesta utilizzando i metodi dell'oggetto
HttpServletRequest e prepara ed invia la risposta utilizzando l'oggetto
HttpServletResponse.
Cerchiamo
ora di capire come scrivere un servlet che riceva un oggetto serializzato
da un applet e lo stampa utilizzando System.out. Il codice del Servlet
è il seguente:
import
java.io.ObjectInputStream;
import
java.io.InputStream;
import
java.io.PrintWriter;
import
java.io.IOException;
import
javax.servlet.ServletException;
import
javax.servlet.http.HttpServlet;
import
javax.servlet.http.HttpServletResponse;
import
javax.servlet.http.HttpServletRequest;
public
class ObjectServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
ObjectInputStream in = new ObjectInputStream(request.getInputStream());
try {
Object o = in.readObject();
System.out.println(o);
} catch(ClassNotFoundException e) {}
}
}
// ObjectServlet
Come
si può osservare si estende la classe HttpServlet e si ridefinisce
il metodo doPost. Quando si riceve la richiesta di POST, secondo quanto
visto nella puntata precedente, si recupera no i dati inviati e si ricostruisce
l'oggetto.
Nel
metodo doPost si ottiene un oggetto di tipo InputStream sul quale costruiamo
un ObjectInputStream necessario a deserializzare l'oggetto ricevuto con
la richiesta.
Osserviamo
come l'input stream non contiene le informazioni dell'header della richiesta.
Queste informazioni sono state infatti già lette dal Servlet Engine
che li ha memorizzati nell'oggetto di tipo HttpServletRequest.
La
riscostruzione dell'oggetto si risolve quindi semplicemente in una chiamata
al metodo readObject. Una volta ricostruito l'oggetto semplicemente viene
stampato utilizzando System.out.
L'applet
che invia l'oggetto sarà simile al seguente:
import
java.io.ByteArrayOutputStream;
import
java.io.ObjectOutputStream;
import
java.io.OutputStream;
import
java.io.Serializable;
import
java.io.IOException;
import
java.net.Socket;
import
java.net.InetAddress;
import
java.awt.event.ActionListener;
import
java.awt.event.ActionEvent;
import
java.awt.Button;
public
class ObjectApplet extends java.applet.Applet implements ActionListener
{
public void send(InetAddress addr, 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(addr, p);
OutputStream sout = s.getOutputStream();
sout.write(outb.toString().getBytes());
sout.write(serobj);
sout.write("\r\n".getBytes());
s.close();
}
public void init() {
Button b = new Button("Invia");
b.addActionListener(this);
add(b);
}
public void actionPerformed(ActionEvent evt) {
try {
String s = "Hello Server";
send(InetAddress.getByName(getCodeBase().getHost()),
8080, "/objser/servlet/ObjectServlet", s);
} catch(IOException e) {
}
}
}
// ObjectApplet
Il
metodo send è quello discusso nella puntata precedente. L'applet
crea un semplice bottone che quando viene premuto invia l'oggetto serializzato,
in questo caso una stringa, al server.
Nel
caso si voglia inviare una risposta per l'avvenuta ricezione all'applet
sarà necessario ottenere all'interno del metodo doPost un oggetto
di tipo OutputStream, impostare il tipo MIME della risposta come application/java-object
e aggiungere un oggetto serializzato in modo che sia inviato all'applet.
Da parte sua l'applet dovrà leggere il risultato dell'operazione.
Questo può essere un utile esercizio per verificare se si è
realmente capito il meccanismo.
Inviare un oggetto
ad un applet
L'invio
di un oggetto ad un applet avviene in modo assolutamente analogo alla comunicazione
dall'applet al server. L'unica differenza sostanziale è che è
sempre l'applet a dover iniziare la comunicazione con una POST.
Il
significato della richiesta sarà: so che mi deve essere inviato
un oggetto. E la risposta consisterà nell'istanza dell'oggetto.
Possiamo quindi intuire la struttura del metodo doPost in questo caso:
...
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
ObjectInputStream in = new ObjectInputStream(request.getInputStream());
try {
ObjectRequest r = (ObjectRequest) in.readObject();
// Trova l'oggetto o in qualche modo
Serializable o = findObject(r);
// Imposta il MIME type
response.setContentType("application/java-object");
// Invia l'oggetto
ByteArrayOutputStream outb = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(outb);
out.writeObject(o);
byte[] serobj = outb.toByteArray();
response.setContentLength(serobj.length);
OutputStream outd = response.getOutputStream();
outd.write(serobj);
} catch(ClassNotFoundException e) {}
}
...
Facile
immaginare anche il codice necessario all'applet per leggere i dati:
...
Object receiveObject(Socket s) throws IOException, ClassNotFoundException
{
InputStream in = s.getInputStream();
LineNumberReader lin = new LineNumberReader(new InputStreamReader(in));
Hashtable params = new Hashtable();
String line = lin.readLine();
int idx;
// Legge l'header fino alla linea vuota
while ((idx = line.indexOf(":")) != -1) {
params.put(line.substring(0, idx), line.substring(idx + 1).trim());
line = lin.readLine();
}
ObjectInputStream oin = new ObjectInputStream(in);
o = oin.readObject();
return o;
}
...
Osseviamo
come sia sempre presente il riferimento all'eccezione ClassNotFoundException.
Questo è dovuto al fatto che il processo di deserializzazione di
un oggetto richiede che sia presente la definizione della classe (il file
.class). Ciò significa che sia l'applet che il servlet dovranno
accedere alla definizione delle classi che vengono trasferite.
Possibili
applicazioni
La
tecnica presentata in questi due articoli può essere utilizzata
in numerose applicazioni. È infatti molto comodo poter inviare da
un ambiente di esecuzione Java ad un altro l'istanza di una classe.
Un'applicazione
naturale può essere quella di rendere persistente lo stato di un
applet Java sul server: quando l'applet sarà caricato nuovamente
accderà al server e si farà inviare lo stato in forma di
oggetto che sarà pronto per essere utilizzato per tornare al punto
in cui era stato chiuso.
Un'altra
applicazione decisamente interessante è quella dell'invocazione
di metodi remoti attraverso HTTP. In questo caso infatti basta inviare
un oggetto al servlet che racchiuda gli oggetti che sono i parametri della
chiamata di metodo. La risposta sarà inviata come risultato. Attraverso
un opportuno bridge si può in questo modo attraversare un firewall
e utilizzare tecnologie come RMI: si realizzano dei semplici wrapper per
i metodi remoti che utilizzano HTTP per il tratto che deve attraversare
il firewall.
Conclusioni
In
questo articolo abbiamo visto come realizzare un servlet che riceva da
un applet un oggetto Java serializzato attraverso una connessione HTTP.
Abbiamo anche discusso delle possibili applicazioni della tecnica presentata.
In
una possibile terza puntata di questa mini serie di articoli potrebbe essere
interessante realizzare un bridge generico per RMI in modo da poter fruire
della piattaforma distribuita di Java anche quando i firewall impediscono
la comunicazione a protocolli differenti da HTTP.
Gli
esempi descritti in questo articolo (funzionanti con Tomcat) possono essere
scaricati qui |