In questo articolo parleremo di un di una delle tecnologie stabili per condividere le risorse server-side e che sta alla base della programmazione web in java: la Servlet. Ne analizzeremo in particolare il ciclo di vita in modo da poterne comprendere al meglio il funzionamento. La Servlet è un componente Web indipendente dalla piattaforma e basato su container utilizzata per generare contenuti dinamici in una pagina Web. Poichè le Servlet sono eseguite in un ambiente multi-thread fornito dal container, gli eventi del ciclo di vita sono completamente dipendente dalla implementazione specifica del container. La comprensione del ciclo di vita di una Servlet è la chiave per capire la complessità delle funzionalità di basso livello della programmazione basata su servlet. Questo articolo fornisce un assaggio di questo processo in maniera concisa.
Indice
Il Container
Il container, o servlet engine, come è talvolta indicato, è l’ambiente di esecuzione delle servlet. Il container può essere parte di un Web server o essere fornito come un add-on di questo oppure può essere all’interno di un Application Server o ancora, come succede nelle applicazioni moderne, essere fornito embedded, cioè come parte di un’applicazione. Esistono vari servler engine, tra questi citiamo naturalmente Tomcat, che è l’implementazione di riferimento della specifica delle servlet, ma anche Jetty o Undertow; o, tra gli Application server, citiamo GlassFish, IBM Websphere o Oracle Weblogic. Le servlet non sono altro che normalissime classi java, che compilate quindi generano bytecode. Il codice bytecode viene caricato dinamicamente all’interno del container ed eseguito. Le classi servlet interagiscono in generale con il server attraverso un meccanismo di richiesta / risposta HTTP (o HTTPS) implementata dal motore servlet. La funzione primaria del container è di contenere classi servlet e gestire il loro ciclo di vita.
Una tipica sequenza di eventi
Per meglio comprendere la cosa vediamo subito una tipica sequenza di eventi attraverso la quale passa una servlet:
- Un client (ad esempio, un browser Web) accede a un server Web e fa una richiesta HTTP.
- La richiesta viene ricevuta dal server Web che la redirigge al servlet container. Il servlet container può essere in esecuzione nello stesso processo del server Web,
in un processo differente sullo stesso host del web server, o su un host diverso dal server Web. - Il container determina quale servlet chiamare in causa basandosi sulle preimpostate configurazioni, e la chiamata avviene passando due oggetti che rappresentano la richiesta (request) e
risposta (response) HTTP. - La servlet utilizza l’oggetto request per scoprire chi è l’utente da cui proviene, estrarne gli eventuali parametri passati insieme a questa e altre informazioni rilevanti al fine di interpretare correttamente la richiesta. In base alla richiesta viene eseguita la logica programmata, preparata la risposta e inviata al client mediante l’oggetto response.
- Una volta che la servlet ha terminato l’elaborazione della richiesta, il container si assicura che la risposta sia stata correttamente deliverata, e ripassa il controllo al Web server.
Il ciclo di vita
Una servlet è gestita attraverso un ciclo di vita ben definito che definisce come viene caricata e istanziata, come viene inizializzata, come vengono gestite le richieste da parte dei clienti, e infine come viene messa fuori servizio. La figura sotto riassume le varie fasi:
Questo ciclo di vita è espresso chiaramente nella definizione della API mediante la definizione di metodi specifici e cioè dal metodo init, service, e destroy, tutti metodi dell’interfaccia javax.servlet.Servlet che tutte le servlet devono implementare direttamente o indirettamente attraverso le classi astratte GenericServlet o HttpServlet. Questi metodi sono quelli che verrano poi invocati dal container per gestire appunto il ciclo di vita della servlet. La figura sotto riassume le Interfacce e le classi in gioco quando si lavora con le servlet:
Ma vediamo in dettaglio le varie fasi del ciclo di vita di una servlet:
Fase di caricamento e di creazione dell’Istanza (1) (2)
E’ il servlet container il responsabile del caricamento e della creazione delle istanze delle servlet. Il caricamento avviene o all’avvio del servlet engine oppure al momento in cui arriva la prima richiesta da parte di un client. Una volta avviato il servlet engine, questo localizza dove sono le classi servlet, che possono trovarsi sul file system locale,su un file system remoto, o su altri servizi di rete e le carica usando il normale meccanismo di caricamento delle classi java. Dopo aver caricato una classe Servlet, il container la istanzia per l’uso.
Inizializzazione (3)
Dopo che l’oggetto servlet è stato istanziato, il container deve inizializzare la servlet prima che possa gestire le richieste da parte dei clienti. La fase di inizializzazione in genere serve ad inizializzare le risorse di cui ha bisogno la servlet per poter gestire le richieste, come ad esempio aprire le connessioni JDBC verso un database oppure collegarsi a servizi remoti ecc.. operazioni che in genere sono costose in termini di risorse e che vengono eseguite una sola volta, appunto in questa fase di inizializzazione. Tutte queste operazioni avvengono nel metodo init che viene invocato dal container passando in input un oggetto in singola istanza per servlet (cioè ne esiste uno per ogni di tipo di servlet dichiarata) che implementa l’interfaccia ServletConfig. Questo oggetto di configurazione permette alla servlet di accedere ai parametri di inizializzazione (coppie nome-valore) presenti nella configurazione della applicazione web. L’oggetto di configurazione consente inoltre alla servlet di poter accedere ad un’altro oggetto (che implementa l’interfaccia ServletContext) che descrive l’ambiente di runtime della servlet.
Gestione delle richieste (4)
Dopo che una servlet è stata inizializzata correttamente, il container può utilizzarla per gestire le richieste dei clienti. Le richieste sono rappresentate mediante oggetti di tipo ServletRequest mentre la risposta è “confezionata” dalla servlet chiamando i metodi esposti dall’oggetto di tipo ServletResponse. Entrambi gli oggetti sono passati come argomento al metodo service dell’interfaccia Servlet. Nel caso di una richiesta mediante protocollo HTTP, la più comune usata nella programmazione web, gli oggetti forniti dal container sono di tipo HttpServletRequest e HttpServletResponse. Un container può inviare richieste simultanee attraverso il metodo service. Spetta al programmatore gestire opportunamente l’elaborazione all’interno di tale metodo nel caso in cui arrivino più richieste contemporaneamente.
Considerazioni sulla gestione delle richieste HTTP
La sottoclasse astratta HttpServlet estende i metodi dell’interfaccia Servlet con dei metodi aggiuntivi oltre a quelli base che sono chiamati automaticamente dal metodo service nella classe HttpServlet per gestire appunto la gestione delle richieste basate su HTTP. Questi metodi sono:
- doGet per la gestione di richieste HTTP GET
- doPost per la gestione delle richieste HTTP POST
- doPut per la gestione delle richieste HTTP PUT
- doDelete per la gestione HTTP DELETE
- doHead per la gestione di richieste HTTP HEAD
- doOptions per la gestione di richieste HTTP OPZIONI
- doTrace per la gestione delle richieste HTTP TRACE
I metodi maggiormente usati sono doGet e doPost. Gli altri metodi sono in genere usati in applicazioni che fanno un uso più spinto del protocollo HTPP, come potrebbe essere ad esempio una API Rest-full.
Distruzione (5)
In questa fase, la servlet viene rimossa dal container non prima però che quest’ultimo abbia invocato il metodo destroy dell’interfaccia Servlet. Questo metodo assicura che la servlet abbia terminato in maniera corretta la propria elaborazione dopodichè rilascia tutte le risorse utilizzate o create durante il suo ciclo di vita.
Conclusioni
L’uso corretto delle fasi del ciclo di vita : inizializzazione, gestione delle richieste e distruzione, fanno in modo che le servlet possano gestire le risorse dell’applicazione in modo efficiente. Durante l’inizializzazione, come abbiamo visto, si prepara l’ambiente alla servlet, inizializzando tutte le risorse di cui questa ha bisogno per poter gestire la fase successiva, quella di servizio. Risorse queste rilasciate nella fase di distruzione. Queste tre fasi sono una rappresentazione lorda del ciclo di vita servlet ed è gestito, come si è vìsto, interamente dal container, dando al programmatore la possibilità di dedicarsi principalmente al business dell’applicazione piuttosto che alla gestione di altri aspetti.
Riferimenti