'Servlets'

Els servlets són programes petits que s’executen dintre dels servidors d’aplicacions. En concret, en aquest apartat s’explicarà:

  • Introducció als servlets
  • Configuració dels servlets utilitzant anotacions i fitxers XML
  • Diferents classes Java relacionades amb els servlets, com el ServletContext o contenidor d’aplicacions

En aquest apartat també parlarem del cicle de vida d’un servlet, els seus paràmetres inicials i com redirigir una petició.

Tots els conceptes que es veuran s’explicaran sempre partint de l’exemple. Quan acabeu aquest apartat estareu preparats per crear un servlet, analitzar les peticions i crear les respostes adequades.

'Servlet' Hola, Món

En aquest apartat aprendreu a utilitzar servlets, les seves etiquetes XML i les anotacions, i s’explicarà el ServletContext com a contenidor d’aplicacions.

Orígens dels 'servlets'

Sun Microsystems va escriure la primera especificació dels servlets. Va finalitzar la versió 1.0 el juny de 1997. A partir de la versió 2.3, l’especificació dels servlets va ser desenvolupada subjecta al Java Community Process.

Començareu creant un nou projecte web amb Maven (vegeu la figura) i l’anomenem servlets (File / New Project / Maven / Web Application).

Figura Procés de creació d’un ‘servlet’

Una vegada s’ha creat, crearem dos servlets (File / New File / Web / Servlet ) en el paquet que indica el mateix NetBeans, en aquest cas, a cat.ioc.m7.servlets. De totes les opcions que podeu configurar només posareu el nom d’Hola, Món com a nom del servlet, les altres opcions tindran el valor per defecte. Creareu un segon servlet i li posareu el nom d’HolaMónXML i activareu l’opció add information to deployment descriptor (web.xml). Per defecte, el codi resultant en crear el servlet Hola, Món és el següent:

  1. import java.io.IOException;
  2. import java.io.PrintWriter;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.annotation.WebServlet;
  5. import javax.servlet.http.HttpServlet;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8.  
  9. /**
  10.  *
  11.  * @author ioc
  12.  */
  13. @WebServlet(urlPatterns = {"/HolaMon"})
  14. public class HolaMon extends HttpServlet {
  15.  
  16. /**
  17.   * Processes requests for both HTTP GET and POST
  18.   * methods.
  19.   *
  20.   * @param request servlet request
  21.   * @param response servlet response
  22.   * @throws ServletException if a servlet-specific error occurs
  23.   * @throws IOException if an I/O error occurs
  24.   */
  25. protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  26. throws ServletException, IOException {
  27. response.setContentType("text/html;charset=UTF-8");
  28. try (PrintWriter out = response.getWriter()) {
  29. /* TODO output your page here. You may use following sample code. */
  30. out.println("<!DOCTYPE html>");
  31. out.println("<html>");
  32. out.println("<head>");
  33. out.println("<title>Servlet HolaMon</title>");
  34. out.println("</head>");
  35. out.println("<body>");
  36. out.println("<h1>Servlet HolaMon at " + request.getContextPath() + "</h1>");
  37. out.println("</body>");
  38. out.println("</html>");
  39. }
  40. }
  41.  
  42. // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
  43. /**
  44.   * Handles the HTTP GET method.
  45.   *
  46.   * @param request servlet request
  47.   * @param response servlet response
  48.   * @throws ServletException if a servlet-specific error occurs
  49.   * @throws IOException if an I/O error occurs
  50.   */
  51. @Override
  52. protected void doGet(HttpServletRequest request, HttpServletResponse response)
  53. throws ServletException, IOException {
  54. processRequest(request, response);
  55. }
  56.  
  57. /**
  58.   * Handles the HTTP POST method.
  59.   *
  60.   * @param request servlet request
  61.   * @param response servlet response
  62.   * @throws ServletException if a servlet-specific error occurs
  63.   * @throws IOException if an I/O error occurs
  64.   */
  65. @Override
  66. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  67. throws ServletException, IOException {
  68. processRequest(request, response);
  69. }
  70.  
  71. /**
  72.   * Returns a short description of the servlet.
  73.   *
  74.   * @return a String containing servlet description
  75.   */
  76. @Override
  77. public String getServletInfo() {
  78. return "Short description";
  79. }// </editor-fold>
  80.  
  81. }

El codi resultant en crear el servlet Hola, Món amb sintaxi XML és el següent:

  1. import java.io.IOException;
  2. import java.io.PrintWriter;
  3. import javax.servlet.ServletException;
  4. import javax.servlet.http.HttpServlet;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7.  
  8. /**
  9.  *
  10.  * @author ioc
  11.  */
  12. public class HolaMonXML extends HttpServlet {
  13.  
  14. /**
  15.   * Processes requests for both HTTP GET and POST
  16.   * methods.
  17.   *
  18.   * @param request servlet request
  19.   * @param response servlet response
  20.   * @throws ServletException if a servlet-specific error occurs
  21.   * @throws IOException if an I/O error occurs
  22.   */
  23. protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  24. throws ServletException, IOException {
  25. response.setContentType("text/html;charset=UTF-8");
  26. try (PrintWriter out = response.getWriter()) {
  27. /* TODO output your page here. You may use following sample code. */
  28. out.println("<!DOCTYPE html>");
  29. out.println("<html>");
  30. out.println("<head>");
  31. out.println("<title>Servlet HolaMonXML</title>");
  32. out.println("</head>");
  33. out.println("<body>");
  34. out.println("<h1>Servlet HolaMonXML at " + request.getContextPath() + "</h1>");
  35. out.println("</body>");
  36. out.println("</html>");
  37. }
  38. }
  39.  
  40. // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
  41. /**
  42.   * Handles the HTTP GET method.
  43.   *
  44.   * @param request servlet request
  45.   * @param response servlet response
  46.   * @throws ServletException if a servlet-specific error occurs
  47.   * @throws IOException if an I/O error occurs
  48.   */
  49. @Override
  50. protected void doGet(HttpServletRequest request, HttpServletResponse response)
  51. throws ServletException, IOException {
  52. processRequest(request, response);
  53. }
  54.  
  55. /**
  56.   * Handles the HTTP POST method.
  57.   *
  58.   * @param request servlet request
  59.   * @param response servlet response
  60.   * @throws ServletException if a servlet-specific error occurs
  61.   * @throws IOException if an I/O error occurs
  62.   */
  63. @Override
  64. protected void doPost(HttpServletRequest request, HttpServletResponse response)
  65. throws ServletException, IOException {
  66. processRequest(request, response);
  67. }
  68.  
  69. /**
  70.   * Returns a short description of the servlet.
  71.   *
  72.   * @return a String containing servlet description
  73.   */
  74. @Override
  75. public String getServletInfo() {
  76. return "Short description";
  77. }// </editor-fold>
  78.  
  79. }

Segons el codi que heu vist, intenteu contestar a la següent pregunta: quin URL s’ha d’escriure al navegador per accedir als servlets?

Si executeu els dos servlets veieu que no hi ha cap diferència, funcionen exactament igual, però si mireu el codi veieu que no és així.

La primera i fonamental diferència és la llibreria import javax.servlet.annotation.WebServlet;, que no hi és en el servlet creat amb XML perquè la seva configuració es farà en el fitxer web.xml. En canvi, al servlet creat per defecte, la seva configuració es posarà com a anotacions dintre del mateix servlet, que es llegiran en el moment de la seva compilació.

Però abans de començar veient les diferències contesteu a la següent pregunta: què és un servlet i com funciona?

Un servlet és un programa del costat del servidor que s’utilitza per generar pàgines web dinàmiques. Genera pàgines web com a resposta d’una petició rebuda des del client (navegador).

El funcionament d’un servlet és el mateix tant amb XML o amb Annotations (anotacions). El servlet implementa el costat servidor de la comunicació client-servidor.

Un servlet pot rebre la petició de dues maneres diferents: amb el mètode GET o amb el mètode POST d’HTTP. Amb el mètode GET, l’habitual, un navegador accedeix a una pàgina web. El mètode POST és el mètode utilitzat en l’enviament de dades al servidor des d’un formulari web.

Un servlet és capaç de rebre una invocació i generar una resposta, com, per exemple, enviar una pàgina web quan algú accedeix al servlet mitjançant la seva URL.

Si utilitzeu el mètode GET s’executarà la funció doGet, i si empreu NetBeans crearà una funció com aquesta:

  1. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. processRequest(request, response);
  3. }

Igualment, si s’utilitza el mètode POST s’executarà la funció doPost:

  1. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. processRequest(request, response);
  3. }

Com veieu, les dues funcions anteriors fan la crida a la mateixa funció protegida, anomenada processRequest. Això és perquè NetBeans suposa que volem el mateix comportament tant si es fa una petició amb el mètode GET com amb el mètode POST.

La funció processRequest ha de generar una resposta segons els paràmetres de la petició. En aquests cas, en ser un programa molt senzill, només volem veure per pantalla una pàgina web amb la benvinguda. La pàgina web que veieu la creeu de manera dinàmica dintre de la funció. Vegeu com s’implementa la resposta:

  1. protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. response.setContentType("text/html;charset=UTF-8");
  3. try (PrintWriter out = response.getWriter()) {
  4. /* TODO output your page here. You may use following sample code. */
  5. out.println("<!DOCTYPE html>");
  6. out.println("<html>");
  7. out.println("<head>");
  8. out.println("<title>Servlet HolaMon</title>");
  9. out.println("</head>");
  10. out.println("<body>");
  11. out.println("<h1>Servlet HolaMon at " + request.getContextPath() + "</h1>");
  12. out.println("</body>");
  13. out.println("</html>");
  14. }
  15. }

Com veieu, esteu escrivint el codi HTML directament a l’objecte resposta (response.getWriter()). Escriviu, utilitzant l’objecte PrinterWriter, la pàgina Hola, Món que volem que vegi l’usuari.

El funcionament intern dels dos servlets és el mateix.

La configuració del servlet amb XML es fa mitjançant el fitxer web.xml, conegut com a descriptor de desplegament (deployment descriptor).

El servidor Apache Tomcat és un exemple de contenidor web, no comercial, que suporta l’execució de servlets definits mitjançant el fitxer web.xml.

El fitxer web.xml es troba dintre de la carpeta WEB-INF i conté la informació corresponent al nom i a l’URL dels servlets. Però bàsicament, aquest fitxer informa el Servlet Container de la classe que ha d’executar (servlet) per a un URL donat.

El fitxer web.xml conté la següent informació:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
  3. <servlet>
  4. <servlet-name>HolaMonXML</servlet-name>
  5. <servlet-class>HolaMonXML</servlet-class>
  6. </servlet>
  7. <servlet-mapping>
  8. <servlet-name>HolaMonXML</servlet-name>
  9. <url-pattern>/HolaMonXML</url-pattern>
  10. </servlet-mapping>
  11. <session-config>
  12. <session-timeout>
  13. 30
  14. </session-timeout>
  15. </session-config>
  16. </web-app>

En el fitxer anterior observeu com es defineix un servlet i quin URL s’ha d’utilitzar per poder emprar-lo des del navegador. L’etiqueta servlet-name conté el nom del servlet i l’etiqueta servlet-class, la classe on està codificat.

Una vegada s’ha definit el servlet s’ha d’informar de l’URL que s’utilitzarà per executar el servlet anterior. Aquesta informació es defineix dintre de l’etiqueta servlet-mapping. En concret, s’ha de dir el nom del servlet i l’URL associat. Les etiquetes XML són servlet-name per al nom del servlet i url-pattern per definir l’URL que s’utilitzarà.

Tota aquesta informació es pot reduir molt utilitzant Annotations (anotacions). Vegeu la informació que es genera en el servlet per defecte per definir tota la informació anterior:

  1. @WebServlet(urlPatterns = {"/HolaMon"})

L’anotació @WebServlet s’utilitza per declarar una classe de tipus servlet. Aquesta classe ha d’heretar de la classe HttpServlet i s’empra per configurar un mapatge URL. Vegeu-ne un altre exemple:

  1. @WebServlet(
  2. name = "ServletExemple",
  3. description = "Un exemple d'anotació Servlet",
  4. urlPatterns = {"/ServletExemple"}
  5. )
  6. public class ServletExemple extends HttpServlet {
  7. // codi...
  8. }
  9.  
  10. //o amb més d'una URL
  11. @WebServlet(
  12. urlPatterns = {"/foo", "/bar", "/cool"}
  13. )
  14. public class ServletExemple extends HttpServlet {
  15. // codi...
  16. }

Vegeu que és molt més ràpid utilitzar les anotacions que el marcatge XML per definir la configuració dels servlets.

Ara ja heu de poder contestar a aquesta pregunta: quin URL s’ha d’escriure per accedir als servlets?

L’URL que s’ha d’escriure seria semblant a: localhost:8080/servlets/HolaMon.

En general:

  1. http://ip-servidor:port/nom-aplicacio/SERVLET-URL-PATTERN

En el vostre cas, el servidor Glassfish us dóna el port 8080 per poder connectar-nos, i la IP és el mateix PC. El nom de l’aplicació correspon al nom del projecte que heu creat amb Netbeans, i el servlet-URL-pattern correspon a l’URL definit, amb l’anotació @WebServlet o bé amb l’etiqueta url-pattern del fitxer web.xml.

Com sabeu, un servidor pot tenir més d’una aplicació funcionant al mateix temps. Dintre de cada aplicació hi pot haver més d’un servlet actiu, atès que totes les aplicacions estan dintre del Servlet Container (vegeu la figura).

Figura Java Servlet Container

Tots els servlets definits en una mateixa aplicació web comparteixen recursos i la informació web de l’aplicació. Per poder accedir a tota aquesta informació s’ha d’accedir al ServletContext, que dóna un conjunt de mètodes als servlets perquè es puguin comunicar.

El ServletContext és un objecte que conté metainformació sobre l’aplicació. Els atributs emmagatzemats estan disponibles a tots els servlets de l’aplicació, fins i tot entre diferents peticions i sessions.

S’hi pot accedir via l’objecte HttpRequest de la següent manera:

  1. ServletContext context = request.getSession().getServletContext();

Aquests atributs es troben emmagatzemats en memòria del Servlet Container, la qual cosa vol dir que estan disponibles a tots els clients de l’aplicació. En canvi, els atributs de sessió només estan disponibles per a l’usuari que ha creat la sessió.

Exemple de creació d’un atribut en el ServletContext:

  1. context.setAttribute("atribut", "valor_del_atribut");

Exemple de lectura d’un atribut en el ServletContext:

  1. Object valorAtribut = context.getAttribute("atribut");

'Servlet' EndevinaColor

En aquest exemple voleu codificar un joc d’endevinació molt senzill. El programa tindrà configurat un color com a paràmetre inicial (constant) i li donareu a l’usuari unes quantes opcions. L’usuari haurà d’endevinar el color configurat.

Primer creareu un servlet nou (File / New File / Web / Servlet) en el mateix projecte Maven de l’exemple anterior i l’anomenareu EndevinaColor. Durant la seva creació seleccioneu que volem afegir la seva configuració en el fitxer web.xml i introduireu un paràmetre inicial nou (vegeu la figura). El paràmetre inicial introduït es diu color, i el seu valor correspondrà al color que l’usuari ha d’endevinar.

Figura Configurar paràmetres d’inicialització amb l’IDE NetBeans

Fixeu-vos en el codi afegit al fitxer de configuració web.xml:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
  3. ...
  4. <servlet>
  5. <servlet-name>EndevinaColor</servlet-name>
  6. <servlet-class>cat.ioc.m7.servlets.EndevinaColor</servlet-class>
  7. <init-param>
  8. <param-name>color</param-name>
  9. <param-value>green</param-value>
  10. </init-param>
  11. </servlet>
  12.  
  13. <servlet-mapping>
  14. <servlet-name>EndevinaColor</servlet-name>
  15. <url-pattern>/EndevinaColor</url-pattern>
  16. </servlet-mapping>
  17. ...
  18. </web-app>

Si us hi fixeu, s’especifica el nou servlet anomenat EndevinaColor i la seva classe Java associada.

A Java EE existeixen dos tipus de descriptors de desplegament (deploymnet descriptors): Java EE deployment descriptors i runtime deployment descriptors. El fitxer web.xml és un exemple de fitxer de desplegament estàndard per a servlets de Java. I el fitxer sun-web-xml és un exemple de fitxer de configuració específic per al servidor Glassfish.

Les etiquetes init-param serveixen per definir els paràmetres inicials o constants als quals podrà accedir el servlet en el moment d’execució.

L’avantatge d’utilitzar paràmetres afegits a la seva configuració és que si es volgués canviar el seu valor no s’hauria de modificar ni compilar el servlet. Un altre paràmetre que s’ha configurat és l’URL EndevinaColor, que correspon al servlet EndevinaColor (servlet-mapping).

Per crear el joc d’endevinació es necessitaran dos fitxers. El primer serà el servlet EndevinaColor.java, i el segon serà la pàgina inicial que es mostrarà a l’usuari amb les opcions que pot escollir. El nom de la pàgina serà EndevinaColor.html i es guardarà a la carpeta per defecte Web Pages. Aquesta pàgina serà un HTML amb un llistat d’enllaços, cadascun dels quals informarà el servlet del color escollit per l’usuari. Com que el paràmetre es passa junt amb l’URL del servlet, el mètode HTTP utilitzat per enviar les dades és GET. Vegeu un exemple de pàgina HTML amb el llistat d’enllaços:

  1. <!DOCTYPE html>
  2. <head>
  3. <title>Start Page</title>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  5. </head>
  6. <body>
  7. <h1>Endevina el color configurat:</h1>
  8. <a href='EndevinaColor?color=white'>blanc</a>
  9. <a href='EndevinaColor?color=red'>vermell</a>
  10. <a href='EndevinaColor?color=blue'>blau</a>
  11. <a href='EndevinaColor?color=yellow'>groc</a>
  12. <a href='EndevinaColor?color=green'>verd</a>
  13. <a href='EndevinaColor?color=black'>negre</a>
  14. </body>
  15. </html>

Com podeu observar, depenent de l’enllaç que esculli l’usuari s’enviarà al servlet un color diferent, que correspon a la proposta que li fa l’usuari. El servlet haurà de comparar aquest color amb el color configurat en el fitxer web.xml. Si són iguals, l’usuari haurà endevinat el color, i si no ho són l’usuari haurà perdut i ho podrà tornar a intentar. A continuació podeu veure aquest comportament traduït a codi Java i utilitzat per implementar el servlet EndevinaColor:

  1. protected void processRequest(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
  2. response.setContentType("text/html;charset=UTF-8");
  3. try (PrintWriter out = response.getWriter()) {
  4.  
  5. out.println("<!DOCTYPE html>");
  6. out.println("<html>");
  7. out.println("<head>");
  8. out.println("<title>Endevina el color</title>");
  9. out.println("</head>");
  10. out.println("<body>");
  11.  
  12. String endevinat = "Llàstima, has perdut!";
  13.  
  14. //L'usuari ha seleccionat un color i ho ha enviat.
  15. String colorUsuari = request.getParameter("color");
  16.  
  17. //S'ha configurat un paràmetre que conté el color a endevinar:
  18. String colorInicial = getServletConfig().getInitParameter("color");
  19.  
  20. if(colorInicial.toLowerCase().equals(colorUsuari.toLowerCase())){
  21. endevinat = "Felicitats! Has endevinat el color.";
  22. }
  23.  
  24. out.println("<h1>" + endevinat +"</h1>");
  25. out.println("<a href='EndevinaColor.html'>Tornar a intentar<a/>");
  26. out.println("</body>");
  27. out.println("</html>");
  28.  
  29. }
  30. }

Si voleu accedir al color que l’usuari ha enviat s’ha de mirar la seva petició (request). Els navegadors web envien molta informació al servidor web que aquest no pot llegir directament perquè forma part de la capçalera de la petició HTTP.

La llibreria (API) dels servlets defineix dues interfícies on s’encapsula la petició de l’usuari en forma d’objecte. Les classes son javax.servlet.ServletRequest i javax.servlet.http.HttpServletRequest.

Quina diferència hi ha entre aquestes dues classes? (vegeu la figura).

Figura La classe HttpServletRequest hereta de la classe ServletRequest

La classe HttpServletRequest hereta la classe ServletRequest per afegir mètodes que es relacionen amb la capçalera del protocol HTTP. Per exemple, els mètodes getSession o getCookies són mètodes de la classe HttpServletRequest però no de la classe ServletRequest. És així perquè la classe HttpServletRequest ha analitzat la petició i ha creat els mètodes necessaris per accedir a la informació d’una manera més intuïtiva.

La funció processRequest dóna accés a la classe HttpServletRequest, mitjançant la qual podrem accedir al paràmetre enviat per l’usuari.

  1. String colorUsuari = request.getParameter("color");

El mètode getParameter(nom_parametre) retorna el valor del paràmetre enviat a la petició de l’usuari. En el cas que no existeixi el paràmetre, el resultat de l’assignació de la variable colorUsuari seria NULL (colorUsuari=NULL).

Ara només queda accedir al paràmetre inicial del servlet per poder saber si l’usuari ha encertat amb el color. Per accedir al color definit en el servlet s’utilitza la següent instrucció:

  1. String colorInicial = getServletConfig().getInitParameter("color");

L’objecte ServletConfig s’utilitza per obtenir informació de configuració del fitxer web.xml. Aquest objecte és creat pel contenidor web per a cada servlet. Si la informació de configuració es defineix a l’arxiu web.xml no cal canviar el servlet. Per tant, és més fàcil d’administrar l’aplicació web si els paràmetres del servlet només es modifiquen de tant en tant.

L’objecte ServletConfig sempre està disponible per al servlet durant la seva execució. Una vegada que el servlet ha completat l’execució, l’objecte ServletConfig serà eliminat pel contenidor.

Llavors, l’avantatge principal d’utilitzar l’objecte ServletConfig és que no cal editar l’arxiu servlet si la informació inicial s’ha introduït a l’arxiu web.xml.

Una vegada tenim la variable inicial ‘color’ ja podem procedir a comparar-la amb el color que ha enviat l’usuari.

  1. String endevinat = "Llàstima, has perdut!";
  2. ...
  3. if(colorInicial.toLowerCase().equals(colorUsuari.toLowerCase())){
  4. endevinat = "Felicitats! Has endevinat el color.";
  5. }
  6.  
  7. out.println("<h1>" + endevinat +"</h1>");

Utilitzant la variable endevinat mostrem la informació a l’usuari. En el cas que sigui el mateix color es mostrarà la frase “Felicitats! Has endevinat el color”; en cas contrari es mostrarà “Llàstima, has perdut!”.

'Servlet' Publicitat als nous

En aquest exemple es vol crear un servlet que identifiqui si és la primera vegada que s’accedeix a la pàgina. En el cas que sigui la primera vegada es mostrarà una pàgina amb un text publicitari; si no és la primera vegada se’n mostrarà una altra.

Començareu creant un nou servlet (File / New File / Web / Servlet) anomenat Publicitat dintre del mateix projecte Maven utilitzat en els exemples anteriors. Durant la creació del servlet hi afegireu un paràmetre addicional anomenat URL que contindrà l’URL del patrocinador de l’aplicació (el valor el podeu inventar). A més a més, hi incorporareu la descripció del servlet al fitxer de configuració web.xml.

Una possible implementació del servlet demanat és el següent:

  1. public class Publicitat extends HttpServlet {
  2. private int visites;
  3.  
  4. private int num;
  5. private String urlPublicitat;
  6. private HashMap ip;
  7.  
  8. @Override
  9. public void init (ServletConfig config) throws ServletException
  10. {
  11. super.init(config);
  12. this.ip = new HashMap();
  13. this.num++;
  14. this.visites = 0;
  15. this.urlPublicitat = getServletConfig().getInitParameter("url");
  16. }
  17.  
  18. @Override
  19. public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
  20. response.setContentType("text/html;charset=UTF-8");
  21. try (PrintWriter out = response.getWriter()) {
  22.  
  23. out.println("<!DOCTYPE html>");
  24. out.println("<html>");
  25. out.println("<head>");
  26. out.println("<title>Servlet Publicitat</title>");
  27. out.println("</head>");
  28. out.println("<body>");
  29.  
  30. String requestIp = request.getRemoteAddr();
  31.  
  32. if(this.ip.containsKey(requestIp)){
  33.  
  34. noEsLaPrimeraVegada(out);
  35. }
  36. else{
  37.  
  38. esLaPrimeraVegada(requestIp, out);
  39. }
  40.  
  41. out.println("<h5>S'han fet " + this.visites +" visites</h5>");
  42. out.println("<h5>S'ha cridat el mètode init " + this.num +" vegades</h5>");
  43. out.println("</body>");
  44. out.println("</html>");
  45.  
  46. this.visites++;
  47. }
  48. }
  49.  
  50. private void noEsLaPrimeraVegada(PrintWriter out){
  51. out.println("<h1>Gràcies per tornar a la pàgina web. Ja no veuràs el patrocinador.</h1>");
  52. }
  53.  
  54. private void esLaPrimeraVegada(String requestIp, PrintWriter out){
  55.  
  56. this.ip.put(requestIp, "");
  57.  
  58. out.println("<h1>És la primera vegada que accedeixes a la pàgina. Benvingut.</h1>");
  59. out.println("<p style='color:red;'>Accedeix al nostre patrocinador clicant al següent enllaç:</p>");
  60. out.println("<a href='" + this.urlPublicitat + "'>Pàgina web del patrocinador</a>");
  61. }
  62.  
  63. }

On es troben implementats els mètodes processRequest, doGet() i doPost()?

En aquest cas no s’ha implementat cap d’aquests mètodes. S’ha anat als orígens i s’ha intentat contestar a la següent pregunta: quins són els mètodes bàsics que ha d’implementar un servlet?

Per poder contestar a la pregunta heu de saber de quines classes hereta un servlet (vegeu la figura).

Figura Herència d’un ‘servlet’
'

La interfície servlet és la interfície més genèrica. Tots els mètodes d’aquesta interfície s’han d’implementar. Els mètodes són: init, service, destroy, getServletInfo i getServletConfig.

La classe abstracta GenericServlet implementa la interfície servlet donant una implementació genèrica a tots el mètodes de la interfície servlet excepte per al mètode service, que està definit com a abstracte. Qualsevol que vulgui implementar un servlet pot heretar d’aquesta classe i només hauria d’implementar el mètode service. També podria donar una implementació alternativa a qualsevol altre mètode, si el sobreescriu.

Finalment, la classe abstracta HttpServlet hereta de la classe GenericServlet implementant els mètodes necessaris per donar una solució adaptada al protocol HTTP. Aquesta classe, tot i ser abstracta, no té cap mètode abstracte. El mètode service ha estat reemplaçat pels mètodes doGet, doPost, etc., amb els mateixos paràmetres que el mètode service (vegeu la figura).

Protocol HTTP

El protocol HTTP està basat en uns mètodes que indiquen l’acció a realitzar sobre un recurs web determinat. Els més coneguts són els mètodes GET i POST, però n’existeixen alguns més com: HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT i PATCH.

Figura Implementació del mètode service en submètodes

El codi corresponent a una implementació senzilla del mètode service de la classe HttpRequest podria ser el següent:

  1. protected void service(HttpServletRequest req, HttpServletResponse resp) {
  2. String method = req.getMethod();
  3.  
  4. if (method.equals(METHOD_GET)) {
  5. doGet(req, resp);
  6. } else if (method.equals(METHOD_HEAD)) {
  7. doHead(req, resp);
  8. } else if (method.equals(METHOD_POST)) {
  9. doPost(req, resp);
  10.  
  11. } else if (method.equals(METHOD_PUT)) {
  12. doPut(req, resp);
  13.  
  14. } else if (method.equals(METHOD_DELETE)) {
  15. doDelete(req, resp);
  16.  
  17. } else if (method.equals(METHOD_OPTIONS)) {
  18. doOptions(req,resp);
  19.  
  20. } else if (method.equals(METHOD_TRACE)) {
  21. doTrace(req,resp);
  22.  
  23. } else {
  24. resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
  25. }
  26. }

Com a regla general no s’ha de sobreescriure mai el mètode service si s’està responent a una petició feta amb el protocol HTTP. La solució més adient és sobreescriure els mètodes doGet o doPost.

Funcionament d'un 'servlet'. Cicle de vida

Contestant a la pregunta “Quins són els mètodes bàsics que ha d’implementar un servlet?”, són tres:

  • init: s’executa quan s’inicia l’aplicació o la primera vegada que s’executa el servlet.
  • service: s’executa en un fil (thread) diferent cada vegada que el servlet rep una petició (request).
  • destroy: s’executa quan s’elimina l’aplicació.

Threads

Els fils d’execució (threads) són les unitats més petites d’execució d’un programa. Un procés està format per diversos threads que poden ser executats simultàniament. La majoria dels llenguatges de programació disposen de llibreries per programar-los. Les aplicacions típiques que els utilitzen son les aplicacions gràfiques i les aplicacions basades en client-servidor.

Aquests tres mètodes constitueixen el cicle de vida d’un servlet (vegeu la figura).

Figura Cicle de vida d’un ‘servlet’

La primera vegada que s’executa el servlet es crea l’objecte que representa aquest servlet i s’executa el mètode init. Aquest objecte, que representa el servlet, no s’elimina fins que no s’elimina tota l’aplicació.

Així, qualsevol petició (request) que rebi de qualsevol client la manipularà el mateix objecte, i qualsevol variable que tingui el servlet es compartirà entre peticions de diferents clients. Cada petició que rebi el servlet s’executarà en un fil (thread) diferent, i poden executar-se diferents peticions de manera concurrent (a la mateixa vegada). El mètode service és el mètode encarregat d’executar les peticions dels clients.

Finalment, quan l’aplicació és eliminada s’eliminen tots els servlets associats. En aquest moment s’executa el mètode destroy per alliberar els recursos que tingui, com per exemple tancar una connexió a basede dades.

El mètode init del 'servlet' Publicitat

El mètode init té la peculiaritat que només s’executarà una vegada, en crear l’objecte del servlet. Podeu pensar en aquest mètode com si fos el seu constructor, però no ho és. El codi que hi haurà dintre d’aquest mètode serà el d’inicialització de les variables privades del servlet.

Fixem-nos en el mètode init del servlet Publicitat:

  1. @Override
  2. public void init (ServletConfig config) throws ServletException
  3. {
  4. super.init(config);
  5. this.ip = new HashMap();
  6. this.num++;
  7. this.visites = 0;
  8. this.urlPublicitat = getServletConfig().getInitParameter("url");
  9. }

S’han inicialitzat totes les variables privades. S’utilitza un objecte del tipus HashMap per emmagatzemar les diferents IP del clients que es connectin al servlet.

S’han creat dues variables, una per comptar el nombre de vegades que s’executa la funció init (variable num ) i una altra per comptar el nombre de vegades que s’executa el mètode service (variable visites).

A més a més, s’ha recuperat el paràmetre inicial url que es troba en el fitxer web.xml. Aquest paràmetre correspon a l’URL del patrocinador de la pàgina web.

El mètode init s’utilitza principalment per a la inicialització del servlet, és a dir, per crear o recuperar objectes que s’utilitzaran durant l’execució del mètode service.

I la instrucció super.init(config);? És imprescindible, atès que associa l’objecte ServletConfig al servlet Publicitat. Sense aquest objecte no podríem accedir als paràmetres inicials emmagatzemats en el fitxer web.xml.

El mètode service del 'servlet' Publicitat

El mètode service s’executarà cada cop que un client accedeixi al servlet. Així, cada vegada que un navegador accedeixi a l’URL del servlet Publicitat s’executarà el següent codi:

  1. public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
  2. response.setContentType("text/html;charset=UTF-8");
  3. try (PrintWriter out = response.getWriter()) {
  4.  
  5. out.println("<!DOCTYPE html>");
  6. out.println("<html>");
  7. out.println("<head>");
  8. out.println("<title>Servlet Publicitat</title>");
  9. out.println("</head>");
  10. out.println("<body>");
  11.  
  12. String requestIp = request.getRemoteAddr();
  13.  
  14. if(this.ip.containsKey(requestIp)){
  15.  
  16. noEsLaPrimeraVegada(out);
  17. }
  18. else{
  19.  
  20. esLaPrimeraVegada(requestIp, out);
  21. }
  22.  
  23. out.println("<h5>S'han fet" + this.visites +" visites</h5>");
  24. out.println("<h5>S'ha cridat el mètode init " + this.num +" vegades</h5>");
  25. out.println("</body>");
  26. out.println("</html>");
  27.  
  28. this.visites++;
  29. }
  30. }

El mètode service té accés, igual que els mètodes doGet i doPost, a l’objecte request de la petició i a l’objecte response per donar una resposta.

El codi que us permet decidir si és la primera vegada que l’usuari accedeix a la pàgina o no ho és és el següent:

  1. String requestIp = request.getRemoteAddr();
  2.  
  3. if(this.ip.containsKey(requestIp)){
  4.  
  5. noEsLaPrimeraVegada(out);
  6. }
  7. else{
  8.  
  9. esLaPrimeraVegada(requestIp, out);
  10. }

Amb l’objecte request obteniu l’adreça IP del client que ha fet la petició. Una vegada teniu l’adreça IP, comproveu si hi ha accedit amb anterioritat:

  1. if(this.ip.containsKey(requestIp)){

La variable ip és de tipus HashMap. Els objectes de tipus HashMap permeten emmagatzemar objectes del tipus clau:valor. L’avantatge d’aquests objectes és que recuperar un element, si se sap la clau, és molt ràpid. A més a més, us proporciona el mètode containsKey(clau), que us retornarà true si la IP està guardada en el HashMap i false en cas contrari. Aquesta variable mai oblidarà aquest valor si no l’elimineu vosaltres, ja que l’objecte servlet perdurarà entre diferents peticions.

Objecte Java HashMap

És una de les col·leccions de Java més populars entre els programadors. Existeixen altres col·leccions basades en Hash com són HashTable i ConcurrentHashMap però el seu rendiment és més baix en entorns amb un sol fil de processament. La col·lecció HashMap permet als programadors fer molts tipus d’operacions com afegir un element, treure’l, recórrer tots els elements, calcular la seva mida, obtenir totes les claus del hash o tots els seus valors, entre d’altres operacions interessants.

Si no es troba emmagatzemada vol dir que és la primera vegada que un usuari amb aquesta IP ha accedit a la pàgina. Llavors s’executarà la funció esLaPrimeraVegada(), que té el següent codi:

  1. private void esLaPrimeraVegada(String requestIp, PrintWriter out){
  2.  
  3. this.ip.put(requestIp, "");
  4.  
  5. out.println("<h1>És la primera vegada que accedeixes a la pàgina. Benvingut.</h1>");
  6. out.println("<p style='color:red;'>Accedeix al nostre patrocinador clicant al següent enllaç:</p>");
  7. out.println("<a href='" + this.urlPublicitat + "'>Pàgina web del patrocinador</a>");
  8. }

Primer de tot s’emmagatzema la IP dintre del HashMap: this.ip.put(requestIP, ””), i a continuació s’escriu el pedaç de pàgina corresponent als usuaris que hi accedeixen per primera vegada, com per exemple l’URL del patrocinador de la web.

Si, en canvi, no és la primera vegada que s’hi accedeix, llavors el codi anterior no s’executa. La funció que s’executaria seria noEsLaPrimeraVegada();, que té el següent codi:

  1. private void noEsLaPrimeraVegada(PrintWriter out){
  2. out.println("<h1>Gràcies per tornar a la pàgina web. Ja no veuràs el Patrocinador.</h1>");
  3. }

La funció anterior només informa l’usuari que ja hi ha accedit prèviament i li dóna les gràcies per tornar a entrar-hi.

D’altra banda, si executeu el servlet Publicitat veureu que la variable num no canvia, ja que és un comptador que només s’actualitza dintre de la funció init i, en canvi, la variable visites s’actualitza cada vegada que hi ha una petició. Aquest és un símptoma que l’objecte servlet Publicitat és únic i s’executa el mètode service en cada petició.

El mètode destroy del 'servlet' Publicitat

Durant la implementació d’aquest servlet no ha estat necessària la utilització de recursos que calgui eliminar utilitzant aquest mètode.

El mètode destroy es crida només una vegada al final del cicle de vida d’un servlet. Aquest mètode li dóna al seu servlet l’oportunitat de tancar les connexions de base de dades, aturar subprocessos de fons o escriure llistes de cookies, així com fer altres activitats de neteja.

La definició del mètode destroy és la següent:

  1. public void destroy ( ) {
  2. // La finalització de codi ...
  3. }

Exemple de Publicitat amb redireccions

Es vol modificar el servlet Publicitat afegint-hi redireccions. En comptes d’escriure directament en la resposta de la petició es vol enviar l’usuari a una pàgina HTML.

En concret, si és la primera vegada que l’usuari accedeix al servlet es mostrarà la pàgina anomenada 1a_vegada.html. La podeu crear accedint a File / New File / HTML5 / HTML File, i hi podeu afegir el següent codi:

  1. <!DOCTYPE html>
  2. <head>
  3. <title>Servlet Publicitat</title>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  5. </head>
  6. <body>
  7. <h1>És la primera vegada que accedeixes a la pàgina. Benvingut.</h1>
  8. <p style='color:red;'>Accedeix al nostre patrocinador clicant al següent enllaç:</p>
  9. <a href='http://ioc.xtec.cat'>Pàgina web del patrocinador</a><br>
  10. <a href='Publicitat2'>Tornar a accedir</a>
  11. </body>
  12. </html>

En canvi, si no és la primera vegada l’usuari veurà el fitxer HTML anomenat resta_vegades.html. El podeu crear accedint a File / New File / HTML5 / HTML File i podeu afegir-hi el següent codi:

  1. <!DOCTYPE html>
  2. <head>
  3. <title>Servlet Publicitat</title>
  4. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  5. </head>
  6. <body>
  7. <h1>Gràcies per tornar a la pàgina web. Ja no veuràs el patrocinador.</h1>
  8. <a href='Publicitat2'>Tornar a accedir</a>
  9.  
  10. </body>
  11. </html>

Per fer aquest exercici creareu un servlet nou (File / New File / Web / Servlet) en el mateix projecte Maven de l’exemple anterior i l’anomenareu Publicitat2. Aquest cop, durant la creació del servlet no afegireu un paràmetre addicional, però sí hi la descripció del servlet al fitxer de configuració web.xml.

Fixeu-vos que en el codi afegit al fitxer de configuració web.xml ja no apareix cap paràmetre inicial:

  1. ...
  2. <servlet>
  3. <servlet-name>Publicitat2</servlet-name>
  4. <servlet-class>cat.ioc.m7.servlets.Publicitat2</servlet-class>
  5. </servlet>
  6. <servlet-mapping>
  7. <servlet-name>Publicitat2</servlet-name>
  8. <url-pattern>/Publicitat2</url-pattern>
  9. </servlet-mapping>
  10. ...

Una possible implementació del servlet demanat és la següent:

  1. public class Publicitat2 extends HttpServlet {
  2.  
  3. private HashMap ip;
  4.  
  5. @Override
  6. public void init (ServletConfig config) throws ServletException
  7. {
  8. super.init(config);
  9. this.ip = new HashMap();
  10. }
  11.  
  12. @Override
  13. public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
  14.  
  15. String requestIp = request.getRemoteAddr();
  16.  
  17. if(this.ip.containsKey(requestIp)){
  18.  
  19. //redirect html - resta de vegades
  20. response.sendRedirect("resta_vegades.html");
  21. }
  22. else{
  23.  
  24. //redirectHTML - 1a vegada
  25. response.sendRedirect("1a_vegada.html");
  26. this.ip.put(requestIp, "");
  27. }
  28. }
  29.  
  30. }

Igual que abans, s’utilitza el mètode init per inicialitzar les variables privades. En aquest exemple s’han tret les variables de comptadors de visites i l’URL del patrocinador. En aquest cas, el patrocinador s’ha afegit directament en la pàgina redirigida.

El mètode service s’ha reduït considerablement. Ara ja no cal que s’escrigui la pàgina resultant, sinó que pot enviar l’usuari a una web o a una altra depenent de si és la primera vegada o no. La propietat ip permet fer aquesta distinció.

Per redirigir a una altra pàgina s’utilitza el mètode sendRedirect de l’objecte response. Aquesta funció redirigeix a un altre recurs, la qual cosa obliga el client (navegador) a fer automàticament una petició al nou recurs. Les redireccions poden ser internes (del mateix servidor) o externes (a un altre servidor). El client veurà a l’URL del navegador el nou recurs on s’ha accedit.

sendRedirect versus forward

S’utilitza el mètode sendRedirect quan es vol dirigir l’usuari a una altra pàgina d’un altre domini o servidor. En canvi, quan la redirecció es fa cap a una pàgina del mateix servidor o domini s’utilitza el mètode forward.

Un altre mètode per redireccionar és utilitzant el request dispatching. En aquest cas, en comptes d’utilitzar l’objecte response s’empra l’objecte request per reenviar la mateixa petició a un altre recurs. Així, el client no ha de demanar el nou recurs. De fet, no sabrà la pàgina li ha donat un altre recurs, ja que l’URL del navegador no canviarà.

Un exemple d’utilització del request dispatching és el següent:

  1. RequestDispatcher rs = request.getRequestDispatcher("nouRecurs.html");
  2. rs.forward(request,response);

Proveu de canviar l’exemple anterior utilitzant el mètode getRequestDispatcher de l’objecte request.

Exemple EndevinaColor

Intenteu canviar el servlet EndevinaColor creat anteriorment. Creeu un altre servlet anomenat EndevinaColor2 i afegiu-hi les següents modificacions:

  • Si és la primera vegada, ha de mostrar el llistat d’enllaços amb els colors (el que abans era la pàgina EndevinaColor.html).
  • Si és la segona vegada, ha de mostrar el resultat de la comparació, és a dir, informar si s’ha endevinat el color.

En aquest cas, en comptes de sobreescriure el mètode service pots utilitzar el mètode processRequest que crea l’IDE NetBeans.

  1. private String colorInicial;
  2.  
  3. @Override
  4. public void init (ServletConfig config) throws ServletException
  5. {
  6.  
  7. super.init(config);
  8.  
  9. //Color configurat com a paràmetre inicial. És el color a endevinar.
  10. this.colorInicial = getServletConfig().getInitParameter("color");
  11. }
  12.  
  13.  
  14. protected void processRequest(HttpServletRequest request, HttpServletResponse response)
  15. throws ServletException, IOException {
  16.  
  17. response.setContentType("text/html;charset=UTF-8");
  18. try (PrintWriter out = response.getWriter()) {
  19.  
  20. out.println("<!DOCTYPE html>");
  21. out.println("<html>");
  22. out.println("<head>");
  23. out.println("<title>Endevina el color</title>");
  24. out.println("</head>");
  25. out.println("<body>");
  26.  
  27. String colorUsuari = request.getParameter("color");
  28.  
  29. if(colorUsuari != null && !colorUsuari.equals("") ){
  30.  
  31. crearPaginaGuanyador(colorUsuari, out);
  32.  
  33. }
  34. else{
  35.  
  36. crearPaginaInicial(out);
  37. }
  38.  
  39. out.println("</body>");
  40. out.println("</html>");
  41.  
  42. }
  43. }
  44. ...

Fixeu-vos que s’ha sobreescrit el mètode init per obtenir la configuració inicial del servlet. En concret, s’ha recuperat el color configurat en el fitxer web.xml. Recordeu que és aquest el color que s’ha d’endevinar.

El mètode processRequest és el mètode que porta a terme la mateixa funció que service. Així, aquí farem tot el codi que s’ha d’executar a cada petició d’un client.

Bàsicament, es crea una resposta depenent de si és la primera vegada que s’hi accedeix o no. Per determinar si és o no la primera vegada s’accedeix al request de la petició cercant el color que proposa l’usuari. Si no proposa cap color s’executa la funció crearPaginaInicial(); si, en canvi, l’usuari proposa un color, es mira si ha guanyat o no amb la crida de la funció crearPaginaGuanyador().

El codi de les funcions és el següent:

  1. private void crearPaginaGuanyador(String colorUsuari, PrintWriter out){
  2.  
  3. String endevinat = "Llàstima, has perdut!";
  4.  
  5. if(this.colorInicial.toLowerCase().equals(colorUsuari.toLowerCase())){
  6. endevinat = "Felicitats! Has endevinat el color.";
  7. }
  8.  
  9. out.println("<h1>" + endevinat +"</h1>");
  10. out.println("<a href='EndevinaColor2'>Tornar<a/>");
  11.  
  12. }
  13.  
  14. private void crearPaginaInicial(PrintWriter out) {
  15. out.println("<h1>Endevina el color configurat:</h1>");
  16. out.println("<a href='EndevinaColor2?color=white'>blanc</a>");
  17. out.println("<a href='EndevinaColor2?color=red'>vermell</a>");
  18. out.println("<a href='EndevinaColor2?color=blue'>blau</a>");
  19. out.println("<a href='EndevinaColor2?color=yellow'>groc</a>");
  20. out.println("<a href='EndevinaColor2?color=green'>verd</a>");
  21. out.println("<a href='EndevinaColor2?color=black'>negre</a>");
  22. }

Ara ja no us cal cap pàgina HTML addicional. El mateix servlet crea la pàgina que necessita veure l’usuari depenent de la petició que ha fet.

Què s'ha après

En aquest apartat l’alumne ha après:

  • La creació i configuració d’un servlet.
  • El cicle de vida d’un servlet (ini, service, destroy).
  • Classes associades als servlets, com poden ser ServletContext, HttpServletRequest i HttpServletResponse.
  • La redirecció d’una petició utilitzant la mateixa petició original o creant-ne una de nova.

Per aprofundir en aquests llenguatges es recomana la realització de les activitats associades a aquest apartat. Una vegada realitzades, l’alumne estarà preparat per continuar amb l’aprenentatge dels servlets.

Anar a la pàgina anterior:
Referències
Anar a la pàgina següent:
Activitats