Molti programmatori che iniziano a cimentarsi con Java imparano quasi subito a destreggiarsi con gli Applet, apprendendone celermente le funzionalità e le caratteristiche principali. Anche chi non "mastica" Java, tuttavia, è sovente in grado di dare una definizione corretta di cosa sia un Applet.
Un discorso analogo non può farsi, invece, per il cugino degli Applet: La Servlet. (o il Servlet, come qualcuno preferisce. Qui usiamo la notazione al femminile). Cerchiamo in questo articolo di capire quali siano le peculiarità delle Servlet, avvalendoci anche di esempi pratici.
Cos'è Servlet?
Che cos'è, dunque, una Servlet? Semplicemente, un programma scritto in Java e residente su un server, in grado di gestire le richieste generate da uno o più client, attraverso uno scambio di messaggi tra il server ed i client stessi che hanno effettuato la richiesta. Tipicamente sono collocate all'interno di Application Server o Web Application Server come, ad esempio, Tomcat.
Visto che, come detto, le Servlet sono scritte in Java esse possono avvalersi interamente delle Java API che, come è noto, consentono di implementare con relativa semplicità anche svariate funzionalità complesse e importanti come, ad esempio, l'accesso ad un database. Ad esse si aggiungono, poi, le più specifiche servlet API, che mettono a disposizione un'interfaccia standard utilizzata per gestire la comunicazione tra un client Web ed una Servlet.
È importante sottolineare il fatto che le Servlet, a differenza degli Applet, non hanno delle GUI associate direttamente ad esse. Pertanto, le librerie AWT e Swing non verranno mai utilizzate direttamente quando si desidera implementare una Servlet.
Altrettanto interessante è il fatto che, almeno in teoria, una Servlet non rappresenta unicamente (come invece si è spesso portati a ritenere) applicazioni basate sul protocollo HTTP (ovvero, un'applicazione di tipo Web). Per gli scenari non Web, infatti, si può fare riferimento alla classe javax.servlet.GenericServlet
che, appunto, è in grado di utilizzare un protocollo generico. D'altra parte è necessario ammettere che, quasi sempre, quando si parla di Servlet si fa riferimento implicitamente ad una estensione particolare della classe GenericServlet
identificata, per la precisione, dalla classe HttpServlet
. Entrambe queste classi (GenericServlet
e HttpServlet
), implementano l'interfaccia javax.servlet.Servlet
.
Il flusso di una Servlet
Vediamo di illustrare, adesso, come lavora una HttpServlet avvalendoci dello schema seguente:
È possibile riassumere il flusso rappresentato nella figura in questo modo:
- Un client invia una richiesta (request) per una servlet ad un web application server.
- Qualora si tratti della prima richiesta, il server istanzia e carica la servlet in questione avviando un thread che gestisca la comunicazione con la servlet stessa. Nel caso, invece, in cui la Servlet sia già stata caricata in precedenza (il che, normalmente, presuppone che un altro client abbia effettuato una richiesta antecedente quella attuale) allora verrà, più semplicemente, creato un ulteriore thread che sarà associato al nuovo client, senza la necessità di ricaricare ancora la Servlet.
- Il server invia alla servlet la richiesta pervenutagli dal client
- La servlet costruisce ed imposta la risposta (response) e la inoltra al server
- Il server invia la risposta al client.
È importante sottolineare che il contenuto della risposta non è necessariamente basato su un algoritmo contenuto all'interno della Servlet invocata (questo può essere, semmai, il caso di applicazioni molto elementari) ma, anzi, è spesso legato ad interazioni della Servlet stessa con altre sorgenti di dati (data source) rappresentate, ad esempio, da Data Base, file di risorsa e, non di rado, altre Servlet.
HttpServletRequest e HttpServletResponse
Abbiamo visto, esaminando il flusso di esecuzione di una servlet, che il flusso stesso è incentrato su due componenti fondamentali: la richiesta (request, inviata dal client verso il server) e la risposta (response, inviata dal server verso il client). In Java, questi due componenti sono identificati, rispettivamente, dalle seguenti interfacce:
javax.servlet.http.HttpServletRequest
javax.servlet.http.HttpServletResponse
Un oggetto di tipo HttpServletRequest
consente ad una Servlet di ricavare svariate informazioni sul sistema e sull'ambiente relativo al client. L'oggetto di tipo HttpServletResponse
, invece, costituisce la risposta da inviare al client e che, come si è detto, può essere dipendente da svariati data source.
ServletContext
Un'altra importante interfaccia messa a disposizione dal Servlet engine è la javax.servlet.ServletContext
. Attraverso di essa è possibile trovare un riferimento al contesto (context) di un'applicazione, ovvero ad una serie di informazioni a livello globale condivise tra i vari componenti che costituiscono l'applicazione stessa.
I metodi principali di una Servlet
Creare una Servlet vuol dire, in termini pratici, definire una classe che derivi dalla classe HttpServlet
. I metodi più comuni per molti dei quali si è soliti eseguire l'overriding nella classe derivata sono i seguenti:
void doGet(HttpServletRequest req, HttpServletResponse resp)
Gestisce le richieste HTTP di tipo GET. Viene invocato da service()void doPost(HttpServletRequest req, HttpServletResponse resp)
Gestisce le richieste HTTP di tipo POST. Viene invocato da service()void service(HttpServletRequest req, HttpServletResponse resp)
Viene invocato al termine del metodo <br/init(). Gestisce la richiesta contenuta nell'oggetto req, inviando poi la risposta attraverso l'oggetto resp. Il metodo service si occupa di smistare le chiamate ad altri metodi (come doGet() o doPost()), in funzione della tipologia di richiesta HTTP.void doPut(HttpServletRequest req, HttpServletResponse resp)
Viene invocato attraverso il metodo service() per consentire di gestire una richiesta HTTP di tipo PUT. Tipicamente, una richiesta del genere consente ad un client di inserire un file su un server, similmente ad un trasferimento di tipo FTP.void doDelete(HttpServletRequest req, HttpServletResponse resp)
Viene invocato attraverso il metodo service() per consentire ad una Servlet di gestire una richiesta di tipo HTTP DELETE. Questo genere di richiesta viene utilizzata per rimuovere un file dal server.void init()
Viene invocato soltanto una volta dal Servlet Engine al termine del caricamento della servlet ed appena prima che la servlet stessa inizi ad esaudire le richieste che le pervengono.void destroy()
È invocato direttamente dal Servlet Engine per scaricare una servlet dalla memoria.String getServletInfo()
È utilizzato per ricavare una stringa contenente informazioni di utilità sulla Servlet (ad es.: nome della Servlet, autore, copyright). La versione di default restituisce una stringa vuota.ServletContext getServletContext()
Viene usato per ottenere un riferimento all'oggetto di tipo ServletContext cui appartiene la Servlet.
I vantaggi delle Servlet
Qualcuno potrebbe domandarsi per quale motivo possa essere più vantaggioso utilizzare le Servlet piuttosto che affidarsi a tecnologie, ancora abbastanza utilizzate, come la Common Gateway Interface (CGI). Beh, le differenze non sono trascurabili. Tra le più evidenti possiamo citare:
- Efficienza. Come abbiamo detto le servlet vengono istanziate e caricate una volta soltanto, alla prima invocazione. Tutte le successive chiamate da parte di nuovi client vengono gestite creando dei nuovi thread che si prendono carico del processo di comunicazione (un thread per ogni client) fino al termine delle rispettive sessioni. Con le CGI, tutto questo non accadeva. Ogni client che effettuava una richiesta al server causava il caricamento completo del processo CGI con un degrado delle performance facilmente immaginabile.
- Portabilità: Grazie alla tecnologia Java, le servlet possono essere facilmente programmate e "portate" da una piattaforma ad un'altra senza particolari problemi.
- Persistenza: Dopo il caricamento, una servlet rimane in memoria mantenendo intatte determinate informazioni (come la connessione ad un data base) anche alle successive richieste.
- Gestione delle sessioni: Come è noto il protocollo HTTP è un protocollo stateless (senza stati) e, pertanto non in grado di ricordare i dettagli delle precedenti richieste provenienti da uno stesso client. Le servlet (lo vedremo in un altro articolo) sono in grado di superare questa limitazione.
Un esempio
Mettiamo in pratica le nozioni basilari che abbiamo finora enunciato e costruiamo una semplice Servlet il cui compito sarà quello di mostrare una tabella di conversione della temperatura da gradi Celsius a Fahrenheit.
Listato 1. Una servlet che visualizza la conversione da gradi Celsius a Fahrenheit
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.DecimalFormat;
import java.io.PrintWriter;
import javax.servlet.http.HttpServlet;
public class CelsiusToFahrenheit extends HttpServlet
{
private static final DecimalFormat FMT = new DecimalFormat("#0.00");
private static final String PAGE_TOP = ""
+ "<html>"
+ "<head>"
+ "<title>Tabella di conversione da Celsius a Fahrenheit</title>"
+ "</head>"
+ "<body>"
+ "<h3>Tabella di conversione da Celsius a Fahrenheit</h3>"
+ "<table>
+ "<tr>"
+ "<th>Celsius</th>"
+ "<th>Fahrenheit</th>"
+ "</tr>"
;
private static final String PAGE_BOTTOM = ""
+ "</table>"
+ "</body>"
+ "</html>"
;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println(PAGE_TOP);
for(double cels = 15; cels <= 35; cels += 1.0)
{
Double fahre = (cels * 1.8) + 32;
out.println("<tr>");
out.println("<td>" + FMT.format(cels) + "</td>");
out.println("<td>" + FMT.format(fahre) + "</td>");
out.println("</tr>");
}
out.println(PAGE_BOTTOM);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
this.doGet(request,response);
}
public String getServletInfo()
{
return super.getServletInfo();
}
}
Abbiamo suddiviso la pagina HTML generata dalla servlet in 3 parti: La parte superiore (TOP), la parte inferiore (BOTTOM) ed infine, quella centrale. Le prime due sono rappresentate da stringhe statiche, definite come costanti (PAGE_TOP, PAGE_BOTTOM). La parte centrale, invece, è generata dinamicamente e gestita dal metodo doGet()
della Servlet.
Si noti come, per comodità, sia stato fatto l'override anche del metodo doPost(),
il cui codice si preoccupa semplicemente di inoltrare la richiesta al metodo doGet()
.
Come effettuare il Deploy della Servlet su Tomcat
Per sapere come effettuare il deploy su Tomcat della Servlet mostrata nel nostro esempio, si rimanda all'articolo dedicato a Tomcat.
Effettuato correttamente il deploy e digitando, sul browser l'indirizzo della servlet (http://localhost:8080/SimpleServlet/SimpleServlet
) potremo visualizzare il contenuto web generato dalla nostra servlet:
Alla prossima.