Temario detallado‎ > ‎5: Web Server Side‎ > ‎Java‎ > ‎

Desarrollo Web: MVC. Patrones de diseño web.

Queremos sumar dos números.

Necesitamos separar cosas que hace el servidor y cosas que hace el cliente. ¿Cómo funciona nuestra solución según recién aprendimos a trabajar?
  1. El cliente solicita la página principal (index.jsp) de la app Calculadora
  2. El servidor procesa la pantalla inicial, que permite ingresar dos números
  3. El cliente ingresa dos números y "submite el formulario" (presiona el botón Sumar)
  4. Esto dispara un pedido a otra página, que debe tomar los valores ingresados por el cliente (vía get o post), efectuar la suma y mostrar ese resultado por pantalla

Intro a Servlets

El formulario de index.jsp dice:
<form method="post" action="calcular">

Mmm... ¿calcular?

¿Qué puedo poner como parámetro action de un form?
  • links externos ("http://....")
  • links internos dentro del mismo proyecto
    • páginas html
    • otros recursos (archivos pdf, xls, con un formato que el browser pueda mostrar)
    • o puedo redireccionar el destino a una clase Java que se ejecute en el servidor y devuelva html al cliente. Tenemos dos posibilidades: 
      • páginas JSP
      • servlets
El method hace post/get a un action "calcular". ¿Dónde va a parar ese pedido entonces?
Abrimos el archivo web.xml de src/main/webapp/WEB-INF y vemos que es un xml que mapea nombres con .... servlets

    <servlet>
        <servlet-name>calcular</servlet-name>
        <servlet-class>ar.edu.unq.tpi.labso.calculadora.control.CalcularServlet</servlet-class>
    </servlet>

Esto permite relacionar un nombre ("calcular") con un package name + nombre de la clase del servlet.  
Podemos jugar un poco con el web.xml para cambiar el url-pattern y el package y vemos qué errores tira.

Ok, pero ¿qué es un servlet?

Es una clase Java que extiende HttpServlet. CalcularServlet en este ejemplo implementa un único método doPost. El servlet está pensado para recibir un pedido (http request), procesarlo y devolver código html que pueda ser parseado por el browser. Entonces ¿qué métodos son importantes para un servlet?
  • doPost(), cuando recibimos un http request vía post
  • doGet(), ídem pero vía get.
Vemos el ejemplo en acción. Sumo dos números y me devuelve el resultado. Cambiamos el method="post" a method="get" y volvemos a probar:
  1. En la URL se visualizan los dos parámetros que le mando y sus valores, ahora ya se que en el primer textbox le asocio el parámetro arg1. Esto se ve en 
   <input type="text" name="arg1" value="${param.arg1}"/>

       del index.jsp. Especialmente en el atributo name. El value lo vamos a dejar por ahora.
    
    2. Pero además de ver los parámetros en la URL, al procesar la página recibe un mensaje de error:  "El método HTTP especificado no está permitido para el recurso requerido (El Metodo HTTP GET no es soportado por esta URL)".   

Claro, en el ejemplo CalcularServlet no existe el método doGet, lo podríamos implementar para que no tire error. Lo hacemos:

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    this.doPost(request, response);
}

Esta es una técnica habitual para no tener que pensar si el llamante nos va a pedir algo vía GET o POST. Lo probamos y vemos ahora que funciona y que además nos muestra los parámetros en la URL:

http://localhost:8080/calculadora-jsp/calcular?arg1=6&arg2=8

Ojo, esto fue sólo un ejemplo didáctico, no necesariamente constituye una buena práctica en sí. Por lo general vamos a preferir usar method=POST para todo y no sufrir limitaciones en el envío de parámetros en el formulario (ya bastante tenemos con que tengamos que pasar todo a string).

Si quieren chusmear más pueden ver:

Estado de un servlet

Si un servlet es una clase Java, ¿puede tener estado?
  • técnicamente, los servlets son singletons, entonces por más que haya 50 usuarios conectados al web server, sólo tenemos una instancia de cada servlet
  • las variables (o atributos) que le definamos a un servlet son compartidas entre todas las sesiones, el acceso a esas variables es concurrente
  • entonces el manejo del estado de una página no es trivial: 
    • el servidor no retiene información por sesión de usuario 
    • y encima al viajar entre página y página paso por el cliente que está limitado por lo que el HTML me deja hacer
Cambiamos el doGet de nuestro ejemplo original para entender mejor el párrafo anterior:

public class CalcularServlet extends HttpServlet {

private double variableLoca = 0;
     
    ...

@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
double arg1 = Double.parseDouble(request.getParameter("arg1"));
// variableLoca es una variable de acceso compartido por todos los usuarios que hacen peticiones
this.variableLoca++;
Calculadora calculadora = new Calculadora(arg1, this.variableLoca);
double resultado = calculadora.sumar();
 
request.setAttribute("resultado", resultado);
request.getRequestDispatcher("index.jsp").forward(request, response);
}

    ...

Ingresamos a dos browsers en simultáneo:
  • al escribir en el primer texto el número "7" y submitir el calcular, vemos el número 8
  • Volvemos a presionar el botón calcular, y nos aparece el número 9
  • Una vez más, llegamos al número 10
  • Desde el otro browser, escribimos el número "7". Al presionar el botón calcular, vemos que nos aparece el número 11. Esto es porque variableLoca se va incrementando no importa desde qué sesión nos conectamos.

MMVC en Web

La forma en que está estructurada la aplicación de la calculadora no es casual:
  • Las JSP representan la parte visual de la aplicación, definen el orden de los widgets en el formulario pero no deberían conocer cuestiones del negocio ni cómo resolver los pedidos. Sólo manejan la presentación de la información al usuario (su input está dado en base al trabajo que hacen los servlets).
  • Los servlets actúan de controllers, reciben el input de formularios de la vista (JSPs) y los adaptan a un formato más amigable (esto puede incluir generar un objeto con toda la info que se cargó por separado en el JSP). También dialogan con los modelos de vista (application model o dominio), con los homes, etc. Su misión es redirigir a una vista (JSP) para mostrar el resultado del pedido originado por el usuario y -sobre todo- darle la información que la vista necesita.
  • Y tenemos un objeto de dominio Calculadora que sabe sumar dos números. 

Paso a paso: cómo funciona el ejemplo que suma números


  1. index.jsp es el formulario que permite capturar los sumandos arg1 y arg2 (que son dos input type text). Cuando el usuario presiona el botón submit se envían los valores arg1 y arg2 al servlet correspondiente (CalcularServlet, según definido en el archivo web.xml)
  2. El servlet ejecuta el doPost según el action definido por la jsp. Recibe un parámetro request de tipo HttpServletRequest, al cual le puedo preguntar por los valores de arg1 y arg2 de la siguiente manera:
    double arg1 = Double.parseDouble(request.getParameter("arg1"));
    double arg2 = Double.parseDouble(request.getParameter("arg2"));

    Esto pincha si yo le paso un string vacío o letras:
    java.lang.NumberFormatException: empty String
        sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:994)
        java.lang.Double.parseDouble(Double.java:510)
        ar.edu.unq.tpi.labso.calculadora.control.CalcularServlet.doPost(CalcularServlet.java:18)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:641)
        javax.servlet.http.HttpServlet.service(HttpServlet.java:722)

    pero dejamos pendiente esta validación por el momento.
 
    3. El servlet no hace cosas del negocio, sino que delega a un objeto de dominio llamado Calculadora:
   
    Calculadora calculadora = new Calculadora();
    double resultado = calculadora.sumar(arg1, arg2);

Este ejemplo es muy simple y no parece haber grandes diferencias entre

    double resultado = arg1 + arg2;

y lo que finalmente hicimos, pero queremos reflejar que el servlet sólo actúa como controller del modelo y la vista, no lo reemplaza.

Cosas que vamos a querer poner en el servlet:
  • adaptar strings a objetos más adecuados para el negocio
  • delegar a objetos home, objetos de negocio u objetos que modelen un caso de uso
  • manejo de la navegación (forward a páginas JSP o a otros servlets)
Cosas que no vamos a querer poner en un servlet:
  • manejo de la transacción
  • llamar a una consulta SQL o trabajar con queries embebidos (hacer el trabajo que le corresponde a un home)
  • definir validaciones y métodos de negocio (hacer el trabajo que le corresponde a un objeto de dominio)
  • encargarse de cuestiones propias de la vista (ej: dibujar una grilla en base a una lista, armar el código HTML que va a visualizar el cliente, etc.)

El resultado de la suma lo tenemos que poner en algún lugar donde lo podamos "recordar".

Hay varios scopes donde podemos guardar estado:
  • a nivel página (es una variable local cuyo ciclo de vida está asociado a la construcción de una página)
  • a nivel request (se pasa en el objeto request de una página a otra)
  • a nivel session (sesión abierta por el usuario)
  • a nivel aplicación (global para todos los usuarios)
Si el resultado lo dejamos a nivel página (double resultado), esto no permite que viaje hacia el index.jsp que es lo que queremos. Entonces lo vamos a poner en el request:
   
    request.setAttribute("resultado", resultado);

El request vemos que es de input-output: lo recibimos para poder capturar los valores devueltos (a través del mensaje getParameter como hemos visto), pero también es un contenedor al cual le podemos setear valores mapeados con una key:
   
    setAttribute(String name, Object o)

En el doPost se suele terminar redirigiendo el flujo: en este caso de vuelta al index.jsp para mostrar el resultado, junto con el request modificado:
   
    request.getRequestDispatcher("index.jsp").forward(request, response);


    4. Todo esto ejecuta... del lado del servidor, incluso el forward a index.jsp, que ahora muestra el resultado gracias a que resultado ya no es nulo:
   
    <% if (request.getAttribute("resultado") != null) { // abro%>
        <p>El resultado es ${resultado}</p>
    <% } // cierro %>


    Vemos los tags <% y %> que permiten entremezclar código HTML con código que va a ejecutar en el servidor.
    Abrimos la página en el browser y vemos que en el source no están esos tags <% ni %>, porque la jsp una vez ejecutada sólo tiene código para el cliente.
  

Resumen de Model 2

Según el model 2 el pedido se hace a un servlet que sirve de controller para luego forwardear el pedido a la JSP que vuelve a mostrar la información al usuario.

Model 2: el Servlet actúa de controller - la página JSP representa la vista