Ir al contenido principal

Lección 9 · Interfaz web con Java

Las tecnologías del lado del servidor

Internet es uno de los medios fundamentales donde puede vivir una aplicación actual. Y ahí aparece un problema que conviene entender antes de ver ninguna solución: en las aplicaciones del lado del cliente, el cliente tiene que tener el software. Si no lo tiene, la aplicación no carga.

Eso pasa con todo lo que corre en el navegador:

  • Una página HTML necesita un navegador compatible con los códigos HTML.
  • Si se usa JavaScript, el navegador tiene que poder interpretarlo.
  • Si se usa Flash, tiene que tener el plugin instalado; con ActiveX, lo mismo.

Y el usuario puede desactivar todas esas tecnologías, o directamente no tenerlas. La única solución posible sería pedirle que las instale, y eso no funciona: hay una desconfianza razonable a instalar software que pide una página web. Encima, los usuarios van siempre detrás de las innovaciones, así que sus versiones de software están lejos de ser las últimas.

Para evitar todos esos problemas se idearon las técnicas de aplicaciones web del lado del servidor, donde la interpretación se hace en el propio servidor y no en el cliente. La ventaja es enorme: del lado del cliente solo hace falta un navegador.

Comparación entre una aplicación que corre del lado del cliente, que depende de lo que el navegador tenga instalado, y una del lado del servidor, donde solo hace falta un navegador. Del lado del cliente o del lado del servidor Del lado del cliente El navegador tiene que saber interpretar todo: · el HTML y el JavaScript · el plugin de Flash o de ActiveX · y la versión que el usuario tenga instalada Si algo falta o está desactivado, la aplicación no funciona. Y no se puede obligar al usuario a instalar nada. Del lado del servidor La interpretación se hace en el servidor, donde quien programa controla qué hay instalado. El cliente solo necesita un navegador, y de la versión que sea: al cliente le llega HTML ya armado. Servlets y JSP son las tecnologías de Java para esto. Esa es la razón por la que una aplicación web se usa desde cualquier dispositivo sin instalar nada.
La diferencia que impulsó todas las tecnologías de servidor: dónde se ejecuta el código.

CGI

CGI (Common Gateway Interface, interfaz de pasarela común) es la tecnología de servidor más veterana, y apareció por las limitaciones del HTML para crear verdaderas aplicaciones de red.

CGI define una serie de características que permiten comunicar una página con una aplicación que reside en el servidor. La aplicación puede estar escrita en casi cualquier lenguaje —el más usado fue Perl—, lo único que tiene que conseguir es que su entrada y su salida estén pensadas para comunicarse con la web, de modo que el usuario no necesite ningún software adicional. Los datos de salida se preparan en formato HTML. Y el servidor donde vive la aplicación CGI tiene que tener implementado un compilador compatible con el lenguaje en que está escrita.

ASP y ASP.NET

ASP parte de simplificar la idea de la tecnología de servidor: son páginas HTML que tienen etiquetas especiales —marcadas con los símbolos <% y %>— que señalan instrucciones que debe ejecutar el servidor, escritas sobre todo en VBScript. El servidor interpreta esas instrucciones y obtiene una página HTML, que es la que llega al cliente. Fue una tecnología muy exitosa, por la enorme cantidad de programadores de Visual Basic que había.

Su problema es que está pensada únicamente para servidores IIS, los servidores web de Microsoft. .NET es la implementación siguiente de la misma empresa: incorpora varios lenguajes bajo una interfaz común —sobre todo C#—, con directivas de servidor y conexión a bases de datos con ADO.

PHP

Es una tecnología similar a ASP: una página HTML con etiquetas especiales, en este caso <?, que encierran comandos para el servidor escritos en un lenguaje de script con muchas similitudes con Perl. La diferencia con ASP es que es una plataforma de código abierto, compatible con Apache y con soporte para muchos de los gestores de bases de datos más populares.

Servlets y JSP

Son las tecnologías que plantea Java para crear aplicaciones que residen del lado del servidor. JSP es similar a ASP y PHP: páginas con etiquetas. Y los servlets forman parte de lo que se conocía como Java EE y hoy se conoce como Jakarta EE. A una aplicación hecha con JSP o servlets se la llama aplicación web.

Java EE / Jakarta EE

Es una plataforma para construir aplicaciones completas: un conjunto de especificaciones estándares, escritas, revisadas y aprobadas por una comunidad de expertos, que en su conjunto definen qué servicios debería proveer una plataforma para aplicaciones empresariales y cómo se debería programar en ella. En la práctica son una serie de tecnologías que permiten escribir aplicaciones del lado del servidor para dar servicios por redes TCP/IP.

Y mantiene el paradigma de Java que se vio en la lección 2: la portabilidad, incluso al cambiar el sistema operativo del servidor. Sus APIs estaban en el paquete javax y, desde que el proyecto pasó a la Fundación Eclipse, el paquete se llama jakarta. Ese cambio de nombre es el que explica por qué en la bibliografía vieja aparece javax.servlet y en la nueva jakarta.servlet: es la misma cosa con otro nombre.

Las tecnologías fundamentales son cuatro: Servlets, JSP, JAXP (procesamiento de documentos XML) y EJB (Enterprise Java Beans).

Para ejecutar una aplicación Jakarta EE hacen falta servidores web compatibles que tengan un servidor de aplicaciones, o contenedor web. Hay de dos tipos:

TipoEjemplos
Comerciales Oracle WebLogic, IBM WebSphere Application Server, Payara Jakarta Server.
Gratuitos y de código abierto (implementaciones parciales) Apache Tomcat, Eclipse Jetty, WildFly, GlassFish, Red Hat JBoss.

Para lo que sigue alcanza con Apache Tomcat: es gratuito, de código abierto, es el contenedor de servlets más usado del mundo y es el que trae el propio NetBeans. Un contenedor web es, en esencia, la parte del servidor que interactúa con los servlets: se ocupa de administrar su ciclo de vida, de mapear una URL con un servlet concreto y de garantizar que quien pide esa URL tenga los permisos correctos.

HTTP

El protocolo de transferencia de hipertexto (HTTP) es el encargado de transmitir las páginas web por las redes TCP/IP, y toda la programación de aplicaciones web se basa en su uso. El ciclo de petición y respuesta es este:

  1. Un cliente establece conexión por un puerto —normalmente el 80— con un servidor web, y en formato de texto le envía una petición.
  2. El servidor analiza la petición y localiza el recurso solicitado.
  3. El servidor envía una copia del recurso al cliente.
  4. El servidor cierra la conexión.
El ciclo de HTTP: el cliente pide, el servidor responde y cierra la conexión. Como el servidor no recuerda nada entre peticiones, hace falta un número de sesión para identificarlo. HTTP: pedir, responder y olvidar Cliente el navegador Servidor puerto 80 1. petición en texto: método, ruta, versión 2. y 3. el servidor busca el recurso y lo devuelve 4. el servidor cierra la conexión y no recuerda nada de la petición anterior El problema: HTTP es un protocolo sin estado. Cada petición es independiente. La solución: el servidor da un número de sesión que el cliente guarda y reenvía. Así el servidor puede reconocer que varias peticiones vienen del mismo cliente y están relacionadas.
El ciclo de HTTP y el problema de fondo que obliga a inventar las sesiones.

Los métodos de una petición

MétodoQué hace
GETObtiene un recurso simple a partir de su URL en el servidor. Es el que usa el navegador cuando se escribe una dirección.
HEADIdéntico al anterior, pero obtiene solo las cabeceras del recurso, sin el contenido.
POSTEl servidor acepta datos que envía el cliente. Es el que usan los formularios.
PUTPermite modificar los datos de un recurso en el servidor.
DELETEPermite eliminar un recurso del servidor.
OPTIONSPide información sobre los métodos que soporta el servidor.
TRACEPide que le devuelvan la propia petición y las cabeceras que envió, para diagnosticar.

Los datos de la petición y las cabeceras

Además del método hay que indicar la línea de lo que se pide: la ruta URL del documento, pero sin las palabras http:// ni el nombre del servidor. O sea que la dirección http://www.ejemplo.com.ar/index.html se pasa como /index.html. Y por último se indica la versión de HTTP: puede ser HTTP/1.0, HTTP/1.1 o HTTP/2.

Después van las cabeceras de petición: líneas con una clave y su valor separados por dos puntos. A través de ellas el cliente declara sus capacidades e información adicional: qué navegador es, qué idioma prefiere, qué tipos de contenido acepta. Es lo que permite que una misma página se vea en español o en inglés según el cliente, sin cambiar la dirección.

Los servlets

Un servlet es un programa que se ejecuta dentro del contenedor web de un servidor de aplicaciones. Los clientes lo invocan usando HTTP, y reciben la respuesta del servlet cuando el contenedor lo carga y lo ejecuta.

Aparecieron en 1997 como respuesta a las aplicaciones CGI, y sus ventajas sobre ellas son cuatro:

  • Mejor rendimiento. Con CGI había que lanzar la aplicación entera con cada petición. Los servlets usan la misma aplicación y para cada petición lanzan un nuevo hilo —al estilo de los sockets—, lo que reduce el uso de memoria del servidor y el tiempo de respuesta. Es exactamente la ventaja de los threads de la lección 6.
  • Simplicidad, que es la clave de su éxito: el cliente solo necesita un navegador HTTP, el resto lo hace el servidor.
  • Control de sesiones: se pueden almacenar datos de las sesiones del usuario, que es la tarea más importante que HTTP no resuelve por sí solo.
  • Acceso a la tecnología Java: abre todas las posibilidades del lenguaje —JDBC, threads— y es independiente de la plataforma.

En principio un servlet podría comunicarse por cualquier protocolo cliente-servidor, pero se usan casi siempre con HTTP. Se compilan en la carpeta classes de la aplicación web, y son clases normales que heredan de una de estas dos:

  • jakarta.servlet.GenericServlet — para crear servlets genéricos, sin atarse a un protocolo.
  • jakarta.servlet.http.HttpServlet — la que crea servlets accesibles por HTTP. Es la que se usa en la práctica y la única que se ve en el resto de la lección.

Fijate en algo que engancha con la lección 3: un servlet no es más que una clase que hereda de otra. Toda la tecnología del lado del servidor está construida con las herramientas de la POO: herencia, sobrescritura de métodos y polimorfismo.

El ciclo de vida de un servlet

El contenedor web es el que maneja la vida del servlet, y las etapas son estas:

El ciclo de vida de un servlet: la primera petición hace que el servidor lo cargue y llame a init una sola vez; después cada petición va a service, que la deriva a doGet o doPost; el servlet escribe la respuesta HTML; y cuando se cierra, se llama a destroy. El ciclo de vida de un servlet Cliente petición HTTP init() una sola vez service() cada petición, en su hilo doGet() si la petición es GET doPost() si la petición es POST destroy() cuando se cierra respuesta HTML primera petición el servlet escribe la respuesta Y están los demás métodos doXXX() para el resto de las peticiones: doPut(), doDelete(), doOptions() y doTrace(). El servlet se carga una vez y se reutiliza: cada petición nueva es un hilo más, no un programa nuevo.
Se carga una vez, atiende muchas peticiones en hilos distintos y se destruye una vez.
  1. Se carga el servlet. Cuando se cursa la primera petición, el servidor lo carga.
  2. init() — se ejecuta una sola vez, y recibe un objeto que implementa jakarta.servlet.ServletConfig. Ese objeto de configuración permite que el servlet acceda a los parámetros de inicialización de la aplicación web.
    public void init(ServletConfig config) throws ServletException {
  3. service() — después de la inicialización, la instancia puede atender solicitudes. Cada solicitud se atiende en su propio hilo, y el contenedor llama a service() para cada una. Ese método determina el tipo de solicitud y la deriva al método apropiado.
    public abstract void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException {
    request y response son los objetos que permiten la comunicación con el cliente.
  4. destroy() — se llama si se va a cerrar el servlet, y su función es liberar recursos. Llamar a destroy() no corta el servlet: eso solo lo puede hacer el servidor.

Cuando se usa HttpServlet el ciclo cambia un poco, porque no se usa service() directamente: la clase lo sustituye para poder manejar los distintos tipos de solicitud. Por cada tipo de método HTTP hay un método doXXX(): doGet(), doPost(), y los demás. Lo que hace service() por dentro es:

  1. El service(request, response) heredado de GenericServlet transforma esos objetos en sus equivalentes HTTP.
  2. Se llama al nuevo service(), cuyos dos parámetros son un HttpServletRequest —con los datos que envió el cliente, y métodos como getParameter()— y un HttpServletResponse —con los datos que el servlet devuelve al cliente, y métodos como getWriter()—.
  3. Ese método llama a doGet() o doPost() según el tipo de llamada HTTP del cliente.

Lo normal al escribir un servlet es heredar de HttpServlet y redefinir doGet() o doPost(), o los dos. Y como es habitual que las llamadas GET y POST produzcan el mismo resultado, se suele escribir un método común que los dos llaman, por ejemplo processRequest(request, response).

La salida y los formularios

Los dos métodos reciben un HttpServletResponse y un HttpServletRequest. El primero sirve para que el servlet escriba datos, y esos datos tienen que estar necesariamente en HTML. Para escribir se usa getWriter(), que devuelve un PrintWriter con el método println(), igual que el System.out de toda la vida:

protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {
    response.setContentType("text/html");
    PrintWriter out = response.getWriter();

    out.println("<html>");
    out.println("<title>Escribiendo html</title>");
}

La gracia de un servlet es que sea dinámico: que interactúe con el usuario. Y la forma cómoda de hacer eso es enviarle parámetros. Los servlets, como CGI y cualquier otra tecnología de servidor, pueden tomar parámetros de dos formas.

Por la URL

Sirve solo para peticiones GET. Consiste en poner después de la ruta al servlet el símbolo ?, el nombre del primer parámetro, un = y su valor, que siempre es texto. Si hay más de uno, se separan con &:

https://www.google.com.ar/search?q=tutorial+de+java&ie=UTF-8&hl=es&meta=

En ese ejemplo se llama al programa search y se le pasan cuatro parámetros: q con valor tutorial+de+java, ie con UTF-8, hl con es, y meta, que no tiene valor.

Por formulario

Es la forma principal, y admite peticiones GET y POST. La diferencia entre las dos está en dónde viajan los parámetros: con GET se genera una dirección URL que los incluye —como en el ejemplo anterior—, con POST se envían de forma oculta.

<!DOCTYPE html>
<html>
<head>
   <title>Pagina inicial</title>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
   <h1>Primer Web App</h1>
   <h2>Ingrese sus datos:</h2>
   <form action="SaludoServlet" method="POST">
      Ingrese su Nombre: <input type="text" name="nombre" size="20"> <br>
      Ingrese su Apellido: <input type="text" name="apellido" size="20"> <br>
      <br>
      <input type="submit" value="Enviar">
   </form>
</body>
</html>

El atributo name de cada <input> es el nombre que va a tener el parámetro que llega al servlet, y su valor es lo que el usuario escribió. El action dice a qué servlet se manda, y el method, cómo. Y del lado del servlet el parámetro se recoge con getParameter():

import java.io.IOException;
import java.io.PrintWriter;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class SaludoServlet extends HttpServlet {

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        String txtNombre   = request.getParameter("nombre");
        String txtApellido = request.getParameter("apellido");

        PrintWriter out = response.getWriter();
        response.setContentType("text/html");

        out.println("<html>");
        out.println("<title>Primer servlet</title>");
        out.println("<body>");
        out.println("<h3>Hola, su Nombre es " + txtNombre +
                    " y su Apellido es " + txtApellido + "</h3>");
        out.println("</body>");
        out.println("</html>");
    }
}

Mirá lo que hace ese servlet: recibe dos textos del formulario y devuelve una página HTML armada con ellos. Es el mismo «hola mundo» de la página de Java, pero del lado del servidor. Y hay un detalle importante sobre el orden: setContentType() debería llamarse antes de escribir, porque define el tipo de contenido de la respuesta y una vez que se empezó a escribir ya no se puede cambiar el encabezado.

Los métodos de request y response

HttpServletRequest

Permite obtener información de los datos que envió el cliente: parámetros de usuario, cabeceras HTTP, información del protocolo, cookies, la ruta de acceso y la identificación de la sesión.

MétodoQué devuelve
getAuthType()El nombre del sistema de autentificación del servidor, o null si no usa ninguno.
getCookies()Un arreglo con todos los objetos Cookie que envió el cliente.
getContentLength()El tamaño en bytes del contenido solicitado.
getContentType()El tipo MIME de la petición, por ejemplo text/html.
getContextPath()La porción de la URL referida al contexto de la aplicación.
getHeaderNames() / getHeader(nombre)Una enumeración con los nombres de todas las cabeceras HTTP, o el contenido de una en particular.
getMethod()El método con el que se llamó: GET, POST, PUT…
getParameter(nombre)El valor de un parámetro. Es el que más se usa.
getParameterNames()Una lista con los nombres de todos los parámetros.
getParameterValues(nombre)Un arreglo con todos los valores de un parámetro, para cuando uno puede tener más de uno —como un grupo de casillas de verificación—.
getParameterMap()Un Map con todos los parámetros y valores de la petición.
getPathInfo()La porción de la ruta que sigue al nombre del servlet.
getQueryString()La cadena de la URL que sigue al carácter ?.
getRequestURL()La URL completa que se usó para la petición.
getRemoteUser()El nombre del usuario del cliente, si se pudo autentificar.
getSession(crear)El objeto HttpSession actual, o null. Crea uno nuevo si el parámetro vale true.
isRequestedSessionIdFromCookie()Si el identificador de sesión se obtuvo de una cookie.
isRequestedSessionIdValid()Si el identificador de sesión es válido.
isUserInRole(rol)Si el usuario está asociado a ese rol.

HttpServletResponse

Permite enviar información al cliente, que en los servlets HTTP se pasa en formato HTML. Para eso se usa setContentType() con el valor "text/html".

MétodoQué hace
addCookie(cookie)Agrega una cabecera Set-Cookie para esa cookie.
addHeader(nombre, valor) / setHeader(nombre, valor)Agrega o establece el valor de una cabecera HTTP. Las versiones IntHeader son para valores enteros.
containsHeader(nombre)Si la salida ya tiene esa cabecera.
encodeRedirectURL(url)Soporta el seguimiento de sesiones por URL cuando el cliente no acepta cookies.
getContentType()El tipo MIME de la respuesta.
sendError(estado) / sendError(estado, msg)Establece el código de estado HTTP y, opcionalmente, un mensaje de error.
sendRedirect(location)Pone el estado HTTP en 302 (movido provisionalmente) y manda al navegador a otra dirección.
setCharacterSet(tipoMIME)Indica la codificación de caracteres de la respuesta, por ejemplo text/html;charset=UTF-8.
setContentType(tipoMIME)Establece el tipo de contenido que se va a enviar.
setStatus(código)Establece el código de respuesta. Solo se usa cuando la respuesta no es un error; si lo es, va sendError.

Para los códigos de respuesta, la interfaz define constantes estáticas que empiezan con SC, y su valor es el número estándar. Por ejemplo, HttpServletResponse.SC_NOT_FOUND vale 404, el código de «página no encontrada». Usar la constante en lugar del número hace que el código se lea solo.

El contexto del servlet

El contexto del servlet es una interfaz que proporciona el servidor de aplicaciones para entregarle servicios a una aplicación web. Se obtiene con el método getServletContext() del objeto ServletConfig, que es el que se recibe en el método init. Y es uno solo para toda la aplicación: todos los servlets de la aplicación comparten el mismo contexto.

A través del contexto se accede a información de la aplicación: atributos cuyos valores persisten entre invocaciones al servlet, peticiones a otros servlets, las capacidades del servlet y los mensajes de error.

MétodoQué hace
getAttribute(nombre)Obtiene un atributo de la aplicación.
getAttributeNames()Una enumeración con todos los atributos del contexto.
setAttribute(nombre, valor)Establece un atributo, que queda disponible para toda la aplicación.
removeAttribute(nombre)Elimina el atributo indicado.
getContext(ruta)El contexto de otro servlet del mismo servidor. La ruta tiene que empezar con /.
getMajorVersion() / getMinorVersion()La versión de la especificación de servlets que el servidor puede ejecutar.
getMimeType(archivo)El tipo MIME de un archivo del servidor.
getRealPath(ruta)La ruta absoluta del sistema de archivos para una ruta interna al servidor.
getServerInfo()El nombre del servidor de aplicaciones que se está usando.

Dos de esos métodos van a volver a aparecer: setAttribute es la forma de que dos recursos distintos compartan datos —un servlet que calcula y una JSP que muestra—, y getRealPath es lo que se necesita para leer o escribir un archivo desde una aplicación web.

Las sesiones HTTP

Navegar por HTTP no se parece a una comunicación cliente-servidor típica. Cuando el cliente pide un recurso, el servidor se lo da y punto, porque HTTP es un protocolo sin estado: cada petición es tratada de forma independiente. Los navegadores hacen una petición nueva por cada elemento de la página que hay que descargar; cada clic genera otra petición. Y el servidor no sabe si esas peticiones vienen del mismo cliente ni si están relacionadas entre sí. En definitiva, se olvida del cliente apenas resuelve su petición.

Eso es un problema cuando se quiere ir almacenando información sobre el cliente: la lista de productos elegidos, los datos del usuario ya ingresados, el idioma que eligió. Para recordar datos hay tres técnicas, de la más vieja a la que se usa hoy:

Cookies

Una cookie es un archivo que se graba en el ordenador del cliente con información sobre él. El servidor la manda en una cabecera de la respuesta, el navegador la guarda y la reenvía en cada petición siguiente al mismo sitio.

Tienen un tiempo de vida: pueden durar solo lo que dura la sesión del navegador —se borran al cerrarlo— o tener una fecha de vencimiento. Y son la forma estándar de identificar a un cliente: al guardar un identificador de sesión en una cookie, el servidor puede reconocerlo en cada petición que sigue.

Añadir el ID de sesión como parámetro

Se agrega el número de sesión como parámetro, ya sea en la dirección (con GET) o como campo oculto de un formulario (con POST). El problema es que el número hay que añadirlo continuamente mientras el usuario navega: cada enlace, cada formulario, cada redirección tiene que llevar el identificador. Un solo enlace sin él y se pierde la sesión.

Con el API de Java: HttpSession

La interfaz HttpSession resuelve el problema dejando que el servidor se ocupe. Se obtiene un manejador de sesión llamando a getSession() sobre el objeto request, y a partir de ahí se guardan y recuperan objetos con setAttribute y getAttribute —los mismos nombres que en el contexto, pero con otro alcance—.

MétodoQué hace
getAttribute(nombre)Obtiene un atributo de la sesión actual, o null si no existe.
getAttributeNames()Una enumeración con todos los atributos de la sesión.
setAttribute(nombre, valor)Establece un atributo para la sesión.
removeAttribute(nombre)Elimina el atributo indicado.
setMaxInactiveInterval(segundos) / getMaxInactiveInterval()Establecen o consultan cuánto tiempo puede estar la sesión sin que el cliente haga nada antes de darse por terminada.
getLastAccessedTime()La fecha y hora de la última petición del usuario, en milisegundos.
invalidate()Anula la sesión actual y elimina todos sus objetos. Es lo que se usa para «cerrar sesión».
isNew()true si el usuario todavía no había establecido sesión.

Como un cliente HTTP no puede avisar que terminó de usar la aplicación —no hay forma de saber que alguien cerró la pestaña—, cada sesión tiene un timeout asociado, de modo que los recursos se puedan liberar. Ese período es el que se consulta y se cambia con getMaxInactiveInterval y setMaxInactiveInterval. Sin ese mecanismo, un servidor con miles de usuarios dejaría las sesiones de todos abiertas para siempre.

JSP: Java Server Pages

JSP (Java Server Pages) ofrece la independencia de operar en diferentes plataformas y servidores, y además combina el poder de Java en el servidor con la facilidad de escribir HTML.

Una página JSP es una página web normal —con extensión .jsp— a la que se le puede agregar código Java usando etiquetas especiales. Esas etiquetas las traduce el servidor de aplicaciones, igual que traduce el código de un servlet. Las etiquetas empiezan con <% y terminan con %>.

Y ahí está la ventaja de fondo sobre el servlet puro: JSP permite apreciar mejor la distinción entre el contenido y su presentación. En un servlet cada línea de HTML va dentro de un out.println(); en una JSP se escribe HTML y solo se abren las etiquetas cuando hace falta código.

En la práctica toda JSP se convierte en un servlet, así que necesita el mismo sistema de archivos: la carpeta WEB-INF, el archivo web.xml, las carpetas classes y lib. Las páginas JSP se guardan en la raíz de la aplicación y las bibliotecas comprimidas —los archivos jar— en lib. Las de Java no, porque el servidor de aplicaciones ya las incorpora.

<%-- Esta página da la fecha y hora cuando fue solicitada
     así como el número de accesos --%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.*"%>
<%! Date loadDate = new Date();
    int loadCounter = 1;%>
<!DOCTYPE html>
<html>
<head>
   <title>Ejemplo JSP</title>
</head>
<body>
   <h1>Bienvenid@!</h1>
   <h2>Desde <%=loadDate%>, ésta página ha sido accesada <%=loadCounter%>
      <%
      if (loadCounter++ == 1)
            out.println(" vez.<br>");
      else
            out.println(" veces.<br>");
      %>
      <%
      out.println("Regrese pronto");
      %>
   </h2>
</body>
</html>

El ciclo de vida de una página JSP

  1. La página empieza como código nativo HTML con JSP: es lo que escribió quien programa, mezcla de HTML e instrucciones Java.
  2. La página se transforma en el servlet equivalente. Eso ocurre tras la primera petición del cliente: se genera un archivo compilado listo para ejecutar, y el servidor lo almacena para uso posterior. O sea que en las peticiones siguientes ya no se compila, salvo que se detecten cambios en el código original.
  3. Se carga la clase para cada petición entrante y se le pasa la petición al servlet ya compilado. Si la página se modificó, el servidor se da cuenta, genera el servlet nuevo, lo compila y lo carga.
  4. La ejecución del servlet produce el HTML, que es lo que recibe el cliente.

Ese es el motivo por el que la primera visita a una JSP es más lenta que las siguientes: en la primera hay que traducir y compilar.

Ámbito y visibilidad

Los objetos JSP pueden crearse de tres maneras: implícitamente por directivas JSP, explícitamente a través de acciones, o excepcionalmente usando fragmentos de código.

El ámbito de un objeto establece su duración desde que se crea hasta que se destruye; la visibilidad indica los lugares de la página donde se puede usar. Hay cuatro ámbitos, del más chico al más grande:

AlcanceÁmbitoQué abarca
1 (menor)PáginaLos objetos pertenecen a la página en que fueron creados.
2PeticiónProvienen de la petición que está siendo atendida.
3SesiónPertenecen a la misma sesión, o sea al mismo usuario.
4 (mayor)AplicaciónPertenecen a la misma aplicación, o sea que los ven todos los usuarios.

Y hay una regla de diseño que conviene tomar desde el principio: usar el ámbito más específico posible para el contexto en el que se está trabajando. Si un dato se usa solo en una página, va en el ámbito de página; si se usa a través de varias peticiones, en el de sesión; y si lo usan distintas sesiones —un contador global de visitas, el estado de la aplicación—, en el de aplicación. Elegir un ámbito más grande del necesario es la forma más fácil de que dos usuarios se pisen los datos.

Los componentes de una página JSP

Una página JSP se escribe combinando seis tipos de elementos, y cada uno se abre y se cierra de una forma distinta.

Directivas

Son instrucciones dirigidas al servidor que indican qué tipo de código se debe generar. Sirven para importar clases o incluir archivos, y su formato es <%@ nombre atributo="valor" %>.

La directiva más importante es page, y sus atributos más usados son:

  • import — la lista de paquetes de clases a importar. Los paquetes java.lang.*, java.servlet.*, java.servlet.jsp.* y java.servlet.http.* se incluyen automáticamente. Es la única directiva que se puede repetir en el mismo archivo.
  • contentType — el tipo MIME de la página, normalmente text/html, y opcionalmente su codificación: text/html; charset=UTF-8.
  • pageEncoding — la codificación de caracteres del archivo, por ejemplo UTF-8.
  • session — si vale false, la página no puede usar sesiones. Por defecto es true.
  • errorPage — permite pasar el control a otra página cuando se genera una excepción no capturada, para personalizar el mensaje de error en lugar de dejar el que muestra el servidor. La página de error tiene que llevar isErrorPage en true, y puede consultar lo que pasó con la variable implícita exception, que tiene los métodos getMessage y printStackTrace de la lección 4.
  • isThreadSafe — true si la página acepta varias peticiones simultáneas.
  • language — el lenguaje de programación de la página. El valor recomendable es "java".

La directiva include, al estilo del #include de C, agrega a la página el código de otro archivo cuyo nombre se indica, y puede haber varias: <%@ include file="/cabecera.html" %>. Y la directiva taglib permite usar etiquetas personalizadas definidas en un descriptor, con un prefijo a elección.

Comentarios

Hay dos tipos, y la diferencia es importante: los propios de JSP, que van entre <%-- y --%> y solo son visibles en el código original; y los de HTML, que van entre <!-- y --> y llegan al navegador, porque quedan dentro del HTML generado. Si el comentario tiene información interna, va en el primero.

Expresiones

Van entre <%= y %> y permiten que el resultado de una expresión Java se convierta en texto dentro de la página generada. No llevan punto y coma al final, porque no son instrucciones sino valores: <%="Lucía Fernández"%>.

Instrucciones (scriptlets)

Van entre <% y %> y son sentencias puras de Java. El servidor las coloca como parte del método service del servlet resultante, así que se puede escribir cualquier fragmento de código, extendido por elementos HTML y sin limitarse a una línea:

<% for (int i = 1; i < 5; i++) { %>
      <h<%=i%>>Lucía Fernández</h<%=i%>>
<% } %>

Pero conviene evitarlos siempre que se pueda. El propio apunte lo recomienda: cuando las bibliotecas de etiquetas dan una funcionalidad similar, usarlas hace que las páginas sean más fáciles de leer y de mantener, ayuda a separar la lógica de negocio de la de presentación —que es SRP, de la lección 5— y hace que las páginas evolucionen más fácilmente. Y hay una razón práctica: el código Java de un scriptlet no se puede reutilizar en otra JSP, así que la lógica común termina reimplementada en varias páginas.

Declaraciones

Van entre <%! y %> y sirven para declarar variables de instancia, métodos o clases internas, con la sintaxis de Java. A diferencia de los scriptlets, no pueden usar objetos implícitos. Lo que se declara ahí es global a todo el archivo: los métodos y las variables son de la página, no de una ejecución. Y si se declaran con static, pasan a ser comunes a todos los usuarios que usen esa página.

Objetos implícitos

Son objetos predefinidos que la página ya tiene disponibles, sin declararlos. Los nueve son estos:

ObjetoQué es y para qué sirve
requestEl HttpServletRequest del servlet. Para obtener información: getParameter, getParameterNames, getParameterValues, getRemoteAddr, getRemoteHost.
responseEl HttpServletResponse. Permite escribir datos en la página y manejar cabeceras, códigos de estado y cookies.
outEl flujo de salida hacia el HTML, equivalente a lo que devuelve getWriter. Es lo que usan los scriptlets para escribir.
sessionEl HttpSession asociado a la petición. Con getAttribute y setAttribute se guardan objetos accesibles desde cualquier JSP de la sesión, y con invalidate() se la cierra.
applicationEl ServletContext: común a toda la aplicación web. Guarda información accesible desde todas las páginas, independientemente de la sesión.
pageContextPara obtener datos sobre el contexto de la página: espacios de nombres y almacenamiento local a la página, con getAttribute y setAttribute.
configEl ServletConfig de la aplicación: parámetros de inicialización y contexto.
pageUna referencia a la página JSP, que es un HttpServlet. Es un sinónimo de this.
exceptionPara capturar errores. Solo es válido en las páginas de error, o sea en las que tienen isErrorPage en true.

Fijate que request, response, session y application son los mismos objetos de los servlets, con los mismos métodos: JSP no es una tecnología distinta, es otra forma de escribir lo mismo.

Concurrencia y rutas reales

La concurrencia en las páginas JSP

Una página JSP puede recibir varias peticiones al mismo tiempo, cada una en su hilo. La directiva page con el atributo isThreadSafe ofrece una forma de evitar los problemas que eso ocasiona, pero el problema de ese enfoque es que no es escalable: reduce considerablemente el desempeño de los sistemas con muchos procesos. La alternativa es usar regiones críticas en los scriptlets, sincronizando el acceso a los objetos compartidos que se vio en la lección 6:

<%@ page import="java.util.*"%>
<%
synchronized (application) {
   Properties list = application.getAttribute("users");
   list.add(new User("ana"));
   application.setAttribute("users", list);
}
%>

La ruta real para acceder a los archivos

Cuando se trabaja con JSP y servlets se usan rutas relativas para referirse a los archivos, por ejemplo /WEB-INF/Promedios.txt. Pero para leer o guardar datos en un archivo hace falta la ruta completa, la ruta real. Y ahí es donde el contexto del servlet vuelve a aparecer:

ServletContext sc = this.getServletContext();
String path = sc.getRealPath("/WEB-INF/Promedios.txt");

// el caracter separador tambien puede dar problemas entre sistemas
path = path.replace('\\', '/');

El último reemplazo resuelve un problema concreto: la ruta que devuelve el servidor puede venir con barras invertidas, que son las de Windows, y eso es un problema en los sistemas que usan la barra normal. Es el mismo asunto de los separadores de la lección 4, y se resuelve igual: normalizando.

Servlets y JSP trabajando juntos

Uno de los problemas principales al manejar servlets y JSP es que a veces hace falta que un servlet llame a otro servlet o a una JSP, y que la respuesta del segundo dependa de una condición del primero. O sea: un servlet delega la respuesta en una página.

Hay tres métodos para hacer esa colaboración, y conviene conocer los tres para entender por qué se usa el segundo:

  1. Encadenado. Se usaba al principio, antes de la aparición de Java EE. Su manejo es complejo e insuficiente para la mayoría de los problemas.
  2. Lanzamiento de solicitudes. Es la forma correcta: permite que un servlet —o una JSP— llame a otro recurso reutilizando los objetos request y response ya creados. La posibilidad la da una interfaz llamada RequestDispatcher.
  3. Petición desde JavaScript. Es la menos elegante, pero resuelve fácilmente problemas chicos: la instrucción location=URL modifica la página actual sustituyéndola por la indicada. Si en esa URL se incluyen parámetros, se llama al servlet o a la JSP pasándolos. Desde un servlet se escribe así:
    out.println("<script language='JavaScript'>" +
        "location='/servlet/form?nombre=pepe&edad=28" +
        "&nacionalidad=francia';</script>");
La forma correcta de repartir el trabajo: el navegador pide, el servlet hace la lógica y guarda el resultado, y le pasa la petición a la JSP que solo se ocupa de mostrar. El servlet hace la lógica, la JSP muestra el resultado Navegador petición POST Servlet valida, consulta la base con JDBC y guarda el resultado con setAttribute forward JSP solo HTML y las etiquetas que muestran los datos vuelve a la misma petición: el navegador nunca supo que hubo dos recursos forward contra sendRedirect Con forward, el reenvío ocurre dentro del servidor: es la misma petición, y los datos viajan en el request o en la sesión. Con sendRedirect, el servidor le pide al navegador que haga una petición NUEVA a otra dirección: se pierde todo lo del request.
El reparto que se usa en la práctica: la lógica en el servlet, la presentación en la JSP, y el reenvío con RequestDispatcher.

Ese esquema es la razón por la que esta lección termina acá: con un servlet que hace la lógica —valida datos, consulta la base con JDBC de la lección 7, o con Hibernate de la lección 8— y una JSP que solo muestra, se tiene una aplicación web completa. Y es, además, la aplicación concreta del principio de responsabilidad única de la lección 5: cada pieza hace una cosa, y por eso se pueden cambiar por separado.

Ejercicios

1. Cliente o servidor

Explicar por qué las tecnologías del lado del servidor resuelven el problema que tienen las del lado del cliente. Después indicar, para cada caso, si es una tecnología de servidor y de quién es: CGI, PHP, ASP.NET, Servlets.

2. El ciclo de HTTP

Enumerar los cuatro pasos de una petición HTTP y explicar por qué el protocolo «sin estado» obliga a inventar las sesiones. Después indicar qué método usaría el navegador en cada caso: ver una página, enviar un formulario con una contraseña, preguntar qué métodos acepta el servidor.

3. Parámetros

Explicar las dos formas de pasar parámetros a un servlet y decir cuál conviene para un formulario de login y por qué.

4. El ciclo de vida

Ordenar y explicar los métodos del ciclo de vida de un servlet, e indicar cuántas veces se ejecuta cada uno: service(), init(), doPost(), destroy().

5. Sesión o aplicación

Indicar en qué ámbito guardaría cada dato y por qué:

  1. El nombre del usuario que inició sesión.
  2. El contador de visitas totales del sitio.
  3. El resultado de una consulta que se va a mostrar en la página siguiente.
  4. La cadena de conexión a la base de datos.

6. Servlet o JSP

Se tiene que mostrar una tabla con los alumnos de un curso. Explicar cómo se reparte el trabajo entre un servlet y una JSP, y por qué no conviene hacerlo todo en la JSP con un scriptlet.

Respuestas sugeridas

1. Las tecnologías del lado del cliente dependen de que el usuario tenga instalado y habilitado el software necesario —el plugin de Flash, un JavaScript compatible— y no hay forma de obligarlo a instalarlo. Las del lado del servidor hacen la interpretación donde quien programa controla el entorno, así que el cliente solo necesita un navegador. CGI: tecnología de servidor, la más veterana, y no es de nadie en particular: la aplicación puede estar en cualquier lenguaje, aunque el más usado fue Perl. PHP: de servidor, y su ventaja sobre ASP es que es de código abierto y compatible con Apache. ASP.NET: de servidor, de Microsoft, y funciona solo en servidores IIS. Servlets: de servidor y de Java, parte de Jakarta EE.

2. Los cuatro pasos: el cliente establece conexión y envía la petición en texto; el servidor analiza la petición y localiza el recurso; el servidor envía una copia del recurso; el servidor cierra la conexión. Como en el cuarto paso se termina la conversación y el servidor no guarda nada, en la petición siguiente no tiene forma de saber que es el mismo usuario: de ahí la necesidad de un número de sesión que el cliente guarde y reenvíe. Los métodos: ver una página → GET; enviar un formulario con contraseña → POST, porque con GET los datos viajan en la dirección y quedan en el historial del navegador y en los registros del servidor; preguntar qué métodos acepta → OPTIONS.

3. La primera forma es en la URL, después de un ?, con los pares nombre=valor separados por &. Solo sirve para GET, y los datos quedan visibles en la barra de direcciones. La segunda es por formulario, y admite GET y POST: con POST los parámetros viajan de forma oculta. Para un login conviene POST, y no es una preferencia estética: con GET la contraseña queda escrita en la URL, o sea en el historial del navegador, en los registros del servidor y en cualquier enlace que se copie y pegue. Además, la longitud de una URL tiene límite.

4. El orden es init() → service() → doPost() → destroy(). init() se ejecuta una sola vez, cuando el servidor carga el servlet, en la primera petición. service() se ejecuta en cada petición, cada una en su propio hilo, y es el que deriva la petición al método doXXX() que corresponde. doPost() se ejecuta cada vez que llega una petición POST: puede ser muchas veces, o ninguna si nunca llega un POST. Y destroy() se ejecuta una sola vez, cuando se cierra el servlet, y sirve para liberar recursos.

5. (a) En la sesión: el nombre del usuario es propio de ese usuario y tiene que durar mientras navega, pero no lo tienen que ver los demás. (b) En la aplicación: el contador de visitas totales lo ven y lo modifican todos, y tiene que sobrevivir a las sesiones. (c) En el ámbito de petición: es un dato que se calcula en un recurso y se muestra en el siguiente dentro del mismo pedido —es el caso del forward del servlet a la JSP—, y no tiene sentido que sobreviva más. (d) En la aplicación, porque es una configuración del sistema que no cambia por usuario. Y en los cuatro casos la regla es la misma: el ámbito más chico que alcance.

6. El reparto: el servlet recibe la petición, valida los datos, consulta la base con JDBC, guarda la lista de alumnos con request.setAttribute("alumnos", lista) y hace un forward a la JSP. La JSP solo arma el HTML y recorre esa lista para llenar la tabla. No conviene hacerlo todo en la JSP con un scriptlet por tres razones: mezcla la lógica con la presentación, con lo que las dos cosas se vuelven difíciles de mantener por separado; el código Java de un scriptlet no se puede reutilizar en otra página, así que la consulta terminaría copiada en varias; y la JSP necesita la conexión a la base, con lo que la capa de presentación queda atada al gestor y a los datos. Separado, en cambio, se puede cambiar el diseño de la página sin tocar la lógica, y la lógica sin tocar el diseño.

Resumen

  • Las aplicaciones de escritorio del lado del cliente dependen de que el usuario tenga el software. Las tecnologías del lado del servidor hacen la interpretación donde quien programa controla el entorno: solo hace falta un navegador. Son CGI, ASP/ASP.NET, PHP y, del lado de Java, Servlets y JSP.
  • Jakarta EE (antes Java EE) es un conjunto de especificaciones, no un programa: define qué servicios debe dar una plataforma para aplicaciones empresariales. Se ejecuta en un contenedor web: Tomcat, Jetty, WildFly, GlassFish, JBoss, WebLogic, WebSphere.
  • HTTP es el protocolo de la web: el cliente pide, el servidor responde y cierra la conexión. Es sin estado, y por eso hacen falta las sesiones. Sus métodos son GET, HEAD, POST, PUT, DELETE, OPTIONS y TRACE.
  • Un servlet es una clase que hereda de HttpServlet y se ejecuta en el contenedor. Su ventaja sobre CGI es el rendimiento: se carga una vez y cada petición es un hilo nuevo, no un programa nuevo.
  • Su ciclo de vida es init() una vez, service() en cada petición —que deriva a doGet() o doPost()— y destroy() al cerrarse.
  • Se escribe la respuesta con getWriter() y se leen los parámetros con getParameter(). Los parámetros llegan por la URL —solo GET— o por formulario —GET o POST, y con POST viajan ocultos—. Los objetos HttpServletRequest y HttpServletResponse tienen los métodos para leer cabeceras y para manejar estados, redirecciones y cookies.
  • El contexto del servlet (ServletContext) es común a toda la aplicación y da atributos compartidos y la ruta real de los archivos, con getRealPath().
  • Las sesiones resuelven el problema del protocolo sin estado: con cookies, con el identificador en la URL o con la interfaz HttpSession, que es la forma correcta. Cada sesión tiene un timeout para poder liberar recursos.
  • JSP es HTML con etiquetas que el servidor traduce a un servlet. Se escribe con directivas (page, include, taglib), comentarios, expresiones, scriptlets y declaraciones, y tiene nueve objetos implícitos: request, response, out, session, application, pageContext, config, page y exception.
  • El ámbito de un objeto puede ser página, petición, sesión o aplicación, y hay que elegir el más chico que alcance.
  • Los scriptlets conviene evitarlos: mezclan la lógica con la presentación y su código no se puede reutilizar.
  • Para que un servlet y una JSP colaboren, la forma correcta es el lanzamiento de solicitudes con RequestDispatcher: el servlet hace la lógica y la JSP muestra.

Y con esto el recorrido está completo: se empezó por el paradigma en la lección 1, siguió el lenguaje, después las clases y los objetos, los errores y las colecciones, el diseño, la concurrencia, los datos y, al final, la publicación de la aplicación en la web. Si hay que llevarse una sola idea de las nueve lecciones, es esta: cada herramienta que aparece en el camino resuelve un problema concreto, y se elige por ese problema, no porque sea la más nueva.