0% encontró este documento útil (0 votos)
437 vistas444 páginas

Manual - Java II - Java EE

Cargado por

Sergio Grau
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
437 vistas444 páginas

Manual - Java II - Java EE

Cargado por

Sergio Grau
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 444

carrier

PROGRAMADOR JAVA DE ORACLE


escola

JAVA EE

MANUAL DEL CURSO

Nombre
Curso
Grupo
MANUAL JAVA
APLICACIONES WEB JAVA EE

ACADEMIA CARRIER)250$&,Ð1 2014

1
Índice de contenidos
UNIDAD 11. APLICACIONES WEB J2EE Y HTML53 UNIDAD 16. ACCESO A DATOS. SEGURIDAD.
1. Aplicaciones web Java EE ..................................................... 3 PATRONES DE DISEÑO ........................................... 208
2. El protocolo HTTP ................................................................ 7 1. Tecnologías de acceso remoto a datos. ............................. 208
3. Aplicación de servidor web para Java EE ............................ 11 2. Pool de conexiones en servidores web .............................. 214
4. Fundamentos de HTML5 ..................................................... 22 3. Seguridad en aplicaciones Web......................................... 220
5. Fundamentos de CSS ........................................................... 34 4. Patrones de diseño............................................................. 235
6. Fundamentos de JavaScript ................................................. 41 PRÁCTICA........................................................................... 246
PRÁCTICA ............................................................................. 49 UNIDAD 17. DOCUMENTOS XML. ........................ 247
UNIDAD 12. LA TECNOLOGÍA SERVLET ............. 51 1. Las tecnologías XML. ....................................................... 247
1. Fundamentos sobre Servlets ................................................ 51 2. JAXP: APIs de Java para XML ........................................ 271
2. Contexto de solicitud y respuesta de los servlets ................. 61 3. El API JAXB. ................................................................... 286
3. Métodos de solicitud en servlets .......................................... 68 4. Librería JSTL XML. ......................................................... 292
4. Redirigiendo la respuesta en servlets. .................................. 80 PRÁCTICA........................................................................... 294
PRÁCTICA ............................................................................. 87 UNIDAD 18. COMPONENTES JAVA EE: EJB 3.0 296
UNIDAD 13. GESTIÓN DE SERVLETS Y FILTROS89 1. Tecnología JMS ................................................................ 296
1. Gestión de sesiones.............................................................. 89 2. EJB 3.0.............................................................................. 304
2. Gestión de eventos y registro de eventos. ............................ 91 3. Ciclo de vida de EJBs e inyección de dependencias. ........ 327
3. Gestión de estado ................................................................. 95 4. Servicio de temporizador en beans.................................... 335
4. Filtros ................................................................................ 111 5. EJBs como contextos de persistencia ................................ 340
PRÁCTICA ........................................................................... 122 6. Seguridad en Java EE. ....................................................... 348
UNIDAD 14. LA TECNOLOGÍA JSP ....................... 124 PRÁCTICA........................................................................... 359
1. Fundamentos de JSP .......................................................... 124 UNIDAD 19. SERVICIOS WEB ................................ 360
2. Reutilización de componentes web .................................... 135 1. Fundamentos sobre servicios Web .................................... 360
3. Uso de JavaBeans en páginas JSP ..................................... 140 2. Servicios web RESTful y JAX-RS.................................... 371
4. JSTL y el lenguaje EL ....................................................... 146 3. Clientes de servicios web RESTful. .................................. 378
PRÁCTICA ........................................................................... 160 4. Técnicas avanzadas con JAX-RS ...................................... 387
UNIDAD 15. ETIQUETAS PERSONALIZADAS .... 163 PRÁCTICA........................................................................... 397
1. Fundamentos de librerías de etiquetas ............................... 163 UNIDAD 20. APLICACIONES MVC ........................ 398
2. Etiquetas basadas en clases manejadoras ........................... 168 1. El marco Spring ................................................................ 398
3. Opciones avanzadas en clases manejadoras de etiquetas. .. 192 2. Técnicas avanzadas con Spring ......................................... 416
4. Ficheros de etiqueta ........................................................... 199 3. Introducción al marco Structs 1.3. .................................... 427
5. Técnicas avanzadas con TLDs ........................................... 205 PRÁCTICA........................................................................... 440
PRÁCTICA ........................................................................... 207

2
UNIDAD 11.APLICACIONES WEB J2EE Y HTML5
1. Aplicaciones webJava EE
J2EE o Java Plataform Enterprise Edition (conocido desde la versión 1.4 como Java EE) es una plataforma
de programación para desarrollar y ejecutar software de aplicaciones en el lenguaje de programación Java.
Permite utilizar arquitecturas de N capas distribuidas y se apoya ampliamente en componentes de software
modulares ejecutándose sobre un servidor de aplicaciones. Java EE tiene varias especificaciones de API, tales
como JDBC, RMI, e-mail, JMS, Servicios Web, XML, etc. y define cómo coordinarlos. Java EE también
configura algunas especificaciones únicas para componentes Java EE. Estas incluyen Enterprise JavaBeans,
servlets, portlets (siguiendo la especificación de Portlets Java), JavaServer Pages (JSPs) y varias tecnologías de
servicios web. Ello permite al desarrollador crear una Aplicación de Empresa portable entre plataformas y
escalable, a la vez que integrable con tecnologías anteriores. Otros beneficios añadidos son, por ejemplo, que
el servidor de aplicaciones puede manejar transacciones, seguridad, escalabilidad, concurrencia y gestión de
los componentes desplegados, significando esto que los desarrolladores pueden concentrarse más en la lógica
de negocio de los componentes en lugar de en tareas de mantenimiento de bajo nivel.
1.1. Las tecnologías Java EE.
Las APIs de Java 2EE incluyen varias tecnologías que extienden la funcionalidad de las APIs base de Java SE.
javax.ejb.*
La API Enterprise JavaBeans define un conjunto de APIs que un contenedor de objetos distribuidos
soportará para suministrar persistencia, RPCs (usando RMI o RMI-IIOP), control de concurrencia,
transacciones y control de acceso para objetos distribuidos.
javax.naming
Los paquetes javax.naming, javax.naming.directory, javax.naming.event, javax.naming.ldap y javax.naming.spi
definen la API de Java Naming and Directory Interface (JNDI).
java.sql
Los paquetes java.sql y javax.sql definen la API JDBC.
java.transaction.*
Estos paquetes definen la Java Transaction API (JTA).
javax.xml.*
Estos paquetes definen la API JAXP, para manipular documentos XML.
javax.jms.*
Estos paquetes definen la API JMS, para gestionar mensajes de e-mail.
javax.persistence
Este paquete provee las clases e interfaces para gestionar la interacción entre los proveedores de
persistencia, las clases administradas y los clientes de la API Java Persistence.
1.2. Arquitectura por capas.
Java EE es una plataforma capaz de proporcionar algunos de los servicios que una aplicación empresarial
necesita:
• La habilidad de almacenar datos en distintas bases de datos comerciales.
• La habilidad de distribuir la aplicación en más de un computador.
• Soportar transacciones.
• Ejecuciones multihilo.
• Gestión de pool de conexiones.
• Diseño escalable.
Java EE permite descomponer una aplicación en componentes que podemos disponer por capas. En los años
80's y 90's era habitual utilizar una arquitectura de 2 capas.

3
Figura 1

Sin embargo, esta arquitectura tiene el inconveniente de que la lógica de negocio y la lógica de presentación
están mezcladas, lo que resulta en un sistema difícil de entender y de mantener.
Con un modelo de 3 capas se separa la lógica de presentación y la lógica de negocio en 2 capas distintas.
Figura 2

Pero tiene la desventaja de la complejidad involucrada en desarrollar la aplicación, y de se requiere por parte
del desarrollador conocimientos de computo distribuido (por ejemplo, sobre RMI, CORBA, ...).
La Arquitectura Java EE es muy similar al modelo de tres capas peromás más extendido:
Figura 3

La tecnología Java EE soporta multicapa y se apoya en Servidores de aplicaciones. Un Servidor de aplicación


proporciona un marco de trabajo para desarrollar la aplicación multicapa. Este marco de trabajo proporciona
servicios tales como cómputo distribuido, multihilo, seguridad y persistencia.
En el pasado, cada servidor de aplicación era desarrollado independientemente y por lo tanto implementaba
los servicios de diferente manera. Por lo tanto, cambiar de un servidor de aplicaciones a otro era difícil o
imposible. En 1997 un grupo de vendedores de servidores de aplicaciones ( BEA, IBM, Oracle, Sybase, SUN
entre otros) comenzaron a trabajar juntos para definir un estándar en los servidores de aplicación basados en
el lenguaje Java. La visión fue crear un conjunto de servicios estandarizados y unas APIs para acceder a estos
servicios. Como resultado se creó el estándar J2EE.
J2EE define tres tipos de componentes que un programador puede desarrollar en cada capa:
• Servlets, para las capas de la lógica del negocio.
• JSP, para la capa de presentación.
• Enterprise Java Beans, para las capas que invocan servicios y acceden a datos.
Esos tres componentes se desarrollan en el lado servidor. Esto significa que el código que implementa cada
uno de estos tres componentes es ejecutado por un servidor de aplicación. Pero también podemos hablar de
componentes del lado cliente, como las páginas HTML que incluso pueden contener código script. Los
componentes del lado cliente se desarrollan como parte de la aplicación pero no son ejecutados por el
servidor de aplicaciones, sino que su contenido o código script es interpretado por el propio cliente.
1.3. Modelo cliente/servidor en aplicaciones Web.
Las aplicaciones Java EE para Web se construyen con componentes web que realizan tareas específicas y que
exponen sus servicios a través de una red usando el protocolo HTTP. Las aplicaciones Web que usan

4
tecnologías Java se componen de: servlets, páginas JSP, archivos HTML, archivos de imágenes y otros. Todos
estos componentes deben coordinarse entre sí para ofrecer un conjunto completo de servicios a los usuarios.
En general, las aplicaciones Web residen en una aplicación servidora Web (por ejemplo, Apache, ISS,
Glassfish, etc.). Pero cada tecnología de desarrollo requiere de un servidor Web que la soporte. Por ejemplo,
la tecnología ASPX de Microsoft es soportada por el servidor Web IIS, pero no por el servidor Glassfish.
Para la tecnología Java EE los servidores Web que los soportan son Apache, Glassfish, JBoss y otros.
La aplicación servidora para Java EE gestiona los diversos recursos mediante: un contenedor de servlets, un
contenedor de EJB, un servidor JNDI, un servidor JMS, etc. Todos estos recursos están descritos en un
archivo de configuración denominado web.xml.
En las aplicaciones Web se distinguen dos tipos de recursos:
• Recursos del lado servidor o dinámicos. Incluyen código que es ejecutado por el servidor correspondiente.
• Recursos del lado cliente o estáticos. No incluyen código que deba ser ejecutado por un servidor.
Una aplicación Web no se ejecuta en un único proceso o en una única máquina. En vez de eso, normalmente
se hospeda en un servidor Web y es accedida a través de un navegador Web usando el protocolo HTTP. Es
necesario tener una comprensión básica de cómo trabajan estos elementos y se comunican entre sí antes de
empezar a escribir código.
Figura 4

El proceso de comunicación típico entre un navegador y un servidor se puede generalizar en los siguientes
pasos:
1) Un usuario usa un navegador Web (como Internet Explores, Chrome u otro) para iniciar un
requerimiento a un recurso de un servidor Web.
2) Se utiliza el protocolo HTTP para enviar un requerimiento de tipo GET al servidor Web.
3) El servidor Web procesa el requerimiento GET sobre el servidor (normalmente localiza el recurso
requerido y lo ejecuta).
4) El servidor Web envía entonces una respuesta al navegador Web. Se usa el protocolo HTTP para enviar
la respuesta.
5) El navegador Web procesa la respuesta (normalmente ésta llega en formato HTML y JavaScript) y
renderiza una página para mostrársela al usuario.
6) El usuario puede introducir datos y realizar acciones como pulsar sobre un botón para enviar datos de
regreso al servidor Web para que los procese.
7) Se usa el protocolo HTTP para enviar los datos de regreso al servidor Web, realizando un requerimiento
de tipo POST.
8) El servidor Web procesa el requerimiento POST (otra vez, ejecutando algún código).
9) El servidor Web envía una respuesta al navegador Web mediante el protocolo HTTP.
10) El navegador Web vuelve a procesar la respuesta y muestra una página Web al usuario.
Este proceso se repite una y otra vez durante una sesión típica de navegación por un sitio Web.
1.4. El rol del servidor Web.
Los primeros servidores Web eran responsables de recibir y procesar requerimientos de usuario desde
navegadores a través de HTTP. El servidor Web gestionaba los requerimientos y enviaba una respuesta de
regreso al navegador Web. Hecho esto, el servidor Web entonces cerraba cualquier conexión entre él y el

5
navegador y liberaba todos los recursos involucrados con el requerimiento. Estos recursos eran fácilmente
liberados cuando el servidor Web finalizaba de procesar el requerimiento.
Este tipo de aplicaciones Web eran consideradas sin estado porque los datos no eran conservados por el
servidor Web entre requerimientos y las conexiones no se reutilizaban. Estas aplicaciones normalmente
involucraban simples página HTML y eran por tanto capaces de gestionar miles de requerimientos por
minuto. La siguiente figura muestra un ejemplo de este simple entorno sin estado.
Figura 5

Hoy en día los servidores Web realizan servicios que van más allá de los servidores Web originales. Además
de servir archivos HTML estáticos, los modernos servidores Web también gestionan requerimientos a páginas
que contienen código que es ejecutado sobre el servidor; el servidor Web ejecuta este código ante el
requerimiento y responde con resultados. Estos servidores Web son también capaces de almacenar datos
entre los requerimientos. Esto significa que las páginas Web pueden conectarse mediante un formulario a
aplicaciones Web que comprenden el estado actual de cada requerimiento individual del usuario. Estos
servidores mantienen una conexión abierta con los navegadores durante un periodo de tiempo anticipando
requerimientos de páginas adicionales por parte del mismo usuario. Este tipo de interacción se ilustra en la
siguiente figura.
Figura 6

1.5. El rol del navegador Web.


Para que un navegador Web pueda mostrar los datos que recibe desde un servidor de una forma
independiente de la plataforma se creó un lenguaje estándar para mostrar contenido. Este lenguaje se concreta
en el lenguaje HTML mediante el uso de etiquetas. El lenguaje HTML fue diseñado para ser capaz de
renderizar información sobre algún sistema operativo sin tener que poner restricciones sobre el tamaño de la
ventana. Esto es así porque las páginas Web se consideran independientes de la plataforma. El HTML fue
diseñado para "fluir", para romper el texto si es necesario ajustarlo a los bordes de la ventana del navegador.
El navegador Web también muestra imágenes y responde a enlaces a otras páginas. Cada página Web
requerida al servidor Web provoca que el navegador Web actualice su contenido para mostrar la nueva
información.
Aunque el rol del navegador Web es simplemente presentar información y recolectar datos de los usuarios,
muchas nuevas tecnologías del lado cliente permiten hoy en día a los navegadores Web ejecutar código como
JavaScript y soportar complementos que aumentan la experiencia del usuario. Tecnologías como AJAX

6
permiten a los navegadores Web realizar refrescos parciales de la página comunicándose con el servidor Web.
Estas tecnologías hacen la experiencia del usuario más dinámica e interactiva.

2. El protocolo HTTP
HTTP (Hypertext Transfer Protocol) es un protocolo de comunicaciones basado en texto que es usado para
solicitar páginas Web a un servidor Web y enviar respuestas de retorno al navegador Web. El protocolo
HTTP es el que gestiona la navegación por páginas web a través de Internet, y por tanto será el utilizado por
las aplicaciones Web.
HTTP es un protocolo orientado a transacciones y sigue el esquema solicitud-respuesta entre un cliente y un
servidor. Al cliente que efectúa la solicitud (habitualmente un navegador web) se lo conoce como "user agent"
(agente del usuario). A la información transmitida se la llama recurso y se la identifica mediante un localizador
uniforme de recursos (URL). Los recursos pueden ser archivos, el resultado de la ejecución de un programa,
una consulta a una base de datos, la traducción automática de un documento, etc.
2.1. Transacciones HTTP.
Una transacción HTTP está formada por un encabezado seguido, opcionalmente, por una línea en blanco y
algún dato. El encabezado especificará cosas como la acción requerida del servidor, o el tipo de dato
retornado, o el código de estado.
El uso de campos de encabezados enviados en las transacciones HTTP le da gran flexibilidad al protocolo.
Estos campos permiten que se envíe información descriptiva en la transacción, permitiendo así la
autenticación, cifrado e identificación de usuario.
Si se reciben líneas de encabezado del cliente, el servidor las coloca en variables de entorno, conocidas como
variable CGI, con el prefijo HTTP_ seguido del nombre del encabezado. Cualquier carácter guion "-" del
nombre del encabezado se convierte a caracteres "_".Ejemplos de estas variables CGI son HTTP_ACCEPT y
HTTP_USER_AGENT:
• La cabecera HTTP_ACCEPT especifica los tipos de contenido (llamados también tipos MIME)de respuesta
que el cliente aceptará, dados los encabezados HTTP. Los elementos de esta lista deben estar separados por
una coma.
• La cabecera HTTP_USER_AGENT especifica el navegador que utiliza el cliente para realizar la solicitud. El
formato general para esta variable es: software/versión biblioteca/versión.
2.1.1. Uso de URLs.
El punto de partida para el inicio de una transacción HTTP es la especificación de una URL (Uniform Resource
Locutor) por parte de la aplicación cliente. Una URL es una cadena de texto que especifica generalmente un
protocolo de red (http o https para solicitudes web), un dominio y la información del recurso solicitado. El
formato general es el siguiente:
protocolo://dominio/informacion_de_recurso
Los protocolos más utilizados son:
• http: para realizar navegaciones por un sitio web. Por ejemplo, para acceder a la página inicial del sitio web
de Oracle usaríamos.
http://www.oracle.es
• https: para realizar navegaciones mediante un protocolo seguro por un sitio web. Por ejemplo, las
entidades bancarias utilizan habitualmente seguridad SSL para acceder a sus servicios. En estos casos
usaríamos:
https://www.entidadbancaria.es/cuentas
• ftp: para acceder al sistema de ficheros del servidor. Algunos sitios web permiten compartir ficheros
hospedados en alguna de sus carpetas. Por ejemplo, para acceder a un sitio de publicación de aplicaciones
Linux podemos usar:
ftp://sunsite.unc.edu/
• mailto: para acceder al servicio de correo electrónico. Por ejemplo, podemos crear un mensaje de correo
electrónico de la siguiente forma.
mailto:[email protected]?subject=asunto&cc=destinatario&body=mensaje
• file: para navegar por el sistema de fichero local. Por ejemplo, si queremos acceder a la carpeta raíz de la
unidad C: usaríamos:
file:///C:/

7
Para los protocolos http y https, el dominio es aquella parte de la URL que identifica el sitio web al que
queremos hacer una solicitud. Existen dos formatos para especificar el dominio:
• Un formato donde se indica un nombre de host o dirección IP y un puerto.
Cada ordenador que comparte algún recurso dentro de una red (lo que se denomina un host) tiene asignado
un número que lo identifica de manera lógica y jerárquica. Este número se denomina dirección IP y está
formado por 4 segmentos. Por ejemplo, la dirección 127.0.0.1 está reservada para identificar el sistema
local. Como alterativa a usar la dirección IP la mayoría de sistemas operativos permiten un nombre de host
más comprensible. Por ejemplo, el nombre localhost es equivalente a la IP 127.0.0.1
Además, dentro de cada host pueden ejecutarse varias aplicaciones servidoras accesibles a través de la red.
Cada host identifica sus aplicaciones servidoras mediante otro número denominado puerto. La mayoría de
aplicaciones servidoras comerciales utilizan un puerto fijo; por ejemplo, el servidor de base de datos de
Oracle utiliza habitualmente el puerto 1521. Por tanto, para especificar un dominio en una URL se utilizará
una sintaxis como:
http://124.23.0.1:1521
• Un formato que especifica un nombre de dominio, como por ejemplo http://www.oracle.es.
Para hacer más amigables las URLs, el protocolo HTTP permite especificar un nombre de dominio en vez
de una IP y puerto. El nombre de dominio queda registrado en un Sistema de Dominio de Nombres
(DNS), donde está asociado con su host y puerto. El protocolo HTTP hace transparente el uso de DNS, y
de esa forma es habitual nombres de dominio para navegar por Internet.
La última parte de una URL especifica el recurso solicitado e información adicional. La información del
recurso consta de las siguientes partes:
• Una ruta, la cual identifica al recurso dentro de la organización física o virtual del sitio web. Por ejemplo,
la ruta "informes/ventas.html" puede hacer referencia a un fichero llamado "ventas.html" ubicado dentro del
directorio "informes". Pero como veremos en unidades posteriores, esta ruta puede estar asociada a una
aplicación y no a un fichero. Si no se especifica la ruta, habitualmente los sitios web definen una ruta o
página por defecto.
• Una cadena de consulta, que especifica datos adicionales. La información de esta cadena está estructurada
normalmente con pares "clave=valor". Se utiliza la cadena de consulta como una técnica sencilla para pasar
datos adicionales con la solicitud de un recurso. Por ejemplo, si queremos invocar la página de búsqueda de
google para buscar por el término "Oracle" utilizaríamos la siguiente URL:
https://www.google.es/?q=Oracle
El comienzo de la cadena de consulta se inicia siempre con el caracter '?'.
2.1.2. Uso de URNs.
Un Nombre de Recurso Uniforme, o URN, es un identificador único para un recurso dentro de la red, y se
puede usar en vez de una URL. Pero a diferencia de una URL, no especifica cómo acceder a él.
Existe un estándar oficial para mantener identificadores únicos para cada recurso. Un ejemplo de recurso
identificado por una URN esISBN:1-930110-59-6.
Actualmente no se suelen utilizar URNs.
2.1.3. Uso de URIs.
Un Identificador de Recursos Uniforme o URI es una cadena de caracteres que identifica los recursos de una
red de forma unívoca. La diferencia respecto a una URL que estas últimas hacen referencia a recursos que, de
forma general, pueden variar en el tiempo.
Normalmente estos recursos especificados en una URI son accesibles en una red o sistema. Las URIs pueden
ser localizador de recursos uniforme (URL), un nombre de recursos uniforme (URN), o ambos (URL+URN).
La sintaxis de una URI es la misma que la de una URL, con la diferencia de que permite añadir a la
información del recurso una especificación de fragmento:
• Un fragmento permite identificar una parte del recurso solicitado. Se suele utilizar con páginas HTML
para que el navegador muestre una parte específica de la página. El comienzo de estaparte se indica
mediante el carácter '#'. Por ejemplo, si en una página llamada "informe.html" definimos un elemento con
el identificador "ventas", podemos utilizar la siguiente URL:
http://dominio/informe.htm#ventas
2.2. Solicitudes HTTP.
Cuando un navegador hace un requerimiento o solicitud se envía un mensaje HTTP al servidor, cuya primera
línea tiene la siguiente estructura:

8
▪ Un nombre de método (GET, HEAD, POST, PUT, DELETE, OPTIONS o TRACE).
▪ La URI local del recurso requerido.
▪ La versión HTTP usada.
Un ejemplo de cabecera de requerimiento es:
GET /informes/saldos/index.html HTTP/1.0
2.2.1. Anatomía de una solicitud HHTP GET.
Una solicitud mediante el método GET añade la ruta del recurso y cualquier parámetro a la URL en la línea de
solicitud.
Para una URL como "http://www.dominio.com/informes/saldos/index.html?nombre1=valor1&nombre2=valor2",
la estructura de llamada puede ser como la siguiente:
GET /informes/saldos/index.html?nombre1=valor1&nombre2=valor2 HTTP/1.1 < línea de solicitud
Host: www.dominio.com < cabeceras …
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4) Gecko/ <
20030624 Netscape/7.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain; <
q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Language: en-us,en;q=0.5 <
Accept-Encoding: gzip,deflate <
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 <
Keep-Alive: 300 <
Connection: keep-alive <
En la URL de ejemplo, nombre1 y nombre2 son los parámetros y, valor1 y valor2 son los valores de dichos
parámetros. Se usa para requerir recursos pasivos (como páginas HTML) o activos (como páginas JSP). La
longitud total de la línea de solicitud no debe exceder de 255 caracteres.
2.2.2. Anatomía de una solicitud HHTP POST.
Las solicitudes con el método POST están diseñadas para ser usadas por navegadores que necesitan realizar
una petición más compleja sobre el servidor. Por ejemplo, si un usuario completa un formulario, la aplicación
puede querer enviar todos los datos introducidos al servidor para que los almacene en una base de datos.
Mediante HTTP POST los datos son enviados al servidor en el cuerpo del mensaje (o payload), y pueden ser
tan largos como se precise.
Para una URL como "http://www.dominio.com/informes/saldos/index.html?nombre1=valor1&nombre2=valor2",
la estructura de llamada puede ser como la siguiente:
POST /informes/saldos/index.html HTTP/1.1 < línea de solicitud
Host: www.dominio.com < cabeceras
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4) Gecko/ <
20030624 Netscape/7.1
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain; <
q=0.8,video/x-mng,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1
Accept-Language: en-us,en;q=0.5 <
Accept-Encoding: gzip,deflate <
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 <
Keep-Alive: 300 <
Connection: keep-alive <
< línea en blanco
nombre1=valor1&nombre2=valor2 < cuerpo mensaje
2.2.3. Otros métodos de solicitud HTTP.
Hay varios métodos de solicitud definidos en HTPP. La siguiente tabla describe los métodos HTTP más
comunes.
OPTIONS Es usado por aplicaciones clientes para solicitar una lista de comandos soportados. De
este modo podemos comprobar si un servidor permite un determinado comando antes de
ocupar el ancho de banda intentando enviar una solicitud no soportada.

9
GET Obtiene una URL desde el servidor. Una solicitud GET para una URL específica, como
/test.htm, recupera el archivo test.htm. Los datos recuperados usando este comando son
normalmente guardados en caché por el navegador. GET también trabaja con colecciones,
como directorios que contienen colecciones de archivos. Si se solicita un directorio, el
servidor puede ser configurado para retornar un archivo por defecto, como index.html,
que puede representar al directorio.
HEAD Recupera la meta información de un recurso. Esta información es normalmente idéntica a
la meta información enviada en una respuesta de solicitud GET, pero el comando HEAD
nunca retorna el recurso actual. La meta información es guardada en caché.
POST Envía datos para que sean procesados por el servidor Web. Esto es normalmente el
resultado de que el usuario introduzca datos en un formulario y los envíe como parte de su
solicitud.
PUT Permite a los clientes crear directamente un recurso, indicado en la URL, sobre el servidor.
El servidor mira el cuerpo de la solicitud, crea el fichero especificado en la URL, y copia
los datos recibidos en el nuevo fichero. Si el fichero existe y no está bloqueado, el
contenido del fichero será rescrito.
DELETE Se usa para eliminar un recurso del servidor Web. Requiere permisos de escritura sobre el
directorio.
TRACE Se usa para testar o diagnosticar; permite al cliente ver que está siendo recibido al otro
final de la cadena de solicitud. Las respuesta de este método nunca son guardadas en
caché.
CONNECT Reservado para usos con un proxy que puede dinámicamente actuar de pasarela, como el
protocolo Secure Sockets Layer (SSL).
DEBUG No definido en la especificación HTTP/1.1, pero usado para comenzar la depuración
ASP.NET. Este método informa a Visual Studio de los procesos supervisados por el
depurador.
2.3. Respuestas HTTP.
Cuando un servidor Web responde a una petición de un navegador u otro cliente Web, la respuesta consiste
típicamente en una línea de estado, algunas cabeceras de respuesta, una línea en blanco, y un mensaje
opcional. Aquí tenemos un ejemplo mínimo con la línea de estado por defecto:
HTTP/1.1 200 OK < Línea de estado
Set-Cookie: JSESSIONID=0AAB6C8DE415E2E5F307CF334BFCA0C1 < Cookie de sesión
Content-Type: text/html < Cabeceras …
Content-Lenght: 36 <
< Línea en blanco
<html><body>Hello World</body></html> < Cuerpo mensaje
La línea de estado consiste en la versión HTTP, un entero que se interpreta como código de estado, y un
mensaje muy corto que corresponde con el código de estado.Los códigos de estado son tres dígitos
agrupados tal como describe a continuación:
1xx Información: solitud recibida, procesando.
2xx Éxito: la acción fue recibida con éxito, atendida y aceptada.
3xx Comando de redirección: una acción remota debe ser realizada para completar la solicitud.
4xx Error del cliente: la solicitud tiene un error de sintaxis o el servidor no sabe cómo completar la
solicitud.
5xx Error del servidor: el servidor falló al completar una solicitud que parece ser válida.
Además de grupos de códigos de estado, HTTP/1.1 define códigos de estado únicos y sus razones. Una
razón no es nada más que breve descripción del código de estado.
La siguiente tabla muestra los códigos de estado comunes y sus razones.
100 Continue
200 OK
201 Created
300 Multiple Choices

10
301 Moved Permanently
302 Found
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
407 Proxy Authentication Required
408 Request Time-out
413 Request Entity Too Large
500 Internal Server Error
501 Not Implemented
El texto de las razones puede ser modificado sin romper el protocolo. Tal como se verá, para especificar
códigos de estado, en los servlets se usa el método setStatus(código), donde como argumento puede usarse
una de las constantes SC_* predefinidas en la claseHttpServletResponse.
La línea de cabecera Content-Type indica el tipo de recurso que será enviado al navegador Web como parte de
la respuesta. En el ejemplo, Content-Type: text/html, indica que se envía un archivo de texto HTML estático.
Un navegador puede administrar varios tipos de archivos, incluyendo documentos PDF, documentos Word,
animaciones de Flash, etc.; y mostrarlos directamente en el navegador. Para ello debemos transmitir al
navegador el tipo MIME apropiado que describa el contenido del flujo de respuesta. El tipo MIME es una
descripción estandarizada de contenidos, independiente de la plataforma, similar al de las extensiones de
archivos bajo Windows.
La cabecera Content-Length establece la longitud del cuerpo. De esta manera el navegador sabrá hasta dónde
tiene que leer contenido para renderizarlo.
2.4.¿Qué determina el método de solicitud HTTP?
Un navegador realiza una petición HTTP GET en los siguientes casos:
• Cuando se introduce directamente una URL en la barra de direcciones del navegador. Por ejemplo:
http://www.google.es/
• Cuando se pulsa un enlace creado con la etiqueta HTML <a />. Por ejemplo:
<a href=" http://www.google.es">Página de búsqueda</a>
• Cuando se envían datos mediante un formulario que no tiene asignado el atributo method, o lo tiene
asignado al valor GET. Por ejemplo:
<form action="http://www.google.es" method="GET">
Texto a buscar: <input type="text" name="q" />
<input type="submit" value="buscar" />
</form>
Los formularios deben incluir habitualmente un botón de posteo. Cuando se pulsa este botón se realiza una
solicitud a dirección indicada en el atributo action de la etiqueta <form />.
Nota. Los formularios utilizan por defecto el método de solicitud HTTP GET.
Un navegador realiza una petición HTTP POST en los siguientes casos:
• Cuando se envían datos mediante un formulario que tiene asignado el atributo method al valor POST. Por
ejemplo:
<form action="http://www.gestionempleado.es" method="POST">
Nombe empleado: <input type="text" name="nombre" />
<input type="submit" value="enviar" />
</form>
Otros métodos HTTP se aplican cuando se realizan solicitudes mediante código se servidor o código script.
Veremos cómo hacer esto más adelante.

3. Aplicación de servidor web para Java EE


La tecnología Java EE para Web es soportada por varias aplicaciones servidoras de Web como Apache
Tomcat, Glassfish, JBossy otras. En este curso nos centraremos en el desarrollo de aplicaciones web para
Apache Tomcat, aunque las técnicas y conceptos desarrollados son aplicables para cualquier otro servidor.

11
3.1. Instalar y configurar Apache Tomcat.
Es necesario tener instalado un servidor web como «Apache Tomcat» para poder ejecutar los servlets y
paginas JSPde Java. Tomcat fue desarrollado para servir páginas HTML, páginas JSP y Servlets.
3.1.1. Instalación de Apache Tomcat.
Podemos descargar una versión de Apache Tomcat desde la página oficial de Oracle:
http://tomcat.apache.org/index.html
Una vez bajado los archivos de instalación se pueden ejecutar para instalar Tomcat en nuestro sistema.
Los servlets y páginas JSP son clases de Java, así que para procesarlos Tomcat requiere de una máquina virtual
de Java. Para indicarle a Tomcat dónde se ubica el SDK se deben definir dos variables de entorno en el
sistema operativo:
JAVA_HOME = <debe contener la ruta dela carpeta raíz del JDK>
JRE_HOME = <debe contener la ruta dela carpeta raíz del JRE>
Para los sistemas Windows, también es aconsejable indicar en la variable de entorno PAHT la ruta de los
binarios de Tomcat (<ruta_instalación_Tomcat>\bin). Por defecto, en Windows, «Apache Tomcat» se instala
en la carpeta:
C:\Program Files\Apache Software Foundation\Apache Tomcat
Dependiendo de la versión instalada, se arranca Tomcat con el comando "startup.bat" (para Windows) o
"startup.sh" (para Unix). Se detiene el servidor de Tomcat con el comando "shutdown.bat" (para Windows) o
"shutdown.sh" (para Unix). Si no existen estos archivos deberemos usar directamente los ejecutables
disponibles de la subcarpeta \bin.
Tomcat utiliza por defecto el puerto 8080 para sus conexiones y reserva la subcarpeta "webapps/ROOT/"
dentro de su carpeta de instalación como carpeta raíz por defecto para ubicar las aplicaciones o sitios Web.
Para comprobar que el servidor de Tomcat está funcionando podemos escribir la dirección
http://localhost:8080/ en un navegador. Debe mostrarse una página de inicio con información sobre Tomcat.
Figura 7

3.1.2. Instalar Tomcat con NetBeans.


El archivo de instalación de NetBeans también incluye los servidores Glassfish y Tomcat. Sin embargo, las
opciones de instalación por defecto sólo instalan el servidor Glassfish. Para instalar Tomcat debemos ejecutar
el instalador de NetBeans y en su primera pantalla pulsar el botón «Personalizar/Customize» y a continuación
seleccionar la opción «Apache Tomcat» y continuar con la instalación.

12
Figura 8

Si NetBeans no detecta de forma automática una instalación de Tomcat, podemos configurarla a mano. Los
pasos a seguir son los siguientes:
1) En el panel de «Prestaciones/Services» debemos añadir un nuevo servidor al nodo «Servidores».
Haciendo clic con el botón secundario del ratón sobre este nodo aparece la opción de añadir un nuevo
servidor. En el cuadro de diálogo «Añadir un servidor» hay que seleccionar el tipo de servidor, en este caso
«Apache Tomcat».

13
Figura 9

2) Tras pulsar el botón «Siguiente», debemos ahora indicar la localización de la carpeta de la instancia de
Tomcat y las credenciales de un administrador de Tomcat. Se pueden crear nuevas credenciales desde este
asistente, pero para ello es necesario que el usuario actual del sistema operativo tenga permisos para
modificar los ficheros de configuración ubicados en la carpeta de instalación de Tomcat.
Figura 10

3) Tras pulsar el botón «Finalizar» aparecerá desplegado en el nodo «Servidores» el nuevo servidor.
Podemos editar sus propiedades, por ejemplo, para cambiar el puerto de conexión.

14
Figura 11

Si tenemos instalado más de un servidor web podemos seleccionarlo al crear cada nuevo proyecto Web.
3.2. Estructura de carpetas de una aplicación web Java EE.
Para publicar una aplicación Web en Tomcat, podemos ubicar todos sus ficheros en el directorio
"webapps/ROOT/", o bien debemos crear un carpeta específica dentro del directorio "webapps".
Los proyectos Web que admite Tomcat deben corresponderse con una estructura bien definida. Esta
estructura se muestra a continuación:
Figura 12

Podemos ubicar nuestras páginas web, por ejemplo, en la capeta raíz "MisitoWeb1". Si creamos una página con
nombre "index.html", se mostrará automáticamente si en un navegador introducimos la dirección:
http://localhost:8080/MiSitioWeb1
donde localhost indica la conexión al propio ordenador, siendo sustituido normalmente por la dirección URL
del servidor web.
Todas las clases compiladas de Java (incluyendo a servlets y filtros) deben meterse en la carpeta "classes".
Mientras que todas la librerías (archivos con extensión jar) deben meterse en la subcarpeta "lib".
El archivo de configuración "web.xml" siempre debe ir directamente en la carpeta "WEB-INF".
3.3. Archivos WAR.
Los archivos y recursos de una aplicación web pueden ser empaquetados dentro de un archivo JAR, a
diferencia que el archivo debe tener la extensión .war en lugar de .jar. La extensión .war viene de las siglas
«Web ARchive», y significa que el archivo deberá ser tratado de diferente manera que un archivo JAR. El
contenedor de servlets del servidor Web puede instalar el archivo WAR de una aplicación web sin una
intervención manual.
15
Dentro del archivo WAR, se crea un directorio llamado META-INF. Dicho directorio contiene el archivo
MANIFEST.MF, el cual permite declarar dependencias con librerías y clases. Es decir, brinda un testeo en
tiempo de despliegue (deploy-time) para que el contenedor pueda verificar las librerías y clases que se necesitan
para ejecutar la aplicación web correctamente. Esto permite que no sea necesario esperar hasta que un recurso
sea solicitado, para verificar que existan todos los componentes de los que depende la aplicación.
El archivo WAR puede sustituir al directorio raíz de la aplicación web si lo ubicamos directamente dentro de
"webapps". Para crear un archivo WAR de la aplicación MiSitioWeb1:
1) Abriremos una consola y nos desplazaremos a la carpeta webapps.
2) Usaremos la utilidad jar para comprimir la carpeta:
jar –cvf MiSitioWeb1.war MiSitioWeb1
3.4. Cómo crear una aplicación web con NetBeans.
El entorno de desarrollo NetBeans ofrece un conjunto de herramientas comprensible para desarrollar
aplicaciones web aplicando la estructura de las aplicaciones Web Java EE.
El primer paso es crear una nueva aplicación usando el menú «Archivo|Nuevo proyecto». En el cuadro de
diálogo «Nuevo Proyecto» hay que seleccionar la categoría «Java Web» y la plantilla «Aplicación Web».
Figura 13

Tras pulsar el botón «Siguiente» hay que asignar un nombre al proyecto (para este ejemplo "WebAplicacion"), y
la ubicación de mismo ("E:\Test").

16
Figura 14

Tras pulsar «Siguiente», a continuación se puede seleccionar el servidor sobre el que se piensa ejecutar la
aplicación Web (para este ejemplo Apache Tomcat 8), y la versión de Java EE.
Figura 15

Tras pulsar «Siguiente» el asistente permite seleccionar varios marcos de trabajo para desarrollar la aplicación
web. De momento no se seleccionará ninguna.

17
Figura 16

Por último, tras pulsar «Finalizar», se añadirá el proyecto Web al panel de «Proyectos».
Figura 17

Los proyectos Web de NetBeans mantienen una estructura lógica que se corresponde con la estructura física
de las aplicaciones Web Java EE. A continuación se comenta la correspondencia:
Nodos del proyecto Ubicación en la aplicación Web Java EE
Web Pages Se corresponde con la carpeta raíz del proyecto Web. En este nodo se deben crear
las páginas HTML y JSP, así como cualquier otro tipo de recurso estático,
pudiéndose organizar estos en subcarpetas.
META-INF Este nodo genera la carpeta "META-INF" de la aplicación. Contiene el archivo de
contexto de la aplicación: "context.xml"
WEB-INF Este nodo genera la carpeta "WEB-INF" de la aplicación. Contiene el archivo de
configuración de la aplicación: "web.xml"
En este nodo también se pueden añadir páginas JSP y otros recursos privados de
la aplicación, tales librerías de etiquetas.
Source packages En este nodo se crean los archivos fuente de los servlets, filtros y clases de Java
organizados por paquetes. Se corresponde con la carpeta "WEB-INF/classes" de la
aplicación.

18
Libraries En este nodo se añaden las librerías y ficheros JAR que queremos referenciar en la
aplicación. Se corresponde con la carpeta "WEB-INF/lib" de la aplicación.
Configuration Files Este nodo de utilidad agrupa los diversos ficheros de configuración de la
aplicación. Es conveniente no editarlos directamente desde este nodo.
Como se puede ver, el nuevo proyecto añade una página HTML, llamada index.html, a la aplicación; esta
página es el recurso por defecto de la aplicación. Pero por defecto no se añade el fichero web.xml. Para añadir
este fichero hay que agregar un nuevo fichero para acceder al cuadro de diálogo «Nuevo fichero». En la
categoría «Web» se puede encontrar la plantilla «Standard Deployment Descriptor (web.xml)». Tras aceptar se
agrega el fichero al proyecto, tal como se muestra a continuación:
Figura 18

Se puede probar el nuevo proyecto Web de la misma manera que se prueba una aplicación de escritorio,
ejecutando el proyecto con el icono «Ejecutar proyecto», pulsando la tecla F6 o pulsando el menú
«Ejecutar|Ejecutar proyecto». NetBeans también permite seleccionar el tipo de navegador desde el cual se
harán las solicitudes a la aplicación web.
Como resultado de ejecutar la aplicación se arrancará el servidor Web (en este caso Tomcat) si es que no
estaba en ejecución, y se desplegará la aplicación sobre el servidor. También se abrirá el navegador web
seleccionado con la página por defecto de nuestra aplicación.
Figura 19

Podemos modificar el proyecto sin tener que volver a ejecutarlo. NetBeans se encargará de trasladar cualquier
cambio al servidor Web en cuanto modifiquemos un fichero y guardemos los cambios. Por ejemplo,
manteniendo abierto el navegador, podemos cambiar el título de la página Web y el formato de su contenido:

19
Figura 20

Ahora basta con refrescar el navegador web para que se reflejen los cambios:
Figura 21

3.5. El archivo descriptor «web.xml».


El archivo web.xml, denominado oficialmente archivo descriptor (Deployment Descriptor o DD), es un
archivo de configuración usado por el contenedor de servlets para obtener información sobre nuestra
aplicación web.
3.5.1. Contenido del archivo descriptor.
El archivo descriptor que se crea cuando se añade a un proyecto web en NetBeans contiene lo siguiente:
Figura 22

El archivo descriptor utiliza el formato XML, expresando toda su información mediante etiquetas. Usa una
etiqueta raíz llamada <web-app>, dentro de la cual se expresa cualquier información dentro de una etiqueta
significativa. Inicialmente sólo se establece el tiempo de invalidación de las sesiones en 30 minutos.
Dentro del nodo raíz <web-app>se pueden declarar las siguientes secciones:

20
Sección Etiqueta xml Significado
Servlet Declarations <servlet> Especifica las propiedades de los servlets.
Servlet Mappings <servltet-mapping> Especifica la URL para mapear un servlet.
Application Lifecycle <listener> Especifica clases observadoras de eventos de la
Listener classes aplicación.
ServletContext Init <context-param> Especifica parámetros para la aplicación web.
Parameters
Error Pages <error-page> Especifica páginas para re direccionar automáticamente
condiciones de error.
Session Configuration <session-config> Especifica el tiempo de expiración de la sesión.
Security Constraints <security-constraint> Especifica condiciones de seguridad para la aplicación
web.
Tag libraries <taglib> Especifica los identificadores de las librerías usadas por
las páginas JSP.
Welcome File list <welcome-file-list> Especifica los archivos de saludo para la aplicación web.
Filter Definitions <filter> Especifican filtros.
Filter Mapping
MIME Type Mappings <mime-mapping> Especifica los tipos MIME para extensiones de archivos.
JNDI names <ejb-ref> Especifica los nombres JNDI de los EJBs.
JSP Config <jsp-config> Especifica configuraciones para grupos de páginas JSP.
En posteriores unidades se ampliarán las explicaciones sobre estas secciones. NetBeans ofrece un editor
especializado para el archivo descriptor. Este editor ofrece varias pestañas donde se agrupan las
configuraciones por categorías. En la categoría «General» podemos escribir información sobre nuestra
aplicación y cambiar el tiempo de invalidación de sesión, entre otras configuraciones generales.
Figura 23

3.5.2. Especificación de páginas por defecto (o welcome files ).


A través del archivo web.xml se puede especificar la lista de recursos por defecto de una aplicación web. Los
recursos por defecto se tienen en cuenta cuando en la URL de un navegador se especifica un final de carpeta.
Por ejemplo:
http://localhost:8084/WebAplicacion/
http://localhost:8084/WebAplicacion/informes/
El servidor web utiliza la lista de recursos por defecto para buscar el primer recurso que se corresponda con
uno de los indicados en la lista. Es decir, el servidor devolverá el primer archivo de la lista que exista en el
directorio especificado en la URL.
Se deben tener en cuenta los siguientes puntos al especificar la lista de recursos por defecto:
• No se puede utilizar una barra delante del nombre del archivo incluido en la lista.

21
• Todos los archivos de la lista definidos con (<welcome-file>) deben estar dentro de un único
elemento<welcome-file-list>, es decir, tienen que estar dentro de la misma lista.
El siguiente trozo de código es un ejemplo típico de páginas por defecto:
<web-app ...>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Con el editor de NetBeans para el fichero web.xml podemos editar esta lista en la categoría «Páginas»:
Figura 24

4. Fundamentos de HTML5
HTML ha sido el lenguaje de publicación de páginas web desde 1992. En este capítulo veremos los
fundamentos de HTML5 (la última especificación de este lenguaje), cómo se estructuran las páginas HTML y
algunas de las características básicas que podemos añadir a una página HTML.
4.1. La estructura de una página HTML.
HTML es el acrónimo de Hyper Text Markup Language. Es un lenguaje estático que determina la estructura y
significado semántico de una página web. Se usa HTML para crear contenido y metadatos que el navegador
usa para renderizar y mostrar información. El contenido HTML incluye texto, imágenes, audio, vídeo,
formularios, listas, tablas y muchos otros elementos. Una página HTML puede también contener híper-
enlaces, los cuales conectan con otras páginas y otros sitios web.
Toda página HTML tiene la misma estructura básica:
• Una declaración DOCTYPEque define la versión HTML que usa la página.
• Una sección<html>que contiene los siguientes elementos:
- Una cabecera (elemento <header>) que contiene información sobre la página para los navegadores.
Esto puede incluir el lenguaje principal (Inglés, Chino, Francés, y demás), el juego de caracteres, hojas de
estilo y ficheros scriptasociados, información del autor, y palabras clave para los motores de búsqueda.
- Un cuerpo (elemento <body>) que contiene todo lo visible de la página.
Un ejemplo básico del código de una página HTML 5 es el siguiente:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Página con la estructura mínima</title>
</head>
<body>
Contenido de la página
</body>
</html>
4.2. Etiquetas, elementos, atributos y contenido.
La cabeza y cuerpo de una página web usan ambos elementos HTML para definir su estructura y contenido.
Por ejemplo, un elemento paragraph, que representa un párrafo de texto en una página, consiste de:
22
• Una etiqueta de apertura, <p>, para denotar que inicio del párrafo.
• El contenido de texto.
• Una etiqueta de cierre, </p>, para denotar el final del párrafo.
A veces se habla indistintamente de etiquetas o elementos, como si fuesen lo mismo, pero esto es incorrecto.
Un elemento consiste de etiquetas y contenido.
Se anidan elementos dentro de otros para obtener mayor información semántica acerca del contenido. Si no
es obvio por el contexto, elementos anidados ayudan a mantener un registro de cuáles son los padres y cuáles
son elementos hijos.
4.2.1. El cuerpo de un documento sencillo.
El siguiente código HTML muestra un contenido sencillo del cuerpo de una página:
<body>
<h1 class="blue">Una introducción a elementos, etiquetas y contenido</h1>
<p>
Los <strong>elementos</strong> consisten de <strong>contenido</strong> encapsulado entre
una etiqueta de <em>comienzo</em> y una etiqueta de <em>cierre</em>.
</p>
<hr />
<p>
Ciertos elementos, como el elemento de la línea horizontal, no necesitan contenido;
consisten de un elemento auto-cerrado. Se conocen como elementos vacíos.
</p>
</body>
Cada elemento HTML le dice al navegador algo acerca de lo que se encuentra entre la etiqueta de comienzo y
la de cierre. Por ejemplo, los elementos strong yem representan un contenido de "fuerte importancia" y de
"énfasis", por lo cual los navegadores los renderizan respectivamente en negritas y en itálicas. Los elementos
h1 representas una cabecera de nivel más alto en nuestro documento, las cuales son renderizadas por los
navegadores con un texto grande y en negritas.
Los atributos proporcionan información adicional, representacional o semántica, sobre el contenido de los
elementos. Aparecen dentro de la etiqueta de comienzo de un elemento y constan de un nombre y de un
valor. El nombre debería estar con minúsculas. Muchos valores de atributos son predefinidos y deberían estar
contenidos entre comillas simples o dobles. En el ejemplo previo, la etiqueta h1 contiene el atributo class
asignado al valor blue.
4.2.2. Estructura del documento en HTML5.
HTML5 incluye nuevos elementos que permiten marcar nuestro contenido y presentar una estructura mejor
para los documentos, comparado con las versiones previas.
Una de las tareas comunes de una página es identificar áreas del documento: la barra de navegación, la
cabecera, el pie de página, y demás.
En HTML4 se usaba el atributo id para diferenciar áreas.Por ejemplo:
<ul id="navigation"> ... </ul>
<div id="footer> ... </div>
Aunque este método es válido, no concede un significado semántico a las diversas áreas. HTML5
proporciona una estructura semántica más rica para documentos, incluyendo los siguientes elementos:
Elemento Descripción
<section> Identifica piezas de contenido de una página. Por ejemplo, los ingredientes y los métodos de
un recipiente mostrado en una página pueden tener dos secciones separadas.
<header> Identifica el contenido de la cabecera de una página. Por ejemplo, el sitio web de una
compañía puede incluir un logotipo, nombre y lema en la cabecera.
<footer> Identifica el contenido del pie de página. Por ejemplo, enlaces al sitio web, instrucciones de
privacidad, o términos y condiciones suelen ser incluidos en el pie de página.
<nav> Identifica el contenido para una sección de navegación principal de la página. Los
programadores suelen usar este elemento para implementar un menú de navegación a través
del sitio web.
<article> Identifica contenido independiente que tendría sentido fuera del contexto de la página
actual. Por ejemplo, un blog, una receta, o una entrada de catálogo.
<aside> Identifica contenido relacionado en un <article>que no es parte de su flujo. Por ejemplo,
podemos usar <aside> para identificar puntos o contenido adicional.
23
El siguiente ejemplo de marcado muestra una forma de marcar un documento HTML5 usando los nuevos
elementos estructurales:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Mis mejores recetas</title>
</head>
<body>
<nav>
<a href="/">Inicio</a>
</nav>
<header>
<h1>Mis mejores recetas</h1>
<p>Mis recetas favoritas</p>
</header>
<article>
<h1>Tostada de alubias</h1>
<section>
<h1>Ingredientes</h1>
<ul>
<li>Alubias</li>
<li>Pan</li>
</ul>
</section>
<section>
<h1>Método</h1>
<ol>
<li>Tostar el pan</li>
<li>Cocer las alubias</li>
<li>Poner las alubias sobre el pan</li>
</ol>
</section>
</article>
<footer>
<small>Última actualización <time datetime="2012-08-12">12 de Agosto de 2012</time></small>
</footer>
</body>
</html>
Es importante tener en cuenta que no hay un orden preescrito en el cual se usan estos elementos ni
proporcionan ningún formato de visualización por defecto. Por ejemplo, puedes decidir incluir tus enlaces de
navegación en el encabezado, pie de página o barra lateral. Similarmente se puede dividir la página dentro de
secciones semánticas (como Desayunos, Almuerzos y Cenas) e incluir elementos <article>dentro de estas
secciones. Lo importante es entender el propósito de cada elemento nuevo y utilizarlo adecuadamente.
Nota:Nótese que en el ejemplo previo hay tres elementos <h1> en la página, en vez de uno solo como
era esperado. En versiones previas, esto debería ser una semántica incorrecta, pero los elementos
<section> y<article> definidos en HTML5 pueden reiniciar la numeración de títulos.El algoritmo de
esquematización de HTML5 que define esto y el uso de <hgroup> se verá a continuación.
4.3. Mostrando texto en HTML.
La mayoría de páginas web requieren de contenido de texto e imágenes. HTML define muchos elementos
que permiten estructurar el contenido y aportan alguna semántica de contexto.
En la siguiente tabla se resumen las etiquetas relacionadas con la renderización de contenido de texto en
títulos y párrafos:
Elemento Descripción
<p> Identifica un párrafo de texto.
<br /> Provoca un salto de línea dentro de un párrafo.

24
<h1> a <h6> Identifican seis niveles de texto de título. Se usa <h1> para identificar el título de toda
la página, <h2> para identificar el título de cada sección de la página, <h3> para
identificar sub-secciones, y así hasta <h6>.
<hgroup> Agrupa contenido debería ser tratado como un título simple. Este elemento puede
contener etiquetas de título <h1> a <h6>.
<hgroup>
<h1>Mis recetas</h1>
<h2>Bueno para comer, fácil para hacer</h2>
</hgroup>
Otros elementos que dan un significado semántico al contenido de texto son:
Elemento Descripción
<time> Permite definir un valor de fecha, duración o periodo no ambiguo, legible tanto por
humanos como por la máquina. El atributo datetime contiene la representación
estándar ISO del contenido del elemento.
<time datetime="2012-08-08">Hoy</time>
<time datetime="2012-08-08T09:00:00-0500">9am hoy en New York</time>
<time>4h</time>
<time>2012</time>
<mark> Identifica que el contenido debe ser tratado como texto marcado o resaltado para
propósitos de referencia.
<p>Este texto debería ser <mark>marcado para su uso futuro</mark>
en vez de ser <em>enfatizado</em>.</p>
<small> Identifica que el contenido debe ser tratado como texto de comentarios, como la letra
pequeña o atribuciones de autor.
<p>Come tus tostada durante cinco minutos. <small>
O hasta que no puedas más.</small></p>
Es importante usar las etiquetas de párrafo y título para identificar secciones, sub-secciones y el contexto de
un texto en la página web. Los títulos y etiquetas hacen que el contenido sea más comprensible para lectores e
indexadores, y más fácil de leer sobre la pantalla.
Nota. Cuando se escribe marcado HTML, cualquier secuencia de espacios en blanco, tabuladores y
retornos de carro dentro del texto es tratado como un único espacio en blanco. La única excepción es
cuando la secuencia de texto está dentro de un elemento <pre>, que le dice a los navegadores que
rendericen todos los espacios.
HTML también define cuatro elementos para denotar un cambio de énfasis en el texto:
Elemento Descripción
<strong> Indica que el texto es más importante que el que le circunda. Los navegadores lo
renderizan normalmente poniéndolo en negritas.
<em> Identifica texto que necesita ser tensionado. Los navegadores normalmente lo
renderizan con itálicas.
<b> Identifican texto que debe ser renderizado en negritas.
<i> Identifican texto que debe ser renderizado en itálicas.
Se pueden combinar y anidar los elementos <strong>, <em>, <b>, y<i> para indicar diferentes tipos de
énfasis. Los navegadores pueden renderizar el texto enfatizado de muchas formas diferentes.
Nota: Los elementos <b>y<i>de HTML4 son simples instrucciones para mostrar el texto, en lugar de
especificar un significado semántico. En HTML5, es mejor usar <strong>y <em> en vez de <b>y<i>.
4.4. Listas.
Las listas organizan conjuntos de información de una forma clara y con un formato comprensible. HTML
define tres tipos de listas:
• Listas no ordenadas. Conjuntos de grupos de elementos sin ningún orden en particular. Cada elemento es
mostrado con una viñeta al inicio.
<p>Lista no ordenada de editores HTML</p>
<ul>
<li>Notepad</li>

25
<li>Textmate</li>
<li>NetBeans</li>
</ul>
• Listas con orden. Conjuntos de grupos de elementos en un orden particular. Cada elemento es numerado
secuencialmente.
<p>Lista ordenada de cómo escribir una página web</p>
<ol>
<li>Crear un nuevo fichero de texto</li>
<li>Añadir algún HTML</li>
<li>Guardar el fichero en un sitio web</li>
</ol>
• Listas de definición. Conjuntos de grupos de pares nombre-valor, como un término y su definición. El
nombre aparece resaltado y la definición se sangra en un párrafo posterior.
<p>Lista de definición con gente importante de Internet</p>
<dl>
<dt>Sir Tim Berners Lee</dt>
<dd>Inventó HTML y escribión WorldWideWeb</dd>
<dt>Linus Torvalds</dt>
<dd>Creó las bases de Linux</dd>
<dt>Charles Herzfeld</dt>
<dd>Autorizó la creación de ARPANET, el predecesor de Internet</dd>
</dl>
Estos tres tipos usan una etiqueta para definir el comienzo y cierre de la lista: <ul>, <ol>, y<dl>,
respectivamente.
Cada entrada individual es identificada con la etiqueta <li> para listas sin orden y con orden, mientras que en
listas de definición se usan las etiquetas <dt>para el nombre (o término) y <dd>para su valor (o definición).
Es posible anidar listas.
4.5. Mostrando imágenes.
Se usa la etiqueta <img>para insertar una imagen en una página web. Esta etiqueta no requiere una etiqueta
de cierre y no contiene nada. Su definición queda determinada por los siguientes atributos:
Atributo Descripción
src Especifica la URL que determina la localización del fichero de imagen a mostrar.
alt Identifica un texto alternativo que se mostrará si el navegador no puede renderizar la imagen
por alguna razón. Este texto normalmente describirá el contenido de la imagen.
title Identifica algún texto que será usado por herramientas de ayuda cuando el cursor pase por
encima de la imagen.
longdesc Identifica otra página web que describe la imagen en más detalle.
height Asignan las dimensiones en píxeles de la caja dentro de la página web que contendrá la
width imagen; si las dimensiones son diferentes de las de la imagen, los navegadores
redimensionarán la imagen.
De todos estos atributos sólo el atributo src es obligatorio.
Uno de los tipos de imágenes más comunes que incluyen las páginas web es un logotipo de algún tipo, como
en el siguiente código:
<body>
<p>
<img src="logo.jpg" alt="El logotipo de mi sitio" height="100" width="100" />
</p>
<h1>¡Bienvenidos a mi sitio!</h1>
</body>
HTML5 incluye el elemento<figure>, que normalmente se usa para identificar una imagen, vídeo o listado de
código y su descripción asociada u otros elementos. Si el contenido necesita un título, podemos anidar el
elemento <figcaption> dentro del elemento <figure>.
<figure>
<img src="plateofbeans.jpg" alt="Un plato de tostadas de alubias" />
<figcaption>Un maravilloso platod de tostadas en cinco minútos</figcaption>
</figure>

26
4.6. Mostrando enlaces a documentos.
La razón principal para la invención de HTML fue vincular documentos. La etiqueta <a>, también conocida
como la etiqueta de anclaje, permite identificar una sección de contenido de le página que enlaza otro recurso
en la web. Normalmente el destino de este enlace de hipertexto es otra página web, pero puede ser
igualmente un archivo de texto, una imagen, un fichero, un correo o un servicio web. Cuando veamos nuestra
página en el navegador, podemos hacer clic sobre el contenido enmarcado de las etiquetas de anclaje para
descargar el documento vinculado en el navegador.
Las etiquetas de anclaje tienen los siguientes atributos no globales:
Atributo Descripción
href Identifica la página web o el recurso enlazado.
target Identifica dónde el navegador debe mostrar el recurso vinculado; valores válidos son
_blank,_parent, _self, y_top.
rel Identifica qué tipo de enlace está siendo creado.
hreflang Identifica el lenguaje del recurso vinculado.
type Identifica el tipo MIME del recurso vinculado.
Un uso común de los enlaces de hipertexto es crear menús de navegación sobre las páginas para que el
usuario pueda visitar otras páginas del sitio.
<body>
<ul>
<li><a href="default.html" alt="Página inicial">Inicio</a></li>
<li><a href="about.html" alt="Acerca de este sitio Web">Acerca de</a></li>
<li><a href="essays.html" alt="Una lista de mis trabajos">Trabajos</a></li>
</ul>
</body>
El atributo hrefes laparte más importante de vincular un recurso en línea a otro. Podemos usar varios tipos
diferentes de valores:
• Una URL de la misma carpeta (por ejemplo: about.html).
• Una URL relativa a la carpeta actual (por ejemplo: ../about.html).
• Una URL absoluta a la carpeta raíz del servidor (por ejemplo: /<ruta de contexto>/pages/about.html).
• Una URL ubicada en otro servidor (por ejemplo: http://www.microsoft.com/default.html).
• Un identificador de fragmento o un nombre de identificador precedido por una almohadilla (por ejemplo:
#section2).
• Una combinación de URL y un identificador de fragmento (por ejemplo: about.html#section2).
En el siguiente ejemplo se muestra cómo definir un fragmento y crear un enlace al mismo:
<body>
<a href="#pie">ir al final de la página</a>
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<p id="pie">Final de página</p>
4.7. Formularios de datos.
Muchos sitios web requieren que el usuario introduzca información, como un nombre de usuario, contraseña
o dirección. Los textos e imágenes definen el contenido que el usuario puede leer, pero un formulario
proporciona un mecanismo de interacción entre el usuario y el sitio web, dándole la posibilidad de enviar
datos al servidor de nuestro sitio web para que los recoja y los procese.
Se usa el elemento <form> para identificar un área de nuestra página que actuará como un formulario de
petición de datos. Este elemento tiene los siguientes atributos:
Atributo Descripción
action Identifica la URL de la página que recibirá los datos posteados.
method Define cómo son enviados los datos al servidor. Valores válidos son: GETyPOST.
accept-charset Define la codificación de caracteres de los datos enviados desde el formulario.
27
enctype Identifica el tipo de formato (tipo MIME) usado cuando se descodifican los datos del
formularios cuando el método es POST.
target Identifica dónde mostrará el navegador la página indicada en el atributo action;
valores válidos son _blank, _parent, _self, y_top.
Se deben añadir controles y elementos de texto dentro del contenido del formulario para determinar su
diseño.
4.7.1. Controles de formulario.
Las etiquetas form normalmente incluyen información para el usuario en forma de texto y etiquetas de entrada
que definen cosas como botones y cuadros de texto. Un uso típico de la etiqueta <form> puede ser el
siguiente:
Código de «index.html» Aspecto en el navegador

<!DOCTYPE html>
<html> Figura 25
<head>
<title>Primer ejemplo web</title>
</head>
<body>
<form method="POST" action="cliente.jsp">
Introduzca el ID de cliente:
<br />
<input type="text" name="Id">
<input type="hidden" name="status" value="id"/>
<br />
<input type="submit" value="Obtén cliente">
</form>
</body>
Este ejemplo solicita al usuario un ID de cliente, muestra un cuadro de edición para introducir el id, y
también muestra un botón para iniciar el envío de datos al servidor Web. A modo de ejemplo se ha incluido
también un campo de tipo hidden, el cual no tiene una visualización en el navegador, ya que se usa para
almacenar algún valor fijo que queramos enviar al servidor.
El atributo method del formulario indica el método HTTP (POST) usado cuando se envía la solicitud al
servidor. El atributo action es una URL relativa al recurso de servidor al cual se envían los datos posteados
desde el formulario.
Nota. Si en un formulario no se especifica el atributo action, el posteo se realiza sobre una solicitud de la
propia página.
Un elemento <input> representa el control HTML principal para entrada de datos y tiene muchas formas
según el atributo type, tal como se muestra en la siguiente tabla. El atributo value permite asignar un valor por
defecto para controles basados en números o texto, y el atributo name permite asignar un nombre que
quedará asociado al dato del control.
<input /> Resultado
text Una caja de texto para editar una sola línea.
password Una caja de texto de una línea para introducir contraseñas.
hidden Un campo para almacenar un texto no visible para el usuario.
checkbox Una caja de verificación. Proporciona una elección si/no o verdadero/falso. Se usa el
atributo selected para indicar si la caja está marcada por defecto.
radio Un botón de radio. Se usa el atributo name para agrupar varios botones de radio. El
formulario sólo permitirá seleccionar un botón de radio dentro de un grupo.
reset Un botón de reseteo. Al pulsar este botón el usuario pondrá todos los controles del
formulario con sus valores por defecto.
submit Un botón de envío. Al pulsar sobre este botón el formulario enviará los datos a la
página indicada en el atributo action.
image Una imagen para usar como un botón de envío. Se usa el atributo src para identificar la
imagen que se usará.
button Un botón de comando. Este botón no tiene ninguna funcionalidad por defecto, pero
28
puede ser usado para ejecutar código script cuando se pulse.
file Un control de fichero. Proporciona un modo de enviar un fichero al servidor.
url Una caja para editar una URL.
Hay otros tres elementos HTML que podemos usar en un formulario:
<textarea> Genera un cuadro de texto plano editable en varias líneas. Se usan los atributos
rowsycols para asignar su tamaño.
<select> Define una lista fija de elementos o una lista desplegable. Se usa el atributo multiple para
indicar si el usuario puede seleccionar más de un elemento. Se usan elementos anidados
<option> para definir los elementos de la lista. Podemos usar el atributo selected de los
elementos <option> para indicar que están seleccionados por defecto, y su atributo value
para asociar un valor con cada elemento. Si se establece el atributo value, será su valor lo
que se envíe, sino se enviará el contenido de texto del elemento <option>.
<button> Define un botón de comando. Se usa el atributo type para indicar si es un botón submit,
reset, obutton. El valor por defecto essubmit.
Se debería usar el elemento <button>en vez de sus equivalentes <input> si necesitamos
que el contenido mostrado del botón sea más complejo que una simple pieza de texto o
una imagen simple.
Es importante resaltar que para que se postee el dato de un control se deben cumplir dos condiciones:
• El control debe estar anidado dentro de un elemento <form/>.
• El control debe tener asignado su atributo name a un valor. Se permite que más de un control tenga
asignado el mismo valor en name.
4.7.2. Elementos de distribución del formulario.
Podemos usar las etiquetas <p>y<div> para aplicar un diseño básico a un formulario. HTML también define
dos etiquetas adicionales que pueden ayudar a mejorar la presentación de un formulario:
• <fieldset>, el cual identifica un grupo de controles dentro del formulario. El navegador refleja esto
dibujando una caja alrededor del contenido con una etiqueta de título. Este título se asigna mediante el
elemento anidado <legend>, el cual debe ser el primer hijo del elemento <fieldset>.
• <label>, el cual define una etiqueta de texto no editable asociada a un control del formulario. Se logra así
enmarcar ambos, el texto y el control, o bien enmarcar el texto y asignando su atributo for al atributo id del
control.
Podemos usar un formulario para una gran variedad de tipos de entradas del usuario. El siguiente ejemplo
muestra cómo aplicar estos elementos para el ejemplo previo.
Código de «index.html» Aspecto en el navegador

<!DOCTYPE html>
<html> Figura 26
<head>
<title>Primer ejemplo web</title>
</head>
<body>
<form method="POST" action="cliente.jsp">
<fieldset>
<legend>Solicitud de cliente</legend>
<label for="Id">ID de cliente:</label>
<input type="text" name="Id">
<input type="hidden" name="status" value="id"/>
</fieldset>
<br />
<input type="submit" value="Obtén cliente">
</form>
</body>
4.7.3. Métodos de posteo de datos de formulario.
Hay dos método HTTP que podemos usar para postear los datos del formulario de regreso al servidor Web:
GET y POST. Cuando se usa GET, los datos son añadidos a la URL como parte de la cadena de consulta. La

29
cadena de consulta es una colección de pares clave-valor, separados por un caracter ampersand (&). El
siguiente ejemplo muestra una solicitud GET:
GET /cliente.jsp?Id=123&status=id HTTP/1.1
Host: localhost:8084
En este ejemplo, se realiza una solicitud GET al servidor Web sobre una página llamada cliente.jsp, situada en
el directorio raíz del sitio Web (esto se indica por la barra inclinada). La cadena de consulta contiene los datos
del formulario después del signo de interrogación (?).
Cuando se usa el método GET para enviar datos al servidor, la URL completa y la cadena de consulta pueden
ser enviados y modificados en la barra de dirección del navegador Web. Ténganse en mente que,
dependiendo del escenario, esto puede ser una desventaja o provocar riesgos de seguridad. Seguramente no
querremos que cualquiera manipule estos datos en la cadena de consulta y que potencialmente vean cosas que
no deberían ver o que corrompan los datos. También podremos querer que los usuarios no puedan señalar
páginas que incluyan información reservada enviada al servidor. Otra desventaja es que la cadena de consulta
está limitada en tamaño por el navegador Web y el servidor Web.
El método POST es el preferido para enviar datos de regreso al servidor como parte de una solicitud HTTP.
Cuando se usa el comando POST, los datos se envían dentro del cuerpo del mensaje de la solicitud como
sigue:
POST /cliente.jsp HTTP/1.1
Host: localhost:8084

Id=123&status=id
Usando el comando POST se elimina la restricción del tamaño de los datos. (Como prueba, se pasaron más de
10 megabytes de datos a un servidor Web para ver si los aceptaba. Esto funcionó, pero el enviar tantos datos
a través de Internet puede causar otros problemas, principalmente relacionados con el ancho de banda, con
errores de tiempo de espera y problemas de rendimiento.)
Además, el método POST impide a los usuarios manipular la solicitud en la barra de dirección de los
navegadores. Esto es así porque los datos se ocultan en el cuerpo del mensaje. Por lo tanto, en muchos
escenarios, el método POST es el más deseable para enviar datos a un servidor Web.
4.7.4. Cómo recuperar los datos posteados desde un formulario.
Los datos posteados por un formulario sólo pueden ser recuperados desde un recurso dinámico, tal como un
servlet o una página JSP. No se pueden postear datos a otra página HTML, puesto que son recursos estáticos
y no permiten incluir código del lado servidor.
Como ejemplo, incluiremos la página cliente.jsp en el proyecto Web creado previamente con NetBeans.
Figura 27

Para incluir el fichero cliente.jsp en el proyecto hay que incluir un fichero de tipo JSP de la categoría «Web». Si
ejecutamos la aplicación se mostrará primero la página index.html. Al pulsar el botón «Obtén cliente» se
postearán los datos del formulario a la página cliente.jsp. La página cliente.jsp incluye en su cuerpo texto
estático y una expresión ${param.Id}. Este tipo de expresiones es conocido como expresiones EL y forman
parte del código del lado cliente que es interpretado por el contenedor de servlets. Se utiliza esta expresión
para recuperar el valor de un parámetro de formulario que coincida en su atributo name con Id.

30
Figura 28

En las siguientes unidades se analizará con más extensión el funcionamiento de los servlets, páginas JSP y
expresiones EL para procesar datos posteados y otro tipo de datos recibidos desde una solicitud.
4.8. Organizar una página usando tablas.
Hemos visto que HTML5 define etiquetas semánticas como <header>, <footer> y otras para organizar el
contenido de las páginas. Pero estás etiquetas no establecen una distribución física de su contenido.
La manera más sencilla de distribuir el contenido de una página es usar tablas. El siguiente código muestra
una plantilla de uso de una tabla:
Cuerpo de la página Aspecto en el navegador

<body>
<table border="1">
<thead>
<tr>
<th>Cabecera 1</th> Figura 29
<th>Cabecera 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Celda 1 1</td>
<td>Celda 1 2</td>
</tr>
<tr>
<td>Celda 2 1</td>
<td>Celda 2 2</td>
</tr>
</tbody>
</table>
</body>
El significado de cada etiqueta es el siguiente:
Etiqueta Significado
table Es la etiqueta contenedora de todas las demás.
thead Es opcional. Define el bloque de filas de cabecera de la tabla.
tbody Es opcional. Define el bloque de filas de contenido de la tabla
tr Define una fila de la tabla.
td Define una celda normal dentro de una fila.
th Define una celda que resalta su contenido.
Las tablas más sencillas de HTML se pueden definen con sólo tres etiquetas: <table>, <tr> y <td>.
A continuación se muestra cómo utilizar una tabla para diseñar una página con dos paneles y un elemento
<footer />. Los paneles se definirán con elementos <div /> y se ubicarán dentro de las celdas de la tabla:
Cuerpo de la página Aspecto en el navegador

31
<body>
<table cellspacing="3">
<tr>
<td>
<div style="padding:1em; background-color: lightgray">
<a href="inicio.html">Inicio</a><br />
<a href="informes.html">Informes</a><br />
Figura 30
<a href="ventas.html">Ventas</a><br />
</div>
</td>
<td>
<div>
Contenido de la página inicial
</div>
</td>
</tr>
<tr>
<td colspan="2">
<div style="background-color: lightgray">
Pie de página
</div>
</td>
</tr>
</table>
</body>
Un bloque <div /> define un panel rectangular dentro de la página, y permite aislar parte del contenido de la
página. Para este ejemplo se han creado dos paneles: uno para mostrar un menú de enlaces y otro para
mostrar el contenido principal de la página. Mediante el atributo style se ha aplicado un color de fondo
(background-color) y un margen interno (padding).
La tabla se ha definido con dos filas. En la primera fila se usan dos celdas, pero en la segunda fila se usa una
única celda que ocupa todos el ancho de dos celdas (esto se indica con colspan="2").
Téngase en cuenta que dentro de una celda podemos anidar otras tablas completas. Esta característica permite
organizar el contenido de cada panel y sección con un diseño más complejo.
4.9. Validar datos de formulario usando atributos HTML5
Cuando creamos un formulario y lo añadimos al sitio web, los datos recolectados pueden variar en términos
de calidad y precisión. De hecho, los formularios están abiertos para recoger cualquier tipo de dato del
usuario, tanto si es válido como si no. Incluso una entrada de usuario puede ser malévola, diseñada para
sondear cómo reacciona la aplicación a datos inesperados.
La validación es el proceso de comprobar los datos para detectar errores obvios. La validación ocurre en dos
lugares:
• En el cliente. Cuando el usuario completa el formulario, algún dato puede ser validado por marcado
HTML y código JavaScript.
• En el servidor. Cuando el formulario envía los datos, el recurso que los recibe en el servidor debe
verificarlos antes de procesarlos.
Es habitual validar en el lado cliente el formato y rango de valores de los datos, mientras que en el servidor se
validan los datos para comprobar que cumplen con las reglas del negocio.
4.9.1. Asegurarse de que los campos no están vacíos.
Para asegurarse que el usuario introduce datos en campos obligatorios, hay que añadir el atributo required a
un elemento <input>. Si el usuario intenta enviar los datos del formulario antes de proporcionar un valor, su
petición será ignorada. El siguiente ejemplo muestra cómo usar el atributo required:
<input type="text" name="Id" required />
Cómo el navegador informa al usuario de que un campo obligatorio está vació es cosa del navegador. Por
ejemplo, Internet Explorer resalta los campos vacíos con un borde rojo y muestra un mensaje.

32
Figura 31

4.9.2. Validación de campos numéricos.


Con HTML5 podemos controlar un rango de valores para los campos numéricos. El siguiente código es un
ejemplo:
<input name="porcentaje" type="number" min="0" max="100" />
El usuario puede escribir cualquier cosa en el campo, pero sólo serán válidos los números entre 0 y 100. Si el
usuario deja el campo en blanco no se aplicará la validación. Por eso es importante marcar el campo con el
atributo required para obligar al usuario a que escriba un valor.
Nota:El campo de tipo number también soporta otros atributos como step, el cual especifica un
incremento válido para los valores numéricos escritos en el campo. Estos atributos no están actualmente
implementados en Internet Explorer 10.
4.9.3. Validación de campos de texto.
HTML5 proporciona campos de entrada como tel (teléfono) y email (correo electrónico) que esperan datos
con un formato específico, pero también podemos aplicar patrones personalizados mediante expresiones
regulares. Por ejemplo, si necesitamos un código de pedido con el patrón 99XXX, donde 9 es cualquier dígito y
X es cualquier letra en mayúsculas, podemos usar el atributo pattern para especificar la expresión regular que
valide el campo. Podemos proporcionar retroalimentación al usuario sobre el formato esperado de los datos
usando el atributo title.
<input id="orderRef" name="orderReference" type="text"
requiredpattern="[0-9]{2}[A-Z]{3}" title="2 dígitos y 3 letras en mayúsculas" />
Para que se aplique la validación del patrón debemos también aplicar la validación required.
4.9.4. Estilos en los campos para proporcionar retroalimentación.
Para ayudar al usuario a identificar los campos que deben completarse, deberíamos indicar visualmente si
estos campos son requeridos o no. Un modo de hacer esto es marcando cada campo con un asterisco. En
este ejemplo, se usa un asterisco en rojo usando CSS.
<form id="registerForm" method="post" action="registration.aspx">
<div id="campoNombre" class="campo">
<label for="nombre">Nombre: </label>
<input id="nombre" name="nombre" required="required" placeholder="Tu nombre de pila" />
<span style="color:red">*</span>
</div>
...
</form>
Un modo mejor para indicar si es un campo es requerido es usando CSS para dar estilo al campo de acuerdo
con el atributo required. Para usar validación cambiando dinámicamente el color del borde, creando
retroalimentación instantánea en cualquier navegador, podemos asignar el borde de campos con datos no
válidos en rojo, mientras los campos con datos válidos estarán con borde verde. La siguiente técnica trabaja
detectando el estado de validación del campo usando las seudo-clases valid einvalid, y proporcionando una
regla para el color del borde en cada estado.
<style type="text/css">
input {
border: solid 1px;
}
input:invalid {
border-color: red;
}
input:valid {

33
border-color: black;
}
</style>

5. Fundamentos de CSS
HTML define la estructura y contexto de una página web, mientras CSS define cómo deben aparecer en el
navegador. En este capítulo veremos los fundamentos de CSS, como crear algunos estilos básicos, y cómo
asociar estos estilos a elementos de una página HTML.
5.1. Fundamentos de la sintaxis de CSS.
CSS es el acrónimo de Cascading Style Sheets. CSS proporciona un modo estándar de definir cómo un
navegador debe mostrar el contenido de una página web. CSS permite asociar reglas de presentación a
fragmentos de HTML basadas en selectores que referencian elementos HTML por nombre, id o clase.
También permite varias cómo se presenta una página de acuerdo a factores del dispositivo sobre el cual se
muestra, desde un monitor grande a un Smartphone, y aún sobre un lector de audio o una impresora.
Cada regla CSS tiene la misma estructura básica:
selector {
propiedad1:valor;
propiedad2:valor;
...
propiedadN:valor;
}
Este ejemplo muestra las cuatro partes de cada regla CSS:
• Unselectordefine el elemento o conjunto de elementos destinos del estilo. El estilo especificado por la
regla CSS es aplicado a todos los elementos de la página web que casen con el selector. Un selector CSS
puede especificar el tipo del elemento como div para seleccionar todos los elementos <div>, o el nombre de
los atributos de una instancia específica de un elemento. Podemos también seleccionar varios tipos de
elementos como p,div para seleccionar todos los elementos <p> y <div>. Se puede usar * para seleccionar
todos los elementos. También son posibles otras expresiones de selectores.
• Un par de llaves que encierran la reglas para los elementos del selector. Una regla define como renderizar
el elemento seleccionado; contiene pares de propiedad-valor separados por punto y coma.
• Una propiedad define el aspecto visual de elemento seleccionado a cambiar.
• Un valor especifica el estilo a aplicar sobre una propiedad. Los valores pueden ser dependientes de la
propiedad. Pueden ser nombres de colores, valores de tamaño en porcentajes, píxeles, o puntos, o el
nombre de una fuente de letra, etc.
Podemos también añadir comentarios a nuestras hojas de estilo usando delimitadores/* */. El navegador
ignorará los comentarios. Se pueden usar fuera o dentro de una regla CSS.
Todas las reglas CSS tienen la misma sintaxis básica. Más allá de eso, la primera clave para CSS es saber las
propiedades que se aplican a conjuntos de elementos. Este ejemplo muestra el uso de algunas propiedades de
texto específicas:
/* Objetivo: títulos de nivel 1; los renderia con texto rosa grande usando la fuente Segoe UI */
h1 {
font-size: 42px;
color: pink;
font-family: 'Segoe UI';
}
/* Objetivo: texto enfatizado; lo renderiza como itálico y con fondo amarillo */
em {
background-color: yellow; /* El amarillo de un buen color de resalte */
font-style: italic;
}
En este ejemplo, las dos reglas se trasladan como sigue:
• Cada elemento <h1> debe tener texto de tamaño 42px, rosa y fuente Segoe UI.
• Cada elemento <em> debe tener un color de fondo amarillo y el texto en cursivas.
Cuando se escribe CSS, note que cualquier secuencia de espacios en blanco es tratado como un único espacio
en blanco.

34
5.2. Añadiendo estilos a una página HTML.
HTML permite asociar las reglas CSS a nuestras páginas web de tres maneras:
• Escribiendo las reglas específicas para un elemento en su atributo style.
<p style="font-family : Candara; fontsize:12px; "> ... </p>
• Escribiendo un conjunto de reglas específicas para una página dentro del elemento <head> usando
etiquetas <style>.
<style type="text/css">
p{
font-family : Candara; font-size: 12px;
}
</style>
• Escribiendo las reglas dentro de ficheros (con extensión .css) de hojas de estilo, y después
referenciándolos en el marcado de la página usando la etiqueta <link>. El lugar más habitual para poner
etiquetas <link> es dentro del elemento <head>.
<link rel="stylesheet" type="text/css" href="mystyles.css" media="screen">
El elemento <link> tiene cuatro atributos CSS relevantes:
Atributo Significado
href Especifica una URL que identifica la localización del fichero de hoja de estilo.
rel Indica el tipo de documento que referencia el elemento <link>; se asigna a
stylesheetcuando enlazamos hojas de estilo.
media Indica el tipo de dispositivo destino para la hoja de estilo; posibles valores son: speech para
sintetizadores de voz, print para impresoras, handheld para dispositivos móviles, screen
para pantallas de ordenador, y all (por defecto) para todos los propósitos.
type Indica el tipo MIME del documento que está siendo referenciado; el tipo correcto para la
hojas de estilo es text/css, el cual es el valor por defecto para este atributo.
Los atributos type y media tienen la misma función para los elementos <style>así como para sus homónimos
del elemento <link>.
Nótese que los estilos se aplican en orden, de arriba abajo, tal como la página es analizada y procesada, Los
últimos estilos sobreescriben los primeros cuando se aplican al mismo elementos. Por ejemplo, si definimos
estilos en un elemento <head>, y entonces añadimos un <link> que referencia una hoja de estilo con estilos
diferentes para los mismos elementos, entonces los estilos de la hoja de estilos reescriben los definidos
directamente en el elemento <head>. Sin embargo, si definimos los estilos en un elemento <head> después
de un <link> que referencia una hoja de estilos, entonces los estilo definidos directamente reescribirán los de
la hoja de estilos. Si definimos estilos en línea como parte de un elemento, estos siempre reescriben cualquier
otro definido previamente.
5.3. Cómo trabajan los selectores CSS.
Los selectores CSS especifican el contenido sobre el que se aplicará el estilo usando el conjunto de reglas
asociadas. Comprender cómo trabajan los selectores CSS es la clave para definir hojas de estilos reutilizables y
extensibles.
La especificación CSS proporciona muchos modos diferentes de seleccionar elementos o conjuntos de
elementos en una página web sobre los que aplicar reglas de presentación.
El siguiente listado resume los selectores básicos y el conjunto de elementos que identifican.
• El selector de elemento, identifica el grupo de elementos de la página con ese nombre. Por ejemplo, h2 { }
retorna el conjunto de todos los títulos de nivel 2 de la página.
• El selector de clase, precedido por un punto, retorna el conjunto de elementos de la página que tienen
asignado el atributo class al especificado. Por ejemplo, .myClass { } retorna el conjunto de elementos que
tienen asignado el atributo class a "myClass".
• El selector de id, precedido por una almohadilla, retorna el conjunto de elementos de la página que tienen
asignados el atributo id al especificado. Por ejemplo, #thisId { } retorna el conjunto de elementos cuyo
atributo id está asignado a"thisId".
El selector de clase puede retornar un conjunto de varios tipos de elementos HTML (por ejemplo, <p>,
<section> y <h3>) si tienen asignado el mismo valor en el atributo class. Lo mismo ocurre para el selector de
id, aunque este selector debería retornar un único elementos ya que el atributo id en una página debería ser
único en cada elemento (aunque no es obligatorio).

35
Hojas de estilo se escriben a menudo con los selectores menos específicos primero y los selectores más
específicos por último.
Los selectores de elemento son los menos específico, seguidos por los selectores de clase y los selectores de
id, y por último las combinaciones de los tres.
h2 { /* selector de elemento */
font-size: 24px;
}
.red { /* selector de clase */
color: red;
}
#alert { /* selector de id */
background-color: red;
color: white;
}
Podemos combinar los selectores usando concatenación. En el siguiente ejemplo, las dos reglas combinan
selectores para identificar un conjunto de elementos más específicos.
El selector h2.blue retorna los elementos <h2> con su atributo class al valor "blue", y h2#toc retorna los
elementos <h2> con su atributo id al valor "toc".
h2.blue {
color: blue;
}
h2#toc {
font-weight: bold;
}
Nótese que estos dos conjuntos pueden tener elementos en común, en cuyo caso las propiedades CSS y los
valores de ambas reglas se aplicarán.
La siguiente tabla muestra ejemplo de varias formas de concatenar selectores y el conjunto de elementos que
el navegador retorna.
Selector CSS Significado
h2.blue Retorna cualquier elemento <h2>de la clase "blue"
h2#blue Retorna cualquier elemento <h2>con el id "blue"
section, h2 Retorna cualquier elemento <h2>y cualquier elemento <section>
section h2 Retorna cualquier elemento <h2>anidado en un elemento <section> a cualquier nivel
section > h2 Retorna cualquier elemento <h2>anidado inmediatamente bajo el elemento <section>
section + h2 Retorna cualquier elemento <h2>inmediatamente después del elemento <section> de
forma que comparten el mismo elemento padre
section ~ h2 Retorna cualquier elemento<h2>después del elemento <section> de forma que
comparten el mismo elemento padre
5.3.1. El selector comodín.
El selector comodín * retorna todos los elementos del documento. Este selector se usa raramente por sí
mismo, pero podemos usarlo en combinación con otros elementos. Por ejemplo, la siguiente regla retorna
todos los elementos anidados en un elemento <aside>, y les aplica transparencia.
aside * { opacity : 0.6; }
5.3.2. Selector de atributos.
Podemos afinar más cualquier selector inspeccionando la declaración de atributos y sus valores dentro del
elemento. El atributo seleccionado se indica entre corchetes, añadido al selector y puede tener cualquiera de
las siguientes formas:
Selector CSS Significado
input[type] Retorna cualquier elemento<input>que usa el atributo typesin valor
input[type="text"] Retorna cualquier elemento<input>que usa el atributo type asignado al valor
"text"
input[foo~="red"] Retorna cualquier elemento<input>con el atributo foo, que contiene una lista
de valores separados por espacios, uno de los cuales es "red"
input[type^="sub"] Retorna cualquier elemento<input>con el atributotypecuyo valor comienza por
"sub"
input[type$="mit"] Retorna cualquier elemento<input>cuyo atributo typefinaliza con "mit"
36
input[type*="ubmi"]Retorna cualquier elemento<input>con el atributo typeconteniendo "ubmi".
input[foo|="en"] Retorna cualquier elemento<input>con el atributo foo cuyo valor es "en" o
comienza con "en"
También podemos combinar selectores de atributos por concatenación. Por ejemplo, para retornar todos los
checkbox marcados por defecto, se usa el siguiente selector:
input[type="checkbox"][selected] { }
5.4. El modelo de cajas de CSS.
Para determinar el diseño de una página HTML, los navegadores tratan cada elemento de la página como un
conjunto de cajas anidadas. El modelo de cajas CSS permite especificar el tamaño de cada caja, y así modificar
el diseño de cada elemento de la página.
El modelo de cajas pone el contenido dentro de cuatro cajas: contenido, relleno, borde y margen.
Figura 32

En la caja central está el contenido, con texto e imágenes. Se usan las propiedades height ywidth para asignar
el alto y ancho de la caja en píxeles.
Alrededor de la caja de contenido está el relleno. Se usa la propiedad padding para asignar el ancho de la caja
de relleno.
Alrededor de la caja de relleno está el borde, que también actúa como una línea visible alrededor del
contenido y relleno. Se usa la propiedad border para asignar el ancho, color y estilo de borde.
Alrededor de la caja de borde está el margen. Se usa la propiedadmargin para asignar el ancho del margen.
El siguiente ejemplo muestra cómo usar el modelo de cajas CSS para dibujar un borde alrededor de un texto
de título, y asignar un margen alrededor del borde para no interferir con otros elementos de la página.
h2.highlight {
height : 100px;
width : 500px;
padding : 10px;
border : 2px dotted blue;
margin : 25px 0 25px 0; /* Podría también escribirse: 25px 0 */
}
Las propiedades margin ypadding son ambas propiedades de formato corto. CSS define actualmente
propiedades individuales para el top, right, bottom, y left de cada caja. Podemos escribir la definición del
relleno de la siguiente manera:
padding-top: 10px;
padding-right : 10px;
padding-bottom : 10px;
padding-left : 10px;
La propiedad border es también de formato corto para el width, style y color. En el ejemplo previo, el borde es
asignado a 2px de ancho, línea de puntos y color azul. Se puede poner en la forma completa:
border-width: 2px;
border-style: dotted;
border-color: blue;
Usando las propiedades border-width, border-style, yborder-color se asume que queremos asignar los valores a
los cuatro laterales de la caja. Si ese no es el caso o sólo queremos asignar uno de los lados, podemos usar las
propiedades border-left-style, border-left-width, border-left-color,border-right-style, border-right-widthy así otras.
Por ejemplo:
p.example {
padding: 10px;

37
border-bottom: solid 1px black;
border-left-style: dotted;
border-left-width: 2px;
}
Las propiedades border-top, border-right, border-bottom, y border-left también tienen su formato corto, como
border, pero para el width, style, y color de los respectivos lados de la caja de borde.
Más allá de las propiedades principales del modelo de caja, CSS define varias otras propiedades para controlar
cómo se debe ver el contenido en el flujo de los elementos de la página. Específicamente:
Propiedad CSS Significado
visibility Permite dejar en blanco los elementos seleccionados, pero dejando el espacio
vacío que ocupan en la página.
display Permite asignar cómo mostrar los elementos seleccionados en la página. Esto
incluye ocultar los elementos seleccionados, o quitarlos completamente de la
página.
position Permite asignar el método de posicionamiento de los elementos seleccionados.
Hay cuatro posibles valores: static (por defecto), fixed, absolute, yrelative.
float Permite hacer que los elementos seleccionados floten sobre el resto de contenido
respecto al borde derecho o izquierdo.
overflow Permite determinar qué ocurre cuando el contenido de un elemento es demasiado
grande para la caja que lo contiene.
box-sizing Permite asignar cómo el ancho y alto se aplican sobre una caja del modelo. Si se
asigna a content-box (por defecto), trabajan como se ha descrito previamente. Si se
asigna a border-box, el ancho y alto se aplican a la totalidad del contenido, relleno,
borde y margen en conjunto.
5.5. Estilos de fondo en CSS.
Muchos sitios web usan una imagen de fondo, color o patrón para proporcionar más colorido y carácter a las
páginas. CSS permite asignar un fondo a cualquier elemento de nivel de bloque usando las siguientes
propiedades:
Propiedad CSS Significado
background-image Permite especificar la URL de una imagen que se usará de fondo. Se puede
usar una URL absoluta o relativa. Si se usa una ruta relativa debe serlo
respecto a la localización de la hoja de estilo o la página web que define el
estilo.
background-image:url('../images/pattern.jpg');
background-size Permite asignar el alto y ancho de la imagen de fondo. Se usan valores
específicos en píxeles o en porcentajes.
background-size: 40px 60px; /* 40px ancho, 60px alto */
background-color Permite asignar el color de fondo de un elemento. Se puede especificar un
color como un valor RGB (rojo-verde-azul) o como una de las 147
constantes predefinidas.
background-color : green;
background-color : #00FF00;
background-color : rgb(0, 255, 0);
background-position Permite asignar la posición de la imagen de fondo bajo el elemento. Esta
propiedad tiene dos valores: el primero para el eje x y el segundo para el eje
y. Se asignan ambos valores como valores absolutos (top, left, bottom, right,
center), porcentajes o píxeles.
/* Imagen adosada a la esquina superior izquierda */
background-position : left top;
/* Imagen adosada a la esquina inferior derecha */
background-position : 100% 100%;
/* Imagen a 8 px de la izquierda y 8 px de arriba */
background-position : 8px 8px;
background-origin Permite asignar a qué caja del modelo de cajas la posición del fondo
debería ser relativa. Posibles valores son content-box (por defecto),
38
paddingbox, y border-box.
background-origin : border-box;
background-repeat Permite asignar cómo debe repetirse una imagen de fondo si es más
pequeña que el elemento seleccionado. Posibles valores son repeat (por
defecto), repeat-x, repeat-y y no-repeat.
background-repeat : repeat-x; /* Repite imagen horizontalmente */
background-repeat : no-repeat; /* No repite la imagen */
background-attachment Permite asignar si la imagen de fondo se desplaza hacia arriba y hacia abajo
o permanece fija en su lugar. Posibles valores son scroll (por defecto) y
fixed.
background-position : fixed;
CSS también proporciona una forma corta en la propiedadbackground, que permite asignar todos los valores
descritos. Por ejemplo:
article { background : transparent repeat-x url('fluffycat.jpg'); }
/* O bien: */
article {
background-color : transparent;
background-repeat : repeat-x;
background-image : url('fluffycat.jpg');
}
5.6. Aplicando herencia en hojas de estilo y HTML.
Como sugiere le frase Hojas de Estilo en Cascada, los elementos de una página puede ser afectados por varias
transformaciones en cascada dependiendo de la relación entre los elemento y las hojas de estilo asociadas con
la página. Para crear hojas de estilo con éxito, es importante comprender dos conceptos: herencia entre
elementos HTML y cómo aplicar varias reglas CSS en cascada a los elementos HTML.
5.6.1. Herencia HTML.
En una página web, los elementos HTML heredan algunas propiedades de sus elementos contenedores a
menos que se especifique otra cosa. Esto es de gran importancia; sin la herencia, tendríamos que replicar las
mismas reglas para cada elemento simple de la página.
Consideremos el escenario en el cual queremos que todos los textos de la página usen la fuente Candar.
Podemos asignar el elemento <body> de la página a esta fuente, y la herencia aplicará este tipo de texto a cada
elemento anidado dentro del cuerpo a menos que otra regla la sustituya.
body {
font-family: Candara;
}
Si no existiese la herencia, deberíamos asignar esta propiedad a cada elemento que contenga texto. Se podría
acabar escribiendo muchos estilos repetidos, tal como se muestra en el siguiente ejemplo, lo cual es muy
difícil de mantener.
h1, h2, h3, h4, h5, h6 {
font-family: Candara;
}
p{
font-family: Candara;
}
...
Nota:No se heredan todas las propiedades CSS del contenedor en su hijos, porque no tendría sentido.
Por ejemplo, si asignamos una imagen de fondo a un elemento <article>, seguramente no querremos que
sus secciones y párrafos hijos muestren la misma imagen de fondo.
5.6.2. Reglas en cascada.
Un único elemento en una página HTML puede casar con más de un selector de una hoja de estilo y puede
estar sujeto a varias reglas diferentes de estilo. El orden en el cual se aplican estas reglas podría provocar que
el elemento se renderice de formas diferentes. El mecanismo de cascada es la forma en la cual las reglas de
estilo se derivan y se aplican cuando varias reglas que entran en conflicto se aplican sobre varios elementos; se
asegura de que todos los navegadores muestren el elemento de la misma forma.
Hay tres factores que los navegadores deben tener en consideración cuando aplican reglas de estilo:

39
1. Importancia. Podemos asegurar la importancia de una propiedad aplicada a un conjunto de elementos
añadiendo el operador de importancia (!important).
h2 { font-weight : bold !important; }
2. Especificidad. Las reglas de estilo con menos especificidad se aplican primer, otras reglas menos
específicas a continuación, y así hasta aplicar las reglas más específicas.
3. Orden de origen. Si existen reglas de estilo para selectores de igual especificidad, se aplican en el orden
en que se definen en la hoja de estilo.
5.7. Organizar una página usando paneles y estilos.
Hemos visto cómo organizar una página usando tablas. El uso de tablas fuerza un diseño muy rígido en la
página ya que cada celda ocupa una posición determinada dentro de la tabla.
Podemos utilizar también el modelo de cajas y los estilos de posicionamiento para ubicar paneles en
posiciones fijas o flotantes de la página.
Cuerpo de la página Aspecto en el navegador

<head>
<style type="text/css">
.menu {
padding: 1em;
background-color: lightgray;
position: absolute;
left: 0ex; width: 9ex; top: 0ex; bottom: 2em
}
.contenido {
border: 3px solid lightgray;
Figura 33
position: absolute;
left: 11em; right: 0em; top: 0em; bottom: 2em
}
.pie {
padding: 4px;
background-color: lightgray;
position: absolute;
height: 1em; right: 0ex; left: 0ex; bottom: 0ex;
}
</style>
</head>
<body>
<div class="menu">
<a href="inicio.html">Inicio</a><br />
<a href="informes.html">Informes</a><br />
<a href="ventas.html">Ventas</a><br />
</div>
<div class="contenido">
Contenido de la página inicial
</div>
<footer class="pie">
Pie de página
</footer>
</body>
En el bloque de estilos se han definido estilos para tres clases, las cuales se aplican a los paneles del cuerpo.
El posicionamiento absoluto (position: absolute) permite flotar un elemento en una posición absoluta
respecto a los borde la página. Para establecer la distancia entre los borde de cada estilo se han usado
unidades de medida em (tamaño del tipo de letra actual) y ex (la altura de la letra x minúsculas).Otras
unidades de mediada son cm (centímetros),mm (milímetros), in (pulgadas o 2,54 cm), pc (picas o 1/6 in), pt
(puntos o 1/72 in), px (pixeles o 1/96 in), ch (el ancho del carácter cero), rem (tamaño de letra el tipo por
defecto).

40
6. Fundamentos de JavaScript
HTML y CSS proporcionan la estructura, semántica e información de presentación para una página web. Sin
embargo, estas tecnologías no describen cómo el usuario interacciona con la página usando un navegador.
Para implementar esta funcionalidad, todos los navegadores modernos incluyen un motor de JavaScript que
soporta el uso de código script en las páginas. También implementan el Document Object Model (DOM), un
estándar W3C que define cómo un navegador debe reflejar una página en memoria para permitir al motor de
script acceder y alterar el contenido de la página.
6.1. Ejecución de código en el lado cliente usando scripts.
HTML permite definir el diseño de nuestras páginas web, pero aparte del elemento <form>no proporciona
nada más para interaccionar con el usuario. Además, el diseño definido usando marcado HTML tiende a ser
bastante estático. Podemos añadir comportamientos dinámicos a una página escribiendo código script. El
código script es un conjunto de instrucciones que se ejecutan por lotes mediante interpretación; esto quiere
decir que no requiere de una compilación previa.
Las páginas HTML permiten embeber código script donde se pueden escribir instrucciones con el lenguaje
JavaScript. Hay varios modos de incluir instrucciones JavaScript en nuestras páginas web, y todas involucran
el elemento <script>:
• Escribir el JavaScript sobre la página como contenido del elemento <script>.
<script type="text/javascript">
alert('Soy una línea de JavaScript');
</script>
• Guardar el JavaScript en un fichero aparte y referenciarlo usando el atributo src del elemento <script>.
<script type="text/javascript" src="alertame.js"></script>
• Referenciar un fichero JavaScript ubicado en otro sitio web.
<script type="text/javascript" src="http://ajax.contoso.com/ajax/jQuery/jquery-1.7.2.js">
</script>
El elemento <script> tiene tres atributos:
Atributo Significado
type Identifica el lenguaje script usado; el valor por defecto estext/javascript.
src Identifica un fichero script para descargar; no hay que usarlo si escribimos código script en
el contenido del elemento.
charset Identifica la codificación de caracteres (por ejemplo, utf-8, Shift-JIS) del fichero script
externo; si no estamos usando el atributo src, no se asigna este atributo.
Siempre hay que especificar ambas etiquetas de <script>, el comienzo y el final, aunque estemos vinculando
un fichero script externo y no tengamos contenido en el elemento.
Es normal para una aplicación web dividir la funcionalidad JavaScript en varios scripts. Además, muchas
aplicaciones web usan ficheros JavaScript externos. El orden en el cual se añaden enlaces a ficheros JavaScript
es importante, y para asegurarnos de que están en el ámbito debemos añadir enlaces a scripts que definen
objetos y funciones antes de los scripts que usan esos objetos y funciones.
Navegadores antiguos no siempre soportan JavaScript, y algunos usuarios pueden deshabilitarlo en los
navegadores modernos. En estos casos, algunas de la características de nuestras páginas web que usen
JavaScript no funcionarán correctamente. Podemos alertar a los usuarios de esto usando el elemento
<noscript>. Este elemento indica a los navegadores que muestren un mensaje, avisando al usuario de que la
página no opera correctamente a menos que habilitemos JavaScript.
<body>
<noscript>Esta página usa JavaScript. Por favor, habilítelo en su navegador</noscript>
...
Resto de la página
...
<script src="MyScripts.js"></script>
</body>
Consejo: En general, es una buena práctica añadir enlaces a scripts como los últimos elementos anidados
dentro del elemento <body>.

41
6.2. Sintaxis de JavaScript.
JavaScript tiene una sintaxis sencilla para escribir instrucciones, declarar variables y añadir comentarios.
Aunque es un lenguaje diferente de Java comparten una sintaxis similar en algunos aspectos. Como Java es un
lenguaje sensible a mayúsculas y minúsculas, pero la mayor diferencia con Java es su forma de tratar con
variables y objetos.
6.2.1. Instrucciones.
Una instrucción es una simple línea de JavaScript. Representa una operación para ser ejecutada. Por ejemplo,
la declaración de una variable, la asignación de un valor, o la llamada a una función.
El siguiente fragmento de código muestra algunos ejemplos de instrucciones:
var unaVariable = 3;
contador = contador + 1;
HazAlgo();
Todas las instrucciones de JavaScript deberían ser escritas en una única línea y terminar con un punto y coma.
La excepción a esta regla es que podemos romper un string muy grande en varias líneas (por legibilidad)
usando la barra diagonal. Por ejemplo:
document.write("Una increible realidad \
es lo grande que es el mundo");
Nota:El finalizador de punto y coma actualmente es opcional. Sin embargo, si no terminamos la
instrucción con punto y coma, JavaScript intenta discernir si debería estar puesto, algunas veces con
resultados inesperados.
Podemos combinar instrucciones en bloques, delimitados entre llaves. Esta sintaxis es usada por funciones,
bloques if, while o for.
Los comentarios en JavaScript son similares a los de Java. JavaScript soporta dos estilos diferentes de
comentar: comentarios de varias líneas que empiezan con /* y finalizan con */, y comentarios de una sola
línea que empiezan con // y finalizan con la línea.
6.2.2. Variables, tipos de datos y operadores.
Hay tres formas de declarar una variable en JavaScript:
1. Dando un nombre y un valor (es la forma menos recomendable).
saludo = "Hola";
2. Declarándola sin dar un valor usando la palabra clave var. Hasta que la variable es asignada a un valor,
JavaScript retornará su valor como indefinido.
var misterio;
3. Combinando ambas formas (que es el estilo más recomendado).
var codigo = "Spang";
A diferencia de Java y otros lenguajes habituales de programación, no podemos especificar el tipo de una
variable en JavaScript. Se declara una variable con la palabra clave var, y entonces JavaScript intenta discernir
el tipo de la variable. JavaScript reconoce tres tipos simples:
1. String (string): Cualquier secuencia de caracteres encerrados entre comillas dobles. Se utilizan caracteres
de escape con la barra diagonal para los caracteres como \" (doble comilla), \' (comilla simple), \; (punto y
coa), \\ (barra diagonal), \& (ampersand). También se usa una barra diagonal para partir un string en varias
líneas.
var simple = "Juan Pérez y José";
var escape = "\"Juan Pérez \& José\"";
var muyLargo = "Este texto es demasiado \
largo para una sola línea.";
2. Número (number): Cualquier número entero o decimal. Si se encapsula un número entre comillas dobles
será tratado como un string.
var answer = 42;
var actuallyAString = "42"; // es tratado como un string
3. Booleano (boolean): Un valor lógico true ofalse.
var canYouReadThis = true;
JavaScript también convierte datos entre tipos, lo cual puede llevar a confusión si no somos cuidadosos. Por
ejemplo, el valor numérico 0 se avalúa a false en expresiones booleanas, así que es importante usar el
operador correcto cuando comparamos los valores de variables.

42
Recordemos que si declaramos una variable sin asignarle valor, la variable queda indeterminada (y retornará el
valor simbólico undefined). Podemos también declarar una variable y asignarle el valor null:
var variableConValorNull = null;
Asignar una variable a null indica que su valor no existe, en vez de que no tiene asignado un valor. Es
importante notar la diferencia.
Podemos determinar el tipo actual de un dato en una variable usando el operador typeof:
var data = 99;
...
if (typeof data == "number") {
// el dato es numérico
}
JavaScript utiliza los mismos operadores habituales que Java y otros lenguajes de programación. Sin embargo,
hay una serie de cuestiones a tener en cuenta con respecto a los operadores JavaScript y cómo convierten
valores entre tipos cuando el intérprete JavaScript ejecuta las expresiones.
• Si añadimos un número a un string, el resultado es un string.
x = 10 + 10; // x es asignado al número 20;
y= "10" + 10; // y es asignado al string "1010";
z = "Ten" + 10; // x es asignado al string "Ten10";
• 0, "" (el string vacío), undefinedynull son evaluados a false en expresiones booleanas. Siempre se usa ===
cuando comparamos cualquiera de estos valores.
var zero = 0;
var emptyString = "";
var falseVar = false;
zero == falseVar; // retorna true;
zero === falseVar; // retorna false;
emptyString == False; // retorna true;
emptyString === False; // retorna false;
6.2.3. Funciones.
La definición de funciones explícitas en JavaScript tiene siempre la misma sintaxis:
function unNombre ( argumento1, argumento2, ..., argumentoN ) {
instrucción1;
instrucción2;
...
instrucciónN;
}
Hay cuatro partes en la definición de una función:
• La palabra clave function indica que comienza la declaración de la función.
• El nombre de la función. Se usa para invocar la función, y es sensible a mayúsculas y minúsculas.
• Una lista de variables separadas por comas, llamadas parámetros, a través de los cuales podemos pasar
valores al interior de la función. Aunque la función no tenga parámetros se deben dejar los paréntesis que
delimitan la lista de parámetros.
• Una serie de instrucciones encapsuladas entre llaves.
Podemos usar el nombre del parámetro dentro de la función como la variable que contiene el valor pasado
como argumento. Pero los argumentos también están disponibles mediante un array llamadoarguments. Se
puede acceder al primer argumento usando la expresión arguments[0], al segundo argumento usando la
expresión arguments[1], etc. Este mecanismo nos da un modo de definir métodos que pueden tener un
número indeterminado de parámetros. Podemos saber cuántos argumentos son pasados consultando el valor
de arguments.length.
Una función que calcula un resultado puede usar la instrucción return para pasarlo como valor de retorno de
la llamada a la función. ¿Qué ocurre cuando el valor es retornado dependiendo de cómo la función es
llamada? Por ejemplo, si queremos calcular el precio a pagar en un hotel, podemos usar la siguiente función e
invocarla:
function CalcularPreciol(numeroDeNoches, precioPorNoche, extras) {
return (numeroDeNoches * precioPorNoche) + extras;
}
...
// en otro sitio del script invocamos la función

43
var TotalCantidad = CalcularPrecio(10, 100, 50);
6.3. Navegación usando JavaScript.
Para navegar a través de las páginas web habitualmente se utilizan hiperenlaces, mediante el elemento HTML
<a />, y formularios, pulsando un botón de tipo submit dentro de un formulario.
Es posible emular estos dos tipos de navegación mediante código JavaScript. La técnica más sencilla es
modificando la página actual de la ventana del navegador. Esto se hace asignando la propiedad location del
objeto window. Por ejemplo:
window.location = "http://www.google.es";
Provoca una redirección automática a la página web de Google. También se puede utilizar:
window.location.href = "http://www.google.es";
Otra forma es utilizando el método navigate() del objeto window, como en el siguiente ejemplo:
window.navigate("otraPagina.html");
Pero el método navigate() no es reconocido por todos los navegadores.
Con estas dos técnicas es posible especificar tanto URLs absolutas como relativas al mismo sitio web.
También podemos emular la pulsación de un botón de formulario mediante código JavaScript. Por ejemplo:
<form action="Default.aspx" method="post">
Un dato <input type="text" name="dato" />
<input type="submit" value="enviar" />
</form>
<script type="text/javascript">
document.forms[0].submit(); // se postean los datos
</script>
La invocación del método subtmit() en el código scriptprovoca
la inmediata llamada al action del formulario.
También se puede emular la pulsación de un enlace invocando su método click():
<a id="enlace1" href="http://www.google.es">google</a>
<script type="text/javascript">
document.getElementById("enlace1").click();
</script>
Este código provoca la inmediata redirección a la URL indicada en el enlace <a />.
6.4. Usando tipos de objetos.
JavaScript permite escribir aplicaciones web orientadas a objetos. Como muchos otros lenguajes modernos, se
permite definir objetos que tienen propiedades, métodos y eventos.
JavaScript proporciona varios tipos de objetos predefinidos, incluyendo:
• El tipo String, que permite manipular cadenas de texto. El tipo String proporciona propiedades como
length, que retorna el número de caracteres del string, y métodos como concat, que podemos usar para
juntar strings, así como métodos para convertir un string a mayúsculas o minúsculas, o buscar un substring
dentro del string.
var eventoSaludo = new String('Bienvenidos a nuestra conferencia');
var len = eventoSaludo.length;
Nótese que se usa el operador new para instanciar variables usando tipos de objetos.
• El tipo Date, que permite trabajar con fechas. JavaScript representa internamente un
dato de fecha como
los milisegundos transcurridos desde el 01/01/1970, y todos los métodos de fecha usan la zona horaria
GMT+0 (UTC), independientemente de que nuestro ordenador muestre una fecha en la zona horaria
apropiada.
var hoy = new Date(1346454000); // Número de milisegundos desde 01/01/1970
var hoy = new Date("September 1, 2012");
var hoy = new Date(2012, 8, 1); // Enero es 0, ..., Diciembre es 11.
• El tipo Array, que permite crear y trabajar con arrays de valores de longitud dinámica
en base cero. Los
arrays proporcionan métodos para buscar elementos dentro del array, para cambiar el orden de los
elementos, o tratar el array como una estructura de pila o cola.
var arrayVacioDeTresElementos = new Array(3);
var arrayDeEstaciones = new Array("Primavera", "Verano", "Otoño", "Invierno");
var terceraEstacion = arrayDeEstaciones[3]; // Invierno
var valores = new Array();
valores.push("Valor 1"); // se ubica en el índice 0
valores.push("Valor 2"); // se ubica en el índice 1

44
valores[valores.length] = "Valor 3"; // se ubica en el índice 2
También podemos usar la notación literal para crear y poblar un array:
var arrayDeEstaciones = ["Primavera", "Verano", "Otoño", "Invierno"];
Podemos determinar si un objeto es miembro de un array usando la función indexOf(). Se debe especificar
el elemento buscado, y la función retorna el índice de la primera aparición del elemento dentro del array, o -
1 si no se encuentra. Por ejemplo, el siguiente código asigna la variable ubicacionOtoño to 2:
var ubicacionOtoño = arrayDeEstaciones.indexOf("Otoño");
• El tipoRegExp, que permite crear y trabajar con expresiones regulares para casar strings. Se usa el método
test() para determinar si un string casa con la expresión regular.
var re = new RegExp("[dh]og");
if (re.test("dog")) {...}
JavaScript también define algunos tipos de objetos singleton. No se usan estos tipos para declarar variables, si
no que se usan las funcionalidades que proporciona el tipo. Los objetos singleton son:
• El objeto Math da acceso a varias constantes (como Pi y E) y funciones matemáticas (como seno, coseno,
raíz cuadrada, y un generador de números seudo-aleatorio) como propiedades y métodos estáticos. Por
ejemplo:
var e = Math.E;
var coseno = Math.cos(45);
var semilla = Math.random();
• El objeto Global contiene funciones y constantes globales para las constantes undefined, NaN, eInfinity.
6.5. Definiendo objetos usando JSON.
La Notación de Objetos JavaScript, o JSON como es conocida normalmente, es una sintaxis para representar
una o más instancias de un objeto y los valores de sus propiedades como un string. La sintaxis básica es como
sigue:
var miObjecto = {
"propiedad1" : "valor1",
"propiedad2" : "valor2",
... ,
"propiedadN" : "valorN"
};
Por ejemplo, podemos crear un objeto Asistente que represente los asistentes de una conferencia, como sigue:
var unAsistente = {
"nombre" : "Juan Pérez",
"puesto" : "1"
};
Hay unas pocas reglas básicas:
• Los nombres de propiedades y valores se separan con dos puntos.
• Cada para nombre-valor se separa con comas.
• Todos los nombres de propiedades y valores se encapsulan con comillas dobles.
• Están prohibidas los comas en matrices y objetos.
• La lista de propiedades se encapsula entre un par de llaves.
También se puede crear un objeto sin ninguna propiedad y posteriormente añadírselas:
var unAsistente = {};
unAsistente.nombre = "Juan Pérez";
unAsistente.puesto = 1;
Convertir un objeto JSON a su representación string se denomina serialización a formato JSON. Es fácil
realizar esta serialización usando el método JSON.stringify():
var str = JSON.stringify(unAsistente);
El proceso inverso, la deserialización, permite recuperar un objeto JSON a partir de su formato serializado.
Para esto se utiliza el método JSON.parse():
var copiaAsistente = JSON.parse(str);
Una colección de objetos serializados se indica mediante una lista separada con comas de objetos serializados
encerrados entre un par de corchetes. Por ejemplo, aquí tenemos dos objetos Asistente serializados en JSON.
var listaDeAsistentes = [
{ "nombre": "Juan Pérez", "puesto": "1" },
{ "nombre": "Martín López", "puesto": "2" }
];

45
JSON ha visto incrementada su importancia como el formato de facto para pasar datos en solicitudes AJAX
entre una página web y un servidor web.
6.6. El modelo de objetos del documento (DOM).
Todos los navegadores actuales implementan un modelo de objetos llamado DOM, definido por W3C para
representar la estructura de una página web. DOM proporciona una API programática, permitiendo escribir
código JavaScript que realice tareas comunes como encontrar el título de la página, cambiar el contenido de
una lista, añadir nuevos elementos a la página, y mucho más. DOM define las propiedades que un script
puede cambiar para un elemento de una página, y qué acciones podemos hacer sobre el documento.
6.6.1. Búsqueda de elementos.
Después de que una página ha sido cargada, una acción común es encontrar un elemento o un conjunto de
elementos para consultarlo o manipularlo. Por ejemplo, podemos necesitar obtener una referencia a una lista
para poblarla con elementos recuperados de un servicio web. DOM representa varias partes de un
documento como un conjunto de arrays:
• El arrayforms contiene detalles de todos los formularios del documento.
• El array images contiene todas las imágenes del documento.
• El arraylinks contiene todos los híper-enlaces del documento.
• El arrayanchors contiene todas las etiquetas <a>del documento con un atributo name.
• El arrayapplets contiene todos los applets del documento.
Todas estas colecciones son propiedades hijas del objeto document. Este objeto representa al documento.
Podemos usar la notación del punto para acceder a cada colección, a cualquier propiedad, método o evento
definido en DOM. Para acceder a elementos individuales de una colección, se puede usar índice o una
referencia al valor del atributo name. Por ejemplo, si tenemos el siguiente formulario en una página:
<form name="contactForm">
<input type="text" name="nameBox" id="nameBoxId" />
</form>
Podemos acceder al objeto DOM que representa al formulario de las siguientes formas:
document.forms[0] // forms es un array en base cero
document.forms["contactForm"]
document.forms.contactForm
document.contactForm
También se puede acceder al objeto DOM que representa la caja de texto dentro del formulario de varias
formas, incluyendo las siguientes:
document.forms.contactForm.elements[0]
document.forms.contactForm.elements["nameBox"]
document.forms.contactForm.nameBox
document.contactForm.nameBox
Alternativamente, DOM define métodos en el objeto document que recuperan elementos según el valor de
sus atributos ID yname. Podemos usar estos métodos para encontrar rápidamente elementos determinados,
sin tener que recorrer toda la ruta de dependencias de los elementos. Estos métodos incluyen:
• document.getElementById( IdString ), que retorna el elemento cuyo atributo ID coincide con el argumento.
• document.getElementsByName( NameString ), que retornar un array de elementos cuyos atributos name
coinciden con el argumento.
El siguiente ejemplo muestra cómo obtener una referencia a un cuadro de texto nameBoxId:
var userNameBox = document.getElementById("nameBoxId");
var username = userNameBox.value;
6.6.2. Añadir, eliminar y manipular objetos en DOM.
Después de encontrar un elemento o conjunto de elementos, el siguiente paso es normalmente modificarlos
de alguna forma, añadiendo nuevos elementos hijos, modificando los elementos existentes o eliminándolos.
El API DOM Core define varios métodos para crear nuevos objetos en un documento, incluyendo:
• document.createElement(nombreEtiqueta)
• document.createTextNode(string)
• document.createAttribute(nombre, valor)
• document.createDocumentFragment
Todos estos métodos crean un objeto de nodo DOM, el cual es una representación genérica de un elemento,
texto o atributo en DOM. Después de crear el nodo DOM, hay que añadirlo al modelo. El modo más simple

46
es usando el método document.getElementbyId() para recuperar el elemento contenedor en el cual queremos
incluir nuestro nodo, y entonces usar uno de los siguientes métodos sobre este elemento contenedor:
• appendChild(nuevoNodo), añade un nuevo nodo como el último hijo del elemento seleccionado.
• insertBefore(nuevoNodo, nodoExistente), añade el nuevo nodo en el modelo DOM después del nodo
existente, pero como un nodo hermano.
• replaceChild(nuevoNodo, nodoExistente), reemplaza el nodo existente por el nuevo nodo.
• replaceData(desplazamiento, longitud, string), reemplaza el texto en un nodo de texto seleccionado. El
parámetro desplazamiento especifica a partir de qué carácter se aplica el reemplazo, longitud indica cuántos
caracteres se reemplazan, y string especifica el texto a insertar.
Para usar estos métodos efectivamente y asegurar el destino donde añadir nuevos nodos, necesitamos
también movernos a través del árbol del documento. Podemos usar las siguientes propiedades para navegar a
través de DOM:
• childNodes, retorna los nodos hijos de un nodo seleccionado.
• firstChild, retorna el primer hijo de un nodo seleccionado.
• lastChild, retorna el último hijo de un nodo seleccionado.
• nextSibling, retorna el siguiente nodo hermano de un nodo seleccionado.
• parentNode, retorna el nodo contenedor de un nodo seleccionado.
• previousSibling, retorna el anterior nodo hermano de un nodo seleccionado.
El siguiente ejemplo muestra cómo modificar una lista de una página:
<!-- Marcado HTML -->
<ul id="ListaHotel">
<li>Habitación A</li>
<li>Habitación B</li>
</ul>
<script>
// Código JavaScript para modificar los elementos de la lista ListaHotel
var list = document.getElementById("ListaHotel");
// Crea una nueva habitación
var newItem = document.createElement("li");
newItem.textContent = "Habitación C";
// Añade la nueva habitación al final de la lista
list.appendChild(newItem);
</script>
DOM también define métodos para quitar nodos del árbol del documento, incluyendo:
• removeChild(nodo), eliminar el nodo destino.
document.removeChild( document.getElementById("ListaHotel").firstChild );
• removeAttribute(nombreAtributo), elimina el atributo destino del nodo elemento.
var list = document.getElementById("ListaHotel");
list.removeAttribute("id");
• removeAttributeNode(nodo), elimina el nodo atributo destino del nodo elemento.
var list = document.getElementById("ListaHotel");
list.removeAttribute(list.attributes[0]);
Para limpiar un nodo de texto en vez de quitarlo completamente, se puede asignar a un string vacío.
6.6.3. Manejando eventos de DOM.
HTML define varios eventos desencadenados por el usuario que podemos asociar a una acción JavaScript.
Por ejemplo, cuando un usuario mueve el ratón por encima de un icono de ayuda, se lanza el evento
mouseover. Cuando se lanza un evento, si hemos asignado un oyente para el evento, el navegador lo ejecutará.
Muchos elementos HTML proporcionan devoluciones de llamada que podemos usar para capturar varios
eventos y definir código que actúe como un oyente. Estas devoluciones de llamada normalmente tienen el
nombre onNombreEvento donde NombreEento es el nombre del evento. Por ejemplo, podemos manejar el
evento mouseover de un elemento <img>usando la devolución de llamada onmouseover, tal como sigue:
<img src="helpicon.gif" id="helpIcon" onmouseover="window.alert('Algún texto de ayuda');" />
Nota: Una devolución de llamada es una referencia a una función que se ejecuta como resultado de que
se complete otra acción. En este caso de manejar eventos para un elemento HTML, el navegador provoca
la devolución de llamada cuando se desencadena el evento correspondiente.

47
También podemos asignar el manejador de evento como una propiedad del nodo que representa la imagen en
DOM, tal como sigue:
document.images.helpIcon.onmouseover = function() { window.alert('Algún texto de ayuda'); };
Este ejemplo muestra una instancia de una función anónima; la palabra clave function es seguida por un par
de paréntesis y el código encapsulado entre llaves. No se requiere el nombre de la función porque la función
sólo será invocada cuando se desencadene la devolución de llamada onmouseover.
Sin embargo, este método sólo permite asignar un oyente al evento. El modelo DOM también permite añadir
y quitar dinámicamente varios oyentes de eventos en elementos del documento usando los siguientes
métodos:
• addEventListener(nombreEvento, funcionOyente, propagacion), añade la función oyente al elemento asociado
al evento referenciado. Podemos pasar la función oyente como una función con nombre o como una
función anónima.
var helpIcon = document.getElementById("helpIcon");
// Añadimos un oyento de evento para el evento mouseover usando una función
function MuestraAyuda()
{
window.alert('Algún texto de ayuda');
}
helpIcon.addEventListener("mouseover", MuestraAyuda, false);
// Alternativamente, podemos usar una función anónima
helpIcon.addEventListener("mouseover", function() { window.alert('Algún texto de ayudat'); }, false);
• removeEventListener(nombreEvento, funcionOyente, propagacion), quita una función oyente del elemento
para el evento referenciado.
helpIcon.removeEventListener("mouseover", MuestraAyuda, false);
Algunos eventos en DOM se propagan, significando esto que si el evento ocurre sobe un elemento (y es
manejado o no), el evento se disparará también sobre el elemento contenedor, y se irá propagando por los
elementos contenedores hasta el evento sea rechazado por uno de ellos. Tanto addEventListener
comoremoveEventListener tienen un tercer parámetro opcional que indica si debe propagarse o no el evento.
El siguiente ejemplo muestra cómo enlazar un pequeño script, el cual copia el contenido de una caja de texto
a una zona de la pantalla, cuando hace clic sobre un botón de formulario.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<title>Enlazando eventos con JavaScript</title>
</head>
<body>
<form>
<p><label>Escribe tu nombre: <input type="text" id="NameBox" /></label></p>
<input type="button" id="submit" value="Púlsame para enviar" />
</form>
<div id="areaDeGracias"></div>
<script type="text/javascript">
function diGraciasu() {
var userName = document.getElementById("NameBox").value;
var gracias = document.createElement("p");
gracias.textContent = "Gracas " + userName;
document.getElementById("areaDeGracias").appendChild(gracias);
}
document.getElementById("submit").addEventListener("click", diGracias);
</script>
</body>
</html>
6.7. Validando campos mediante JavaScript
Los tipos de campos y atributos de HTML5 son útiles para realizar validación simple de los datos. Sin
embargo, para tipos de validación más complejos, o donde se necesite referenciar varios campos en un
proceso de validación, podemos necesitar usar código JavaScript.

48
Podemos usar manejadores de eventos para validar campos de formulario con JavaScript. Cuando el usuario
confirma un formulario, se lanza el evento submit, y podemos capturarlo para validar los datos antes de que
sean enviados al servidor para su procesamiento. Si la validación sucede, el manejador del evento debe
retornar true y el formulario enviará los datos; si la validación falla, el manejador del evento debe retornar
false para anular la confirmación.
El siguiente ejemplo de código muestra un formulario que ejecuta el método validarEdadcuando el usuario
postea los datos. El atributo onsubmit del formulario especifica el código JavaScript que será ejecutado
cuando ocurra el evento submit:
<body>
<form id="registrationForm" method="post" action="registro.aspx"
onsubmit="return validarEdad();" >
Tu edad <input type="number" id="edad" name="edad" />
<input type="submit" value="Enviar" />
</form>
<script type="text/javascript">
function validarEdad() {
var edad = document.getElementById("edad").value;
return (edad >= 18);
}
</script>
</body>
En este ejemplo se valida que la edad introducida por el usuario sea mayor que 17. En otros escenarios la
función referenciada en el atributo onsubmit debería examinar cada campo para validarlo. Si algún campo
fallase, la función retornará false, y si no retornará true. Esta forma de validar permite realizar una
comprobación más comprensiva de cada campo, y permite validar datos entre campos.
Cada campo de un formulario también lanza un evento input, y podemos capturarlo para realizar sólo la
validación sobre cada campo de forma independiente, en vez de sobre todo el formulario. Esta validación
ocurre a medida que el usuario introduce los datos, y si los datos no son válidos podemos asignar el indicador
CustomValidity para decirle al formulario que no puede enviar los datos hasta que sean corregidos. Por
ejemplo, si el formulario contiene un campo con un id deconfirmar-edad, podemos validar el dato introducido
en el campo de la siguiente manera:
<body>
<form id="registrationForm" method="post" action="registro.aspx">
Tu edad <input type="number" id="edad" name="edad" oninput="validarEdad();" />
<input type="submit" value="Enviar"/>
</form>
<script type="text/javascript">
function validarEdad() {
var campo = document.getElementById("edad");
if (edad >= 18) {
campo.setCustomValidity("");
} else {
campo.setCustomValidity("Debe ser mayor de edad");
}
}
</script>
</body>
En este ejemplo, si la edad introducida es menor que 18 la función setCustomValidity()muestra un mensaje de
error personalizado e impide que los datos sean enviados al servidor. Si el dato es válido, se pasa un string
vacío a la función setCustomValidity()reiniciando el manejador del error, y los datos serán enviados al servidor.

PRÁCTICA
01.Cread una aplicación Web con una página inicial ("index.html") donde se muestre un formulario que
permita introducir dos números y seleccionar una operación (como mínimo suma, resta, multiplicación y
división).

49
Utiliza código JavaScript para gestionar el clic sobre cada botón de operación. Una función de JavaScript
debe encargarse de recuperar los valores escritos en los controles de edición, realizar la operación
correspondiente y mostrar el resultado.
Incluye opciones de validación para por si falta uno de los operandos o si la operación no es posible.

02. Cread una aplicación web que permita añadir nombres de producto a una lista no ordenada:

Si hay algo escrito en el cuadro de edición, al pulsar el botón «Agregar a la lista» hay que añadir el nuevo
producto al final de la lista posterior. Usa código JavaScript para hacer esto, almacenando los productos en un
array.
Al pulsar el botón «Enviar al servidor» hay que enviar el array en formato JSON al servidor Web. Para ello
crea una página JSP llamada "productos.jsp", y úsala para postear el array de productos. En la página
"productos.jsp" simplemente muestra el array en su formato serializado.

50
UNIDAD 12. LA TECNOLOGÍA SERVLET
1. Fundamentos sobre Servlets
Los servlets son clases de Java que se ejecutan en un servidor como respuesta a una solicitud desde una
página web en un ordenador cliente, y que (aparte de otros procesos) normalmente generan una página web
de respuesta en el ordenador cliente.
1.1. El contenedor web.
Un contenedor web (o también llamado contenedor de servlets) es un módulo que permite mantener la
arquitectura multicapa de Java EE. Aquellos servidores web que soportan la plataforma Java EE, como
Apache Tomcat, implementan este módulo.
Este módulo especifica dentro de un servidor un entorno de ejecución para componentes web que incluye
seguridad, concurrencia, gestión del ciclo de vida, procesamiento de transacciones, despliegue y otros
servicios. Un contenedor web suministra los mismos servicios que un contenedor de páginas JSP así como de
otros tipos de recursos de la plataforma J2EE.
Un modelo muy simplificado de cómo funciona un contendor web viene dado por el siguiente escenario:
• Una página web, que contiene un formulario, es mostrada en un navegador cliente (como «Internet
Explorer» o «Chrome»).
• El usuario rellena el formulario y pulsa el botón para postear los datos. Los datos son posteados mediante
una solicitud Post o Get, y enviados a un recurso especificado por la ruta de la solicitud.
• En el servidor web, el contendor web determina el servlet asociado a la ruta solicitada.
•El servlet recupera los datos posteados, los procesa y genera una página web de respuesta hacia el
navegador cliente.
Figura 1

1.2. El API Servlet.


Dentro del modelo cliente/servidor de la tecnología Web de Java EE, los servlets son las piezas
fundamentales para poder generar recursos dinámicos. Son capaces de recuperar cualquier información
enviada por un cliente y generar una respuesta dinámicamente.
Al tratarse de clases de Java se benefician de la gran capacidad de Java para ejecutar métodos en ordenadores
remotos, para conectarse a base de datos, aplicar seguridad en la información, etc.Además son ejecutados por
el Servidor Web, el cual controla su ciclo de vida, y por tanto garantiza que el código que ejecute el servlet se
hará de forma segura en el contexto general de ejecución del servidor web.
Además de lo dicho, los servlets aportan las siguientes características:
• Son independientes del servidor y de su sistema operativo. La independencia de plataforma es una de las
características del lenguaje Java.
• Un servlet puede invocar a otro servlet. De esta forma se puede distribuir de forma más eficiente la carga
de trabajo que supone procesar una solicitud HTTP.
• Pueden obtener fácilmente información acerca del cliente (la que le permite el protocolo HTTP), tal como
su dirección IP, el puerto, el método de llamada, el tipo de navegador, etc.
• Permite gestionar cookies y sesiones, de forma que se puede guardar información en el lado cliente
(mediante cookies) y en el lado servidor (mediante variables de sesión).

51
• Permiten interactuar con bases de datos en arquitecturas de 3 capas. Esto habilita que las solicitudes se
puedan adaptar a operaciones sobre una base de datos.
• Al poder generar una respuesta de forma dinámica, permiten crear contenedores que aporten una
estructura común a las páginas, o trozos reutilizables para embeber en varias páginas.
La tecnología de los servlets está basada en dos paquetes:
•javax.servlet. Este paquete define 6 interfaces y 3 clases para la implementación de servlets genéricos, sin
especificación de protocolo.
•javax.servlet.http. Define clases específicas para el protocolo http (el usado para navegar por páginas
web).
1.2.1. La clasebase «HttpServlet».
Los servlets que se utilizan en aplicaciones Web HTTP deben ser clases que extiendan a la clase base
javax.servlet.HttpServlet. El siguiente trozo de código muestra los paquetes que se utilizan normalmente al
crear un servlet:
import javax.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
public class MiServlet extends HttpServlet {
....
}
Para dotar de funcionalidad a un servlet se han de redefinir una serie de métodos de la clase base:
▪ public void init (ServletConfig config)
Se ejecuta la primera vez que el servidor web debe instanciar al servlet. A través del argumento config se
accede a los parámetros de inicialización del servlet que se establecieron al configurarlo. El método
config.getServletContext() retorna un objeto de tipo ServletContext, que permite acceder a la información
del servidor web.
Importante. Este método debe completar su ejecución antes de que el servlet pueda recibir cualquier
solicitud. El contenedor de servlets no podrá activar el servlet para que gestione una solicitud si el método
init() lanza una ServletException, o si no se ejecuta en un periodo de tiempo definido por el servidor web.
▪ public void destroy ()
Será llamado por el servidor web cuando la instancia del servlet esté a punto de ser descargada de
memoria. En este método se han de realizar las tareas necesarias para conseguir una finalización
apropiada, como cerrar archivos y flujos de entrada de salida externos a la petición, cerrar conexiones
persistentes a bases de datos, etc.
▪ public void service (ServletRequest request, ServletResponse response)
El método service() se ejecuta cada vez que el servlet es llamado y sus argumentos nos permiten obtener
información de la petición (request) y un flujo de salida para escribir la respuesta (response). Según el
método de solicitud HTTP, este métodoservice(), a su vez, invoca alguno de los siguientes métodos.
▪ protected void doGet (HttpServletRequest request, HttpServletResponse response)
▪ protected void doPost (HttpServletRequest request, HttpServletResponse response)
▪ protected void doTrace(HttpServletRequest reques, HttpServletResponse response)
▪ protected void doOptions(HttpServletRequest request, HttpServletResponse response)
▪ protected void doDelete(HttpServletRequest request, HttpServletResponse response)
▪ protected void doPut(HttpServletRequest request, HttpServletResponse response)
▪ protected void doHead(HttpServletRequest request, HttpServletResponse response)
Cada uno de estos métodos se ejecuta en respuesta a un tipo de solicitud HTTP concreto. Por ejemplo,
si se utiliza el verbo POST será invocado el método doPost(), si se utiliza el verbo DELETE será invocado el
método doDelete(), etc.
El método service() es el encargado en primer lugar de procesar una solicitud. Pero como ya incorpora una
funcionalidad para redirigir la solicitud a uno de los métodos do_(), se recomienda no reescribir service() y
reescribir los métodos do_() apropiados para procesar el tipos de solicitudes que nos interese.
1.2.2. Interfaz «HttpServletRequest».
La interfaz java.servlet.http.HttpServletRequest proporciona los métodos para recuperar la información de la
petición desde el navegador cliente.
Métodos de HttpServletRequest:
▪ String getHeader(String cabecera)
Devuelve el valor de la cabecera http indicado de la página web de llamada o null si no se suministró.

52
▪ Cookie[] getCookies()
Devuelve una array con las "cookies" del cliente.
▪ Enumeration getHearderNames()
Obtiene una enumeración de todos los nombres de cabecera de la página web de esta petición particular.
▪ int getMethod ()
Devuelve el método de petición principal (normalmente GET o POST, pero son posibles cosas como
HEAD, PUT, y DELETE).
▪ URI getRequestURI ()
Devuelve la URI (la parte de la URL que viene después del host y el puerto, pero antes de los datos de
los parámetros enviados).
▪ String getRemoteHost ()
Devuelve el nombre del ordenador que realizó la petición.
▪ String getParameter (String parámetro)
Devuelve el primer valor asociado al parámetro indicado o null si dicho parámetro no existe.
▪ String[] getParameterValues(String parámetro)
Devuelve un array con los valores asociados al parámetro especificado o null si dicho parámetro no
existe.
▪ Enumeration getParameterNames ()
Devuelve una enumeración de los nombre de los parámetros empleados en la petición.
1.2.3. Interfaz «HttpServletResponse».
La interfaz java.servlet.http.HttpServletResponse proporciona métodos para realizar la respuesta al cliente que
originó la petición.
Métodos de HttpServletResponse:
▪ PrintWriter getWriter ()
Obtiene un stream a través del cual transmitir la página web de respuesta al cliente.
▪ void setContentType (String tipo)
Permite establecer el tipo MIME (tipo de documento) de la respuesta.
▪ void setContentLenght (int len)
Permite especificar la longitud de la respuesta. Si no se indica el propio motor de servlets calculará la
longitud de la respuesta.
1.3. Creación y registro de un servlet.
Para que los servlet puedan ser utilizados como recursos dinámicos en una aplicación web deben ser
registrados en el contenedor web. Para ver cómo se hace esto crearemos primero un proyecto web llamado
"WebAplicación" usando NetBeans, y le añadiremos el archivo descriptor "web.xml".
Figura 2

En la página inicial index.html se ha añadido un enlace <a /> a través del cual podremos invocar un servlet.
1.3.1. Cómo añadir un servlet a un proyecto web.
Comenzaremos añadiendo un servlet al proyecto "WebAplicación" usando la plantilla que incorpora NetBeans.
Al ser clases de Java, los servlets deben crearse en el nodo «Source packages». Sobre este nodo hay que abrir
el menú contextual y pulsar sobre «Nuevo|Servlet». Si no aparece la opción «Servlet» hay que seleccionar la
opción «Otros» y en la categoría «Web» seleccionar la plantilla «Servlet».

53
Figura 3

En el cuadro de diálogo «Nuevo Servlet» pondremos como nombre de la clase "ServletSaludo", y la


ubicaremos en un nuevo paquete con el nombre "servlet".
Figura 4

Tras pulsar el botón «Siguiente», el asistente nos ofrece las opciones para configurar y registrar el servlet en el
contenedor web.
1.3.2. Registro de un servlet en el archivo descriptor.
El mecanismo tradicional para registrar un servlet es hacerlo en el fichero descriptor de la aplicación. En el
cuadro de diálogo de «Nuevo Servlet» simplemente tenemos que marcar la opción «Add information to
deployment descriptor (web.xml)». Si existe el fichero /WEB-INF/web.xml se registrará la información que
establezcamos en este asistente, pero si no existe el servlet no será registrado en el contenedor web.

54
Figura 5

Para configurar un servlet debemos proporcionar un nombre de servlet (como este nombre se utiliza para
información interna de registro podemos utilizar el propio nombre de la clase), y un patrón URL. Este patrón
URL es la ruta de la URI que mapeará las solicitudes con el servlet. Para este ejemplo se ha asignado /saludo.
Por motivos que se explicarán a continuación es importante que un patrón URL comience con una barra /.
Tras pulsar el botón «Finalizar» se creará el servlet y quedará registrado en el fichero descriptor. El fichero
web.xml quedará como sigue:
<?xml version="1.0" encoding="UTF-8"?>
<web-app …………… >
<servlet>
<servlet-name>ServletSaludo</servlet-name>
<servlet-class>servlet.ServletSaludo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletSaludo</servlet-name>
<url-pattern>/saludo</url-pattern>
</servlet-mapping>
………………….
</web-app>
Como se ve, la clase del servlet se registra con una etiqueta <servlet>, donde se especifica el nombre del
servlet y su nombre de clase cualificado (incluyendo los paquetes donde está contenida).
La etiqueta <servlet-mapping>se encarga de mapear un servlet declarado en <servlet> con su patrón URL (la
ruta a la que quedará asociado). Pero un mismo servlet puede tener asociados varios elementos <servlet-
mapping>, esto quiere decir que podemos asociar un servlet con varios patrones URL. Por ejemplo,
añadiremos el código necesario para asociar también el servlet ServletSaludo con la URI "/saluda".
<?xml version="1.0" encoding="UTF-8"?>
<web-app …………… >
<servlet>
<servlet-name>ServletSaludo</servlet-name>
<servlet-class>servlet.ServletSaludo</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ServletSaludo</servlet-name>

55
<url-pattern>/saludo</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>ServletSaludo</servlet-name>
<url-pattern>/saluda</url-pattern>
</servlet-mapping>
………………….
</web-app>
Cada una de las rutas especificadas en el patrón URL podrá ser usada en las páginas para invocar el servlet.
1.3.3. Registro de un servlet mediante anotaciones.
Desde el JDK 7 también se pueden registrar los servlets mediante anotaciones en la propia clase. Para
conseguir esto, en el cuadro de diálogo de «Nuevo Servlet» simplemente tenemos que dejar desmarcada la
opción «Add information to deployment descriptor (web.xml)».
Figura 6

Para ese ejemplo, en el fichero "ServletSaludo.java"se añadirán las siguientes anotaciones sobre la clase:
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "ServletSaludo", urlPatterns = {"/saludo"})
public class ServletSaludo extends HttpServlet {
……………….
}
La anotación javax.servlet.annotation.WebServlet posee los siguientes atributos:
Atributo Descripción
name Un string con el nombre del servlet. Este nombre es usado internamente por el
contenedor web y se corresponde con la etiqueta <servlet-name>.
urlPatterns Un array de strings con los patrones URL asociados con el servlet.
loadOnStartup Un entero que indica el orden de carga del servlet.
56
initParams Un array de objetos @WebInitParam con parámetros de inicialización del servlet.
asyncSupported Un valor booleano que indica si el servlet soporta operaciones asíncronas.
smallIcon Un string con la ruta con un icono pequeño para representar el servlet.
largeIcon Un string con la ruta con un icono grande para representar el servlet.
description Un string con una descripción larga del servlet.
displayName Un string con una descripción corta del servlet.
Si queremos que el servlet también mapee con la ruta "/saluda" debemos modificar la anotación WebServlet de
la siguiente manera:
@WebServlet(name = "ServletSaludo", urlPatterns = {"/saludo", "/saluda"})
1.3.4. Reglas acerca del mapeado de patrones URL.
Los patrones URL que mapean rutas con los servlets deben cumplir con las siguientes reglas:
• Para que el servlet case con una URI exacta debe empezar siempre con una barra (/). Por ejemplo:
/gestion/Informes.do CORRECTO
gestion/Informes NO CORRECTO
• Para que el servlet case con cualquier archivo de una carpeta (real o virtual) se puede usar el asterisco (*)
como caracter comodín.
/gestion/* CORRECTO
gestion/* NO CORRECTO
• Para que el servlet case con cualquier archivo (real o virtual) con una extensión determinada también
podemos usar el caracter comodín asterisco (*). Si se usa el asterisco al inicio de la URI, ésta no debe
empezar con una barra (/). Por ejemplo:
*.do CORRECTO
/*.do NO CORRECTO
Importante. Un url-pattern sólo puede incluir un caracter asterisco (*), y éste sólo puede aparecer al
principio o al final de la URI. Por tanto no es correcta la URI: /gestion/*/Informe
Los patrones URL usados para mapear un servlet se pueden corresponder con rutas físicas existentes o con
rutas virtuales no existentes. Por ejemplo, si un servlet declara el siguiente patrón URL:
<url-pattern>/gestion/*</url-pattern>
Podemos usar la siguiente URI para invocar el servlet:
http://localhost:8080/gestion/Informe1.jsp
Tanto la carpeta gestion como el archivo Informe1.jsp pueden o no existir en el disco del servidor. Sin
embargo, para el contenedor web es importante saber cuál es la localización virtual de un servlet,
independientemente de la ubicación física del fichero .class. Por ello el patrón URL exacto de un servlet debe
comenzar con la barra /, para indicar al contenedor web que la ruta comienza desde la carpeta raíz de la
aplicación. Esto es importante cuando hagamos redirecciones desde un servlet usando rutas relativas.
Para el mapeado de una URI solicitada con el patrón URL se aplican las siguientes reglas:
1) Cuando se invoca un servlet mediante una URI, el contenedor de servlets mira primero por un patrón
que case exactamente, y si no lo encuentra mira por un patrón que case una carpeta, y si no existe mira por
un patrón que case una extensión.
2) Si una solicitud de URI casa con más de un patrón de carpeta, el contenedor de servlets elige el que sea
más específico. Por ejemplo, si se han definido los dos siguientes patrones:
<url-pattern>/foo/bar/*</url-pattern>
<url-pattern>/foo/*</url-pattern>
Y se invoca la ruta /foo/bar/miRecurso, entonces el contenedor elige al primer patrón porque es más
específico que el segundo.
1.4. Ejecución de un servlet.
Siguiendo con el proyecto de ejemplo previo, vamos a analizar la clase "ServletSaludo" y ver cómo ejecutar el
servlet. Una vez eliminados los comentarios, el servlet creado previamente contiene el siguiente código:
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;

57
import javax.servlet.http.HttpServletResponse;
public class ServletSaludo extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<title>Servlet ServletSaludo</title>");
out.println("</head>");
out.println("<body>");
out.println("<h1>Servlet ServletSaludo at " + request.getContextPath() + "</h1>");
out.println("</body>");
out.println("</html>");
}
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "Short description";
}
}
La plantilla generada por NetBeans reescribe por defecto los método doGet() y doPost(), para que servlet
pueda procesar solicitudes GET y POST. Si no queremos que el servlet procese uno de los verbos HTTP basta
con no reescribir el método do_() correspondiente. Como una facilidad, la plantilla generada para la clase hace
que tanto doGet() como doPost() ejecuten el mismo código de proceso haciendo una llamada a un método
protegido processRequest().
Se pueden reescribir otros métodos de proceso como doDelete(), doHead(), etc. Todos estos métodos de
proceso reciben dos objetos como parámetro:
• Un objeto HttpServletRequest, que nos dará acceso a cualquier información recibida con la solicitud.
• Un objeto HttpServletResponse, que nos dará acceso a un canal a través del cual enviar una respuesta
dinámica al navegador. También proporciona métodos para redirigir la solicitud y agregar datos a la
respuesta.
El método processRequest() recibe estos dos parámetros, y los utiliza para generar de forma dinámica, como
respuesta, una sencilla página HTML. A continuación se comenta el significado de las instrucciones de este
método:
• response.setContentType("text/html;charset=UTF-8")
El primer paso para generar una respuesta dinámica desde un servlet es especificar el tipo de contenido o
tipo MIME que se enviará al navegador. En este caso se especifica que el navegador recibirá un documento
HTML con la codificación de caracteres UTF-8.
•PrintWriter out = response.getWriter()
Para poder enviar una respuesta dinámica al navegador cliente hay que recuperar un canal de tipo stream a
través del cual podamos escribir el contenido. Tanto el método getWriter() como getOutputStream() del
objeto HttpServletResponse permiten obtener este canal.
• out.println("<!DOCTYPE html>")
Se utiliza el canal de escritura para enviar el contenido de una página HTML con sus etiquetas
constituyentes.

58
Una vez enviada la etiqueta de cierre </html> es necesario cerrar el canal de escritura. En este código el
bloque try () {} se encarga de invocar automáticamente el método close() del stream de escritura.
El código de ejemplo de este servlet simplemente envía al navegador una página con un mensaje. Veamos
ahora cómo ejecutar el servlet.
La opción más directa para probar un servlet es ejecutarlo directamente usando el menú «Ejecutar fichero» o
pulsando las teclas Mayus+F6:
Figura 7

Se abre un cuadro de diálogo que nos permite escribir la ruta con la que queramos invocar el servlet con el
método GET HTTP. Tras aceptar se ejecutará el servlet y su resultado se mostrará en un navegador web:
Figura 8

También podemos ejecutar el servlet invocándolo a través de un enlace. Si ejecutamos la aplicación web se
abrirá en el navegador la página index.html, desde la cual podemos navegar al servlet pinchando en el enlace:

59
Figura 9

1.5. Ruta de contexto para localizar servlets.


El proceso de solicitud de un servlet implica que el servidor web debe saber localizar el servlet. A esto se le
denomina enrutamiento de la petición al servlet, y consiste de dos pasos:
• El contenedor de servlets identifica la aplicación web a la que pertenece la petición.
• Luego se localiza el servlet (perteneciente a la aplicación web) apropiado para manejar la petición.
Ambos pasos requieren que el contenedor de servlets divida la URL de la petición en tres partes:
• Context path (ruta de contexto): El contenedor de servlets intenta concordar la mayor parte posible de la
URI con el nombre de una aplicación web disponible. Si no existe concordancia, se asocia la petición con la
aplicación web por defecto.
Para el servidor Tomcat la aplicación web por defecto es la que está instalada en la carpeta webapps/ROOT.
• Servlet path (ruta del servlet): El contenedor de servlets intenta concordar la mayor parte del resto de la
URI con un patrón URL definido para el servlet. Si no se encuentra ninguna concordancia, se retorna una
página de error.
• Path info (información de ruta). Es el resto de información de la URI, es decir, el resto luego de haber
determinado el servlet que manejará la petición. En caso de que la URI posea parámetros, el path info es
todo el resto de la misma hasta la aparición del primer carácter '?', el cual indica el comienzo de la cadena de
consulta con los parámetros.
La interfazjavax.servlet.http.HttpServletRequest provee tres métodos para obtener esta información:
getContextPath(), getServletPath(), getPathInfo(). Además, el método getQuerySting() permite obtener la parte
correspondiente a la cadena de consulta.
Como ejemplo supongamos que en la aplicación "WebAplicacion" creamos un servlet asociado al patrón URL
"/saludo/*". Si recibimos una solicitud como:
http://localhost:8084/WebAplicacion/saludo/don?nombre=Pedro
Obtendremos la siguiente información de la URI:
getContextPath() = "/WebAplicacion"
getServletPath() = "/saludo"
getPathInfo() = "/don"
getQuerySting() = "nombre=Pedro"
Una vez identificada la ruta de contexto, el contenedor de servlets sigue un algoritmo para determinar el
servlet que manejará la petición. Este algoritmo verifica, en ciertos pasos ordenados, la concordancia de la
URI con los patrones URL de los servlets. Una vez hallada la concordancia el algoritmo finaliza. Los pasos
que se siguen son:
• El contenedor intenta coincidir la URI completa. En este caso considera que el path info es null.
• Se intenta coincidir la URI recursivamente a través del separador / quitando elementos de atrás para
adelante. Si hay una coincidencia, la parte que concuerda es el servlet path y la parte restante es el path info.

60
• Si el último nodo de la URI contiene una extensión, el contenedor de servlets intenta encontrar un servlet
que maneje todas las peticiones con esa extensión. En este caso, la URI completa es el servlet path, y el path
info es null.
• Si el contenedor sigue sin encontrar una coincidencia, envía la petición al servlet por defecto. Si no existe
un servlet por defecto, se devuelve un mensaje de error.

2. Contexto de solicitud y respuesta de los servlets


Un servlet existe dentro de un contenedor web, el cual se encarga de redirigir solicitudes hacia los métodos de
proceso del servlet. El contenedor web es el encargado de crear los objetos HttpServletRequest y
HttpServletResponse que reciben los métodos el servlet. Estos dos objetos encapsulan toda la información que
necesita para su ejecución el servlet: parámetros de solicitud, cabeceras, cookies y atributos de contexto.
2.1. Inicialización de los servlets.
Los servlets usados en las aplicaciones web deben estar declarados en el archivo de configuración web.xml. El
contenedor de servlet de la Aplicación Servidor instancia cada servlet registrado usando el método
Class.forName(ClaseDelServlet).newInstance(). Para ello es preciso que la clase del servlet posea un constructor
público sin argumentos.
2.1.1. Ciclo de vida de los servlets.
El contenedor web inicializa un servlet cargando su clase, invocando su constructor sin argumentos, e
invocando el método init().
El método init() (el cual puede sobrescribir el programador) solo es invocado una vez en la vida del servlet, y
siempre antes de que éste pueda dar servicio a cualquier petición cliente.
El método init() brinda acceso a los objetos ServletConfig y ServletContext, los cuales el servlet necesita para
obtener información sobre su propia configuración y la de la aplicación web.
El contenedor finaliza la vida de un servlet invocando su método destroy().Esta instancia ya no podrá ser
reutilizada. El contenedor decide destruir un servlet si éste no es solicitado por un largo tiempo.
La mayoría del tiempo de vida de un servlet se pasa ejecutando el método service() para cada petición de un
cliente. Por defecto cada petición a un servlet se ejecuta en un hilo separado y sólo existe una instancia de una
clase servlet en particular.Una vez destruido, el recolector de basura se encargará de liberar la memoria
ocupada por el servlet.
Figura 10

2.1.2. Configuración de la precarga de un servlet.


Como se ha visto, la primera vez que un cliente invoca un servlet el contenedor instancia su clase e inicializa
el servlet (asignando el ServletContext, invocando oyentes, etc.).
Si queremos que un servlet sea cargado en tiempo de despliegue (cuando el servidor web arranca nuestra
aplicación), en vez de la primera solicitud, podemos usar el elemento <load-on-startup> del archivo
descriptor. Un valor no-negativo dentro de esta etiqueta le dice al contenedor que inicialice el servlet cuando
se despliega la aplicación web, o cuando es recargada.
Si queremos precargar varios servlets podemos controlar el orden en que son inicializados mediante el valor
de la etiqueta <load-on-startup>. Por tanto los valores de esta etiqueta determinan el orden en que son
precargados los servlets.

61
Por ejemplo, las siguientes declaraciones de servlets fuerza que primero sea precargado el servlet1 y después
del servlet2:
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>foo.TestUno</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>servlet2</servlet-name>
<servlet-class>foo.TestDos</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
2.2. Parámetros de inicialización de un servlet.
El contenedor web permite asociar datos de manera exclusiva para cada servlet. Estos datos pueden ser
declarados en el elemento <servlet> del fichero descriptor o en la anotación @WebServlet. La interfaz
javax.servlet.ServletConfig ofrece métodos que permiten recuperar los parámetros de inicialización asociados a
un servlet. Cada servlet tiene asociado un objeto de este tipo que puede ser obtenido mediante el método
getServletConfig().
La interfazServletConfig declara métodos para leer los parámetros de inicialización del servlet:
▪ String getInitParameter(String name), retorna el valor del parámetro especificado o null si no existe.
▪ Enumeration getInitParameterNames(), retorna una enumeración con los nombres de los parámetros.
▪ ServletContext getServletContext(), retorna el objeto ServletContext para este servlet.
▪ String getServletName(), retorna el nombre del servlet como está declarado en el archivo de configuración.
Para declarar parámetros de inicialización en el archivo web.xml deben usarse etiquetas <init-param>. En este
ejemplo se declaran parámetros para acceder a una base de datos:
<web-app>
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class>servlet1</servlet-class>
<init-param>
<param-name>driverclassname</param-name>
<param-value>sun.jdbc.odbc.JdbcOdbcDriver</param-value>
<description>Clase del controlador</description>
</init-param>
<init-param>
<param-name>dburl</param-name>
<param-value>jdbc:odbc:MySQLODBC</param-value>
<description>Url de la conexión</description>
</init-param>
</servlet>
</web-app>
Las etiquetas para definir un parámetro de inicialización son:
<param-name>, el nombre del parámetro.
<param-value>, el valor del parámetro.
<description>, una descripción opcional para el parámetro.
Para declarar parámetros de inicialización mediante anotaciones se utiliza el código siguiente en la clase del
servlet:
@WebServlet(name = "servlet1", initParams = {
@WebInitParam(name = "driverclassname", value = "sun.jdbc.odbc.JdbcOdbcDriver"),
@WebInitParam(name = "dburl", value = "jdbc:odbc:MySQLODBC")})
public class Servlet1 extends HttpServlet {
……………..
}
Estos parámetros podrían ser leídos en el método init() del servlet y utilizados para crear una conexión a base
de datos.
@Override
public void init(ServletConfig config) throws ServletException {

62
String driver = config.getInitParameter(driverclassname);
String url = config.getInitParameter(dburl);
// …….. resto de código para crear una conexión …………..
}

Importante. No se pueden modificar los parámetros de inicialización desde el código de un servlet.


2.3. El contexto de aplicación de un servlet.
La interfaz javax.servlet.ServletContext ofrece información sobre el entorno en el cual se ejecuta un servlet,
incluyendo la aplicación web y el contenedor de servlets.Cada aplicación web posee un único objeto de
contexto que es accesible en los servlets mediante los métodosHttpServlet.getServletContext()y
HttpServlet.getServletConfig().getServletContext().
2.3.1. Acceso a recursos de la aplicación desde el contexto de los servlets.
La interfaz ServletContext declara dos métodos para acceder a los recursos definidos en la aplicación web:
▪ java.net.URL getResource(String path), retorna la URL del recurso especificado (el path debe comenzar con
/ y no ser una ruta absoluta).
▪ java.io.InputStream getResourceAsStream(String path), retorna un InputStream enlazado al recurso. Es
equivalente a getResource(path).openStream().
Podemos utilizar estos métodos para enviar el contenido de un fichero ubicado en el sitio web como
respuesta al navegador. Por ejemplo, el siguiente servlet recibe como parámetro el nombre de un fichero de
texto, lo busca en la carpeta "/ficheros" del sitio web y retorna su contenido:
package servlet;
import java.io.*;
import java.net.URL;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletFichero", urlPatterns = {"/fichero"})
public class ServletFichero extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String nombreFichero = request.getParameter("nombre");
if (nombreFichero == null) {
throw new ServletException("Falta el nombre del fichero");
}
response.setContentType("text/plain");
ServletContext context = getServletContext();
URL url = context.getResource("/ficheros/" + nombreFichero);
if (url == null) {
throw new ServletException("No se encuentra el fichero");
}
try (OutputStream out = response.getOutputStream();InputStream in = url.openStream()) {
int b;
while ((b = in.read()) >= 0) {
out.write(b);
}
}
}
}
Este código primero recupera un parámetro de solicitud llamado "nombre" y si no existe lanza una excepción.
Se utiliza el parámetro "nombre" para localizar un fichero ubicado a partir de la carpeta raíz de la aplicación. El
método getResource() retornará null si no existe el fichero y se lanzará una excepción, sino se envía el
contenido del fichero al navegador leyendo byte a byte.
Otro método interesante de la interfaz ServletContextes:
▪String getRealPath(String path), retorna la ruta absoluta de disco correspondiente a una ruta relativa del sitio
web.

63
Este método recibe como argumento una ruta relativa a la carpeta raíz del sitio web, y debe comenzar con
una barra /. Este método puede resultar interesante si necesitamos crear un objeto File con la ruta de un
recurso del sitio Web. Por ejemplo, podemos modificar el servlet previo de la siguiente manera:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String nombreFichero = request.getParameter("nombre");
if (nombreFichero == null) {
throw new ServletException("Falta el nombre del fichero");
}
response.setContentType("text/plain");
ServletContext context = getServletContext();
File url = new File(context.getRealPath("/ficheros/" + nombreFichero));
if (!url.exists()) {
throw new ServletException("No se encuentra el fichero");
}
try (OutputStream out = response.getOutputStream();
InputStream in = new FileInputStream(url)) {
byte[] bytes=new byte[(int) url.length()];
in.read(bytes);
out.write(bytes);
}
}
2.3.2. Parámetros de inicialización de la aplicación.
Al igual que se pueden crear parámetros de inicialización asociados a un servlet específico, se pueden crear
parámetros de inicialización disponibles en todos el contexto del sitio web. Estos parámetros de contexto se
declaran en el archivo descriptor de la siguiente manera:
<web-app>
<context-param>
<description>Ubicación de imágenes</description>
<param-name>imagenes</param-name>
<param-value>/imagenes</param-value>
</context-param>
</web-app>
Cada parámetro debe ir encapsulado en un elemento <contex-param> en el primer nivel del nodo raíz <web-
app>. Se puede recuperar el valor de un parámetro de contexto fácilmente mediante el objeto ServletContext:
String rutaImagenes = getServletContext().getInitParameter("imagenes")
También podemos recorrer todos los parámetros de contexto mediante el método
ServletContext.getInitParameterNames().Este método retorna una enumeración con todos los nombres de los
parámetros.
Enumeration<String> parametros= this.getServletContext().getInitParameterNames();
while (parametros.hasMoreElements()) {
String nombre = parametros.nextElement();
String valor = getServletContext().getInitParameter(nombre);
}
2.4. Cabeceras HTTP.
La etiqueta <head> permite definir información de cabeceras de las páginas HTML. Estas cabeceras
informan al navegador web sobre ciertos aspectos o comportamientos de la página. Por ejemplo, una
cabecera típica de un documento HTML puede ser la siguiente:
<!DOCTYPE html>
<html>
<head>
<title>Título del documento</title>
<meta http-equiv="Refresh" content="15">
<meta http-equiv="Expires" content="Tue, 20 Jan 2010 16:25:27 GMT">
<meta name="Generator" content="EditPlus">
<meta name="Author" content="El nombre del autor">
<meta name="Keywords" content="palabra1, palabra2, etc.">
<meta name="Description" content="Una descripción sobre el documento">
</head>

64
<body>
</body>
</html>
En estas cabeceras situadas dentro del elemento <head /> reside información acerca del documento, y
generalmente no se aprecia cuando se navega por él. Estos elementos son opcionales.
La etiqueta <title> permite especificar el título del documento, el cual aparecerá en la barra de título del
navegador.
La etiqueta <meta> permite especificar varios tipos de información. Por ejemplo, la etiqueta <meta http-
equiv="Refresh" content="15"> hace que el navegador vuelva a cargar la página activa al cabo de 15 segundos.
La etiqueta <meta htpp-equiv="Expires" ... > permite forzar la expiración inmediata en la caché del navegador
de la página recibida, lo que provoca que la página sea solicitada de nuevo al servidor cada vez, en lugar de
cargar la copia que ya existe en la máquina del cliente.
La etiqueta <meta name= "Keywords" ... > permite especificar una lista de palabras para ayudar a los
buscadores de Internet como Google, Yahoo, etc. a encontrar nuestras páginas.
Es importante tener en cuenta que podemos usar todos los elementos <meta> que necesitamos, pero sin
repetirlos.
Las cabeceras de tipo <meta> pueden ser obtenidas mediante código en un servlet y algunas de ellas pueden
ser establecidas desde código.
2.4.1. Obteniendo las cabeceras de la petición en un servlet.
La clase HttpServletRequest proporciona métodos para obtener los nombres y valores de las cabeceras de la
petición:
▪ String getHeader(String headerName), para retornar uno de los valores asociado a la cabecera especificada.
▪ Enumeration getHeaderValues(String headerName), para retornar una enumeración de strings con los
valores asociados a la cabecera especificada.
▪ Enumeration getHeaderNames(), para retornar una enumeración con los nombres de las cabeceras.
Como ejemplo, el siguiente código de un servlet recupera todas las cabeceras de la página que lo invoca y los
muestra como respuesta:
@WebServlet(name = "ServletCabeceras", urlPatterns = {"/cabeceras"})
public class ServletCabeceras extends HttpServlet {
public void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Enumeration<String> cabeceras = request.getHeaderNames();
response.setContentType("text/html");
try (PrintWriter out = response.getWriter()) {
out.println("<html><body>");
while ( cabeceras.hasMoreElements() ) {
String nombre = cabeceras.nextElement(); // el nombre de la cabecera
out.println("<b>" + nombre + ": </b>"); // se escribe el nombre de la cabecera
out.println( request.getHeader(nombre) + "<br>"); // se escribe el valor de la cabecera
}
out.println("</body></html>");
}
}
}
Un posible resultado de invocar este servlet puede ser:

65
Figura 11

2.4.2. Leer y escribir cabeceras de página desde un servlet.


La clase HttpServletResponse proporciona métodos para enviar cabeceras de respuesta:
▪ void setHeader(String name, String value), para enviar una cabecera y su valor.
▪ void setIntHeader(String name, int value), lo mismo para enviar un valor entero.
▪ void setDateHeader(String name, long millisecs), lo mismo para enviar un valor de fecha.
▪ void addHeader/addIntHeader/addDateHeader se usan para asociar múltiples valores con la misma
cabecera.
▪ boolean containsHeader(String name), indica si una cabecera está ya puesta o no.
Seleccionar las cabeceras de respuesta normalmente depende de la selección de códigos de estado. Se pueden
usar para especificar cookies, para suministrar la fecha de modificación (para el caché), para instruir al
navegador sobre la recarga de la página después de un intervalo designado, para decir cuánto tiempo va a
estar el fichero usando conexiones persistentes, y otras muchas tareas.
La forma más general de especificar cabeceras es mediante el método setHeader(). El método setDateHeader()
permite enviar una fecha en milisegundos, y el método setIntHeader() permite enviar un entero. Se usan
addHeader(), addDateHeader(), y addIntHeader() para añadir una nueva cabecera.
Finalmente, HttpServletResponse también suministra unos métodos de conveniencia para especificar cabeceras
comunes:
▪ El método setContentType() selecciona la cabecera "Content-Type", y se usa en la mayoría de los Servlets.
▪ El método setContentLength() selecciona la cabecera "Content-Length", útil si el navegador soporta
conexiones HTTP persistentes (keep-alive).
▪ El método addCookie() selecciona un cookie.
▪ El método sendRedirect() selecciona la cabecera "Location" así como se selecciona el código de estado 302.
La tabla siguiente describe las cabeceras de respuesta más comunes y sus significados:
Cabecera Interpretación/Propósito
Allow ¿Qué métodos de petición (GET, POST, etc.) soporta el servidor?
Content-Encoding ¿Qué método se utilizó para codificar el documento? Necesitamos decodificarlo
para obtener el tipo especificado por la cabecera Content-Type.
Content-Length ¿Cuántos bytes han sido enviados? Esta información es sólo necesaria si el
navegador está usando conexiones persistentes. Nuestro servlet debería escribir el
documento en un ByteArrayOutputStream, preguntar su tamaño cuando se haya
terminado, ponerlo en el campo Content-Length, luego enviar el contenido con
byteArrayStream.writeTo(response.getOutputStream().
Content-Type ¿Cuál es el tipo MIME del siguiente documento? Por defecto en los servlets es
text/plain, pero normalmente especifican explícitamente text/html.
Date ¿Cuál es la hora actual (en GMT)? Usamos el método setDateHeader() para
especificar esta cabecera.

66
Expires ¿En qué momento debería considerarse el documento como caducado y no se
pondrá más en el caché?
Last-Modified ¿Cuándo se modificó el documento por última vez? El cliente puede suministrar
una fecha mediante la cabecera de petición If-Modified-Since. Ésta es tratada como
un GET condicional, donde sólo se devuelven documentos si la fecha Last-
Modified es posterior que la fecha especificada. De otra forma se devuelve una
línea de estado 304 (Not Modified). De nuevo se usa el método setDateHeader()
para especificar esta cabecera.
Location ¿Dónde debería ir el cliente para obtener el documento? Se selecciona
indirectamente con un código de estado 302 (SC_FOUND), mediante el método
sendRedirect() de HttpServletResponse.
Refresh ¿Cuándo (en segundos) debería pedir el navegador una página actualizada? En
lugar de recargar la página actual, podemos cargar otra con setHeader("Refresh",
"5; URL=http://host/path").
Referer Contiene la URL de la página que realizó la solicitud del recurso actual.
Server ¿Qué servidor soy? Los servlets normalmente no usan esto; lo hace el propio
servidor.
Set-Cookie Especifica una cookie asociada con la página. Se debe usar el método addCookie()
de HttpServletResponse.
WWW-Authenticate ¿Qué tipo de autorización y domino debería suministrar el cliente en su cabecera
Authorization? Esta cabecera es necesaria en respuestas que tienen una línea de
estado 401 (CS_UNAUTHORIZED). Por ejemplo response.setHeader("WWW-
Authenticate", "BASIC realm=\"executives\"").

2.5. Tipo de contenido de la respuesta.


El método setContentType() de la clase HttpServletResponse permite enviar la cabecera "Content-Type" al
navegador cliente para definir el tipo de contenido de la respuesta generada desde el servlet. Los navegadores
y otros programas cliente pueden utilizar esta cabecera para determinar la correcta visualización de un
documento.
2.5.1. Tipos MIME.
Un navegador puede administrar varios tipos de archivos, incluyendo documentos PDF, documentos Word,
animaciones de Flash, etc.; y mostrarlos directamente en el navegador. Para ello debemos transmitir al
navegador el tipo MIME apropiado que describa el contenido del stream de respuesta. El tipo MIME es una
descripción estandarizada de contenidos, independiente de la plataforma, similar al de las extensiones de
archivos bajo Windows. Los tipos MIME consisten en dos componentes de información separados por una
barra:
• El primer componente es el tipo de medio (o formato). En esencia, el valor es text para cualquier clase de
texto, audio para todos los documentos de audio, image para imágenes, video para cualquier tipo de imagen
animada, y application para datos binarios específicos de la aplicación.
• El segundo componente es el tipo de documento específico. El valor por defecto es text/html e indica una
página web formada por texto.
En la siguiente tabla se describen los tipos MIME más habituales:
Tipo MIME Descripción
text/html HTML (Protocolo de transferencia de Hipertexto)
text/xml XML (Lenguaje Extensible de MarkUp)
text/vnd.wap.wml WAP (Protocolo para Aplicaciones Inalámbricas). Páginas para mostrar en
dispositivos móviles incluyendo teléfonos celulares.
text/plain Texto normal. La información de Content-Encoding determina el código de
página a ser utilizado para la interpretación del archivo de texto.
text/richtext Un archivo de texto en formato enriquecido
application/pdf Documento PDF que puede visualizarse con Adobe Acrobat Reader.
application/octet-stream Estrictamente datos binarios, incluyendo aplicaciones que se ofrecen para
descarga
application/msword Documento de Microsoft Word para Windows

67
application/rtf Un archivo de texto en formato RTF
application/zip Archivo ZIP
application/vnd.ms-excel Archivo de Microsoft Excel
application/excel Archivo de Microsoft Excel
audio/mpeg Archivo MP3
image/jpeg Imágenes JPEG
image/png Imágenes en formato PNG
image/bmp Imágenes en formato Windows BMP
La cabecera Content-Disposition se complementa con la cabecera Content-Type para forzar la descarga del
documento con un nombre. Por ejemplo, si queremos asignar un nombre al fichero, solo tenemos que enviar
la siguiente cabecera:
response.setHeader("Content-Disposition","attachment; filename=archivo.extension");
Esta cabecera provoca normalmente que aparezca un cuadro de diálogo solicitándonos qué hacer con el
fichero.
2.5.2. Asociación de extensiones con tipos MIME.
Podemos asociar extensiones de archivos con los tipos MIME en el archivo descriptor de la aplicación Web
(web.xml). En el siguiente ejemplo se asocia la extensión mpg con un documento de vídeo:
<web-app>
<mime-mapping>
<extension>mpg</extension>
<mime-type>video/mpeg</mime-type>
</mime-mapping>
</web-app>
2.5.3. Enviar archivos binarios al cliente.
Como alternativa a getWriter(), el método getOutputSteam() de HttpServletResponse retorna un objeto
ServletOutputStream que podemos usar para enviar un archivo binario al cliente.
Por ejemplo, el siguiente método doGet() de un servlet muestra cómo enviar un fichero de tipo JAR al
navegador cliente:
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("application/jar"); // se pone el tipo de contenido MIME
File f = new File("test.jar");
try (OutputStream out = response.getOutputStream();
InputStream in = new FileInputStream(f)) {
byte[] bytes=new byte[(int) f.length()];
in.read(bytes);
out.write(bytes);
}
}
Si, en otro ejemplo, quisiésemos enviar un archivo de Word deberíamos usar las siguientes cabeceras:
res.setContentType("application/msword");
res.addHeader("Content-Disposition", "attachment; filename=midocumento.doc");

3. Métodos de solicitud en servlets


Como se ha visto, los servlets permiten capturar solicitudes de URIs que casen con sus patrones URL y son
capaces de generar una respuesta dinámica. En este apartado veremos las diversas técnicas que se usan en los
navegadores para realizar solicitudes y cómo un servlet puede responder a ellas.
3.1. Procesos HTTP en los servlets.
Para cada solicitud con un método de llamada HTTP desde el cliente (GET, POST, HEAD, ...), la clase
HttpServlet declara un método respectivo: doGet(), doHead(), doPost(), doPut(), doDelete(), doOptions(), y
doTrace().
Para entender el orden de ejecución de estos métodos, la secuencia de eventos que se producen cuando se
procesa un requerimiento en la clase HttpServlet es la siguiente:
1) El contenedor de servlet llama al método service() de la clase servlet.
2) Este método service() llama al método service() de la clase baseHttpServlet.

68
3) El método service() de la clase base analiza el requerimiento y determina el método de llamada.
Dependiendo de ello invoca el método do_() correspondiente.
Si se rescribe el código del método service() se pierde esta funcionalidad base, y el programador deberá
implementar su propio código de proceso.
3.2. Solicitudes GET HTTP.
Desde un navegador se pueden realizar solicitudes con el método GET HTTP de varias formas:
• Escribiendo directamente una URI en la barra de direcciones del navegador.
• Pinchando un enlace creado con la etiqueta <a href="">.
• Incrustando una imagen con la etiqueta <img src= "" />. Debemos tener en cuenta que cuando el
navegador carga la página, si se encuentra con una etiqueta <img> realiza una solicitud GET sobre la ruta
especificada en su atributo src.
• Usando código JavaScript. Veremos ejemplos de ello con otros métodos de solicitud.
Un ejemplo de servlet que procesa solicitudes GET se ha analizado previamente con la aplicación
"WebEjemplo" y el servlet "ServletSaludo".
3.2.1. Solicitudes GET con paso de parámetros.
Las solicitudes GET HTTP permiten pasar datos adicionales en la parte de la URI denominada cadena de
consulta. La cadena de consulta es la última parte de una URI y tiene la siguiente sintaxis.
?clave=valor&clave=valor&clave=valor
La cadena de consulta consta de un signo de interrogación ? y de una lista de parámetros separados por un &.
Cada parámetro consta de una clave y un valor opcional separados por un signo igual. La clave del parámetro
será el nombre por el cual se le identifique. Si no se especifica el valor se recibirá un string vacío.
Una cuestión importante sobre los parámetros de la cadena de conexión es que una URI no admite espacios
en blanco, por ello podemos usar la codificación %20 en su lugar. Por ejemplo, la siguiente consulta permite
pasar el nombre y apellidos de una persona en el parámetro nombre:
?nombre=Pedro%20Salgueiro
Una cuestión importante es que varios parámetros dentro de la misma cadena de consulta pueden tener la
misma clave, y que ésta es sensible a mayúsculas y minúsculas.
3.2.2. Obteniendo los parámetros GET desde un servlet.
Se utiliza el objeto HttpServletRequestpara obtener los parámetros de consulta enviados con la URI. Esta clase
proporciona los siguientes métodos:
▪ String getParameter(String clave), permite recuperar el valor asociado a un parámetro especificado. Si no
existe ningún parámetro con la clave especificada retorna el valor null.
▪ String[] getParameterValues(String clave), permite recuperar todos los valores asociados a un parámetro
especificado (útil para listas con varios elementos seleccionados). Si no existe ningún parámetro con la clave
especificada retorna el valor null.
▪ Enumeration<String> getParameterNames(), permite obtener una enumeración con los nombres de los
parámetros.
▪ Map<String, String[]> getParameterMap(), retorna un mapa donde cada elemento es un parámetro con su
clave y lista de valores.
Para ver cómo trabajan estos métodos supongamos creado el siguiente servlet en la aplicación
"WebAplicacion":
package servlet;
import java.io.*;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletCapturaParametros", urlPatterns = {"/captura"})
public class ServletCapturaParametros extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
Enumeration<String> claves = request.getParameterNames();
while (claves.hasMoreElements()) {

69
String clave = claves.nextElement();
out.println(clave + "=" + request.getParameter(clave));
}
}
}
}
El servlet ServletCapturaParametros sólo ha reescrito el método doGet() y por ello cualquier solicitud a este
servlet que no utilice el verbo GET provocará una página de error.
Este servlet captura todos los parámetros de la cadena de consulta e itera sobre ellos para mostrar la clave y el
valor. Primero se establece el tipo de contenido que se enviará al navegador:
response.setContentType("text/plain;charset=UTF-8");
El tipo MIME "text/plain" le indica al navegador que recibirá un documento de texto sin formato. Mediante
un bucle se itera sobre las claves que devuelve la enumeración request.getParameterNames(). En cada iteración
se escribe en una línea el nombre de la clave, y el valor asociado con ella. Se obtiene este valor usando el
método request.getParameter().
Por ejemplo, si ejecutamos este servlet con la URI:
http://localhost:8084/WebAplicacion/captura?nombre=Pedro%20Salgueiro&edad&pais=España
Obtendremos el siguiente resultado en el navegador:
nombre=Pedro Salgueiro
edad=
pais=España
Al no pasar ningún valor en el parámetro edad se obtiene un string vacío. Hemos dicho que podemos poner
varios parámetros con el mismo nombre, pero si ejecutamos este servlet con la URI:
http://localhost:8084/WebAplicacion/captura?aficion=Lectura&aficion=Bricolage
Obtendremos el siguiente resultado en el navegador:
aficion=Lectura
Aunque hemos pasado con parámetros con el nombre aficion, el método getParameter() sólo permite
recuperar el valor del primer parámetro con ese nombre. Cuando sepamos que existen parámetros con el
mismo nombre podemos utilizar el método getParameterValues() en sustitución de getParameter().
Podemos modificar el servlet de la siguiente manera:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(request.getParameterMap());
Enumeration<String> claves = request.getParameterNames();
while (claves.hasMoreElements()) {
String clave = claves.nextElement();
out.println(clave + "=" + Arrays.asList(request.getParameterValues(clave)));
}
}
}
Para obtener ahora el resultado:
aficion=[Lectura, Bricolage]
3.3. Solicitudes POST HTTP.
Desde un navegador se pueden realizar solicitudes con el método POST HTTP de varias formas:
• Posteando datos mediante un formulario.
• Usando código JavaScript. Veremos ejemplos de ello con otros métodos de solicitud.
El objetivo del método POST HTTP es realizar solicitudes donde se postean datos. Pero al contrario que el
método GET, los datos posteados con POST no forman parte de la URI solicitada sino que se embeben en al
cuerpo de la solicitud y por tanto se ocultan para el usuario. Esto es importante cuando se postean
contraseñas.
Para que un formulario postee datos debe incluir al menos un control HTML que tenga asignado su atributo
name a algún valor. Se creará un parámetro por cada control HTML, utilizando el nombre del control como
clave del parámetro, y el contenido del control como valor del parámetro.

70
3.3.1. Un formulario que postea datos simples.
A continuación agregaremos al proyecto de ejemplo "WebAplicacion" una nueva página llamada "login.html"
donde incluiremos un formulario para solicitar un nombre y contraseña de usuario:
Figura 12

En la etiqueta <form> se ha especificado el método POST y una acción llamada registro. Esta acción se
corresponderá con el patrón URL de un servlet llamado ServletRegistro, definido de la siguiente manera:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletRegistro", urlPatterns = {"/registro"})
public class ServletRegistro extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("Tipo de contenido: " + request.getContentType());
out.println("Contenido del cuerpo: " + request.getReader().readLine());
}
}
}
Inicialmente este servlet procesará las solicitudes POST devolviendo un documento de texto sin formato.
Como ejemplo práctico se utiliza el método request.getContentType() para obtener el tipo de contenido del
cuerpo recibido con la solicitud. Para recuperar el contenido del propio cuerpo disponemos de los métodos
request.getReader() y request.getInputStream(); estos dos métodos retornan un stream de lectura. En este caso
se utiliza request.getReader() para leer una línea de la cabecera, la que contiene los datos posteados.

71
Figura 13

Podemos comprobar que el tipo MIME para contenido de formulario es "application/x-www-form-urlencoded",


y que el formato de los parámetros de formulario es el mismo que el de los parámetros de las cadenas de
consulta.
Aunque recuperar el contenido del cuerpo de la solicitud puede resultar útil, lo habitual es recuperar los
parámetros de formulario de la misma manera que se recuperan los parámetros de las cadenas de consulta:
usando los método getParameter(), getParameterValues() y getParameterMap() del objeto HttpServletRequest.
Modificaremos el servlet para que sólo valide a un usuario si coincide su nombre con su contraseña.
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String user = request.getParameter("user");
String password = request.getParameter("password");
if (user==null || password==null || user.equest || user.isEmpty() || password.isEmpty())
throw new ServletException("Falta el usuario o la contraseña");
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
if (user.equals(password))
out.println("El usuario se ha validado");
else
out.println("En usuario no ha sido validado");
}
}
Primero se recuperan los dos parámetros, pero se comprueba que existan ambos. Recordemos que el método
getParameter() retorna el valor null si el parámetro especificado no existe. También se comprueba que
contengan algún valor. Si falla uno de los parámetros se lanza una excepción, la cual provocará una página de
error en el navegador.
Tras comparar los datos se devuelve un texto al navegador.
3.3.2. Un formulario que incluye botones de radio y cajas de selección.
En el siguiente ejemplo veremos cómo postean datos los botones de radio (radio button) y las cajas de
selección (check boxes). Para ello incluiremos una nueva página "menu.html" en el proyecto "WebAplicacion".
<!DOCTYPE html>
<html>
<head>
<title>Selección de menú</title>
</head>
<body>
<form method="POST" action="menu">
<table>
<tr>
<th>Plato:</th><th>Condimentos:</th>

72
</tr>
<tr>
<td>
<input type="radio" name="plato" value="Paella" checked />Paella<br>
<input type="radio" name="plato" value="Tortilla" />Tortilla<br>
<input type="radio" name="plato" value="Paella" />Ensalada
</td>
<td>
<input type="checkbox" name="condimento" value="sal" />Sal<br>
<input type="checkbox" name="condimento" value="pimienta" />Pimienta<br>
<input type="checkbox" name="condimento" value="azafran" />Azafrán
</td>
</tr>
<tr>
<td colspan="2"><input type="submit" value="anotar" /></td>
</tr>
</table>
<form>
</body>
</html>
Esta página permitirá seleccionar un plato y sólo uno, y qué condimentos deben incluir.
Se han utilizado botones de radio para especificar los platos, ya que cuando forman un grupo permiten
realizar selecciones excluyentes, de forma que sólo uno de los botones esté seleccionado a la vez. Podemos
establecer la selección de un botón de radio incluyendo el atributo checked. Para formar un grupo de botones
de radio hay que asignarles el mismo nombre, pero teniendo en cuenta que sólo se posteará el valor del botón
seleccionado, por tanto podremos recuperarlo con el método getParameter(). El valor del botón queda
establecido en su atributo value.
Se han utilizado cajas de selección para seleccionar los condimentos. En este caso a todas las cajas se les ha
puesto el mismo nombre, pero como las cajas de selección no son excluyentes se podrán recibir varios
parámetros con el mismo nombre, por tanto habrá que recuperarlos con el método getParameterValues(). El
valor del cuadro de selección queda establecido en su atributo value.
Figura 14

El servlet ServletMenu recibirá los datos posteados y mostrará un mensaje como respuesta:
package servlet;
import java.io.*;
import java.util.Arrays;

73
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletMenu", urlPatterns = {"/menu"})
public class ServletMenu extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String plato = request.getParameter("plato");
String[] condimentos=request.getParameterValues("condimento");
String strCondimentos = condimentos==null? "nada" : Arrays.asList(condimentos).toString();
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("Se ha anotado: " + plato);
out.println("Debe estar condimentado con: " + strCondimentos);
}
}
}
Para este ejemplo no se han realizado validaciones, simplemente se recuperan los datos de cada parámetro.
Sin embargo hay que tener en cuenta que si no existe un parámetro el método getParameterValues() retorna el
valor null.
3.3.3. Un formulario que postea ficheros.
La especificación HTML5 incluye ahora un elemento <input type="file" />, el cual permite seleccionar uno o
varios ficheros del lado cliente y subirlos al servidor.El elemento <input type= "file" />, por defecto,
simplemente postea el nombre o nombres de los ficheros seleccionados. Estos nombres se pueden recuperar
en el lado servidor de la misma forma que se recupera el valor de cualquier otro control de formulario.
Pero si queremos que el contenido de los ficheros seleccionados se reciba en el servidor debemos modificar el
elemento <form /> para que incluya el atributo enctype con el valor"multipart/form-data".
Por ejemplo, vamos a añadir una nueva página "documento.html" en el proyecto "WebAplicacion":
<!DOCTYPE html>
<html>
<head>
<title>Subida de documentos</title>
</head>
<body>
<form method="POST" action="documento" enctype="multipart/form-data">
Un documento <input type="file" name="doc" />
<br/>
<input type="submit" value="Subir" />
</form>
</body>
</html>
Por defecto, el elemento <input type="file" /> solo permite subir un único fichero. Crearemos ahora el servlet
"ServletDocumento", que será el encargado de procesar el fichero subido.
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
import javax.servlet.http.Part;
@MultipartConfig(location = "E:/temp", maxFileSize = Integer.MAX_VALUE)
@WebServlet(name = "ServletDocumento", urlPatterns = {"/documento"})
public class ServletDocumento extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
try {

74
// Se recupera el objeto Part que representa el fichero subido
Part fichero = request.getPart("doc");
// Se guarda el fichero en la ruta especificada en la anotación MultipartConfig
fichero.write(fichero.getName());
out.println("Se ha subido el fichero: " + fichero.getSubmittedFileName());
out.println("Su contenido es: ");
// Se lee el contenido del fichero y se muestra
byte[] contenido = new byte[(int) fichero.getSize()];
fichero.getInputStream().read(contenido);
out.println(new String(contenido));
} catch (Exception ex) {
out.println(ex);
}
}
}
}
Hay que destacar en este código el uso de la anotación javax.servlet.annotation.MultipartConfig. Esta anotación
habilita al servlet para recibir ficheros posteados, los cuales pude recuperar mediante el método getPart() o
getParts(). La anotación MultipartConfig permite especificar, entre otros, el tamaño máximo permitido para
poder recuperar un fichero, y la carpeta por defecto para grabar los ficheros en el servidor.
Como alternativa a la anotación MultipartConfig podemos configurar estas opciones en el archivo descriptor,
en el elemento que registra al servlet:
<servlet>
<servlet-name>ServletDocumento</servlet-name>
<servlet-class>servlet.ServletDocumento</servlet-class>
<multipart-config>
<location>E:/temp</location>
<max-file-size>2147483647</max-file-size>
</multipart-config>
</servlet>
Como se ha recibido un único fichero se utiliza getPart() especificando el nombre del parámetro. Un objeto
Part representa el fichero subido y proporciona métodos para recuperar su contenido y grabarlo directamente
en una carpeta del servidor.
Figura 15

3.4. Otras solicitudes HTTP.


Además de GET y POST es posible realizar solicitudes usando otros verbos como PUT, DELETE, TRACE, etc.

75
Estos otros métodos de solicitud no se utilizan para navegar entre páginas, tal como si lo hacen GET y PUT.
Por eso los navegadores Web y el estándar HTML sólo soportan directamente GET y PUT. Los demás
métodos requieren realizar una solicitud desde instrucciones de código que puedan recuperar la respuesta.
La siguiente tabla resume el uso de los demás métodos HTTP:
Método Significado
HEAD Se utiliza para realizar solicitudes como GET, pero sin que el servidor devuelva el cuerpo del
mensaje. Es decir, sólo se devuelve la información de cabecera.
PUT Se utiliza para enviarun recurso identificado en la URI desde el cliente hacia el servidor.
OPTIONS Se utiliza para pedir información sobre las características de comunicación proporcionadas
por el servidor. Le permite al cliente negociar los parámetros de comunicación.
TRACE Inicia un ciclo de mensajes de petición. Se usa para depuración y permite al cliente ver lo que
el servidor recibe en el otro lado.
DELETE Se usa para solicitar al servidor que borre un recurso identificado en la URI.
CONNECT Este método se reserva para uso con proxys. Permitirá que un proxy pueda dinámicamente
convertirse en un túnel. Por ejemplo para comunicaciones con SSL.
El estándar HTML5 incluye un objeto denominado XMLHttpRequest, que podemos utilizar en el código
JavaScript para realizar solicitudes especificando el método HTTP.
Para probar el uso de este objeto primero crearemos un servlet llamado "ServletTest" donde iremos
reescribiendo los métodos do_(). Inicialmente reescribiremos el método doGet() para que retorne un mensaje
de texto plano:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletTest", urlPatterns = {"/test/*"})
public class ServletTest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("Solicitud GET recibida");
}
}
}
3.4.1. Cómo hacer solicitudes con «XMLHttpRequest».
El objeto XMLHttpRequest permite realizar solicitudes HTTP mediante código y permite retornar texto, JSON,
y datos XML.
Se usa el siguiente código JavaScript para crear un objeto XMLHttpRequest:
var request = new XMLHttpRequest();
Podemos usar este objeto para iniciar una solicitud remota. Para hacer esto se deben asignar propiedades que
especifiquen la URI y el tipo de dato que será solicitado; esto permitirá que el servidor que reciba la solicitud
pueda reconocer los datos que le son enviados. El siguiente código muestra como solicitar el servlet
"ServletTest" con el método GET:
request.open( "GET", "test" );
request.send()
El método open() define el método HTTP y la uri que será solicitada (en este ejemplo se usa una URI relativa,
ya que suponemos que la página y el servlets se están ejecutando dentro de la misma aplicación web), y la
indicación de que la solicitud se debe realizar asíncronamente. Para transmitir la solicitud se invoca el método
send(), el cual admite opcionalmente el contenido del cuerpo de la solicitud. Las llamadas al método send()
son asíncronas por defecto, por ello debemos gestionar el evento readystatechange para recuperar la
respuesta:
request.onreadystatechange = function() {
// Si se ha recibido una respuesta el código será 4
if (request.readyState==4) {

76
alert(request.responseText);
}
}
La propiedad readyState que puede tener uno de los siguientes valores:
0: El objeto XMLHttpRequest no ha sido abierto.
1: El objeto XMLHttpRequest ha sido abierto.
2: El objeto XMLHttpRequest ha enviado una solicitud.
3: El objeto XMLHttpRequest ha empezado a recibir una respuesta.
4: El objeto XMLHttpRequest ha finalizado de recibir una respuesta.
La respuesta de la solicitud se puede recuperar con las propiedades responseText o responseXml. Podemos
también gestionar los errores de la comunicación:
request.onerror = function (error) {
alert(erro.message);
}
Por tanto si creamos ahora una página "otrosMetodos.html" con el siguiente contenido:
<!DOCTYPE html>
<html>
<head>
<title>Solicitud por código</title>
</head>
<body>
<div id="contenido"></div>
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("GET","test",true );
request.send();
request.onreadystatechange = function() {
if (request.readyState==4) {
document.getElementById("contenido").innerHTML=request.responseText;
}
}
request.onerror = function (error) {
alert(erro.message);
}
</script>
</body>
</html>
Y si abrimos la página en un navegador el resultado será el siguiente:
Figura 16

3.4.2. Solicitudes HEAD HTTP.


Una solicitud HEAD debería retorna toda la información que ofrecería la misma solicitud con GET pero sin el
contenido.
Primero añadiremos el método doHead() al servlet ServletTest:
@Override
protected void doHead(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
String contenido="Solicitud GET recibida";

77
response.setContentLength(contenido.length());
}
El método define la cabecera con el tipo de contenido y la longitud del mismo. Ahora en la página HTML
recuperaremos todas estas cabeceras:
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("HEAD","test",true );
request.send();
request.onreadystatechange = function() {
// Si se ha recibido una respuesta
if (request.readyState==4) {
document.getElementById("contenido").innerHTML = request.getAllResponseHeaders();
}
}
</script>
Y si abrimos la página en un navegador el resultado será el siguiente:
Figura 17

3.4.3. Solicitudes PUT HTTP.


Una solicitud PUT debería utilizarse para enviar un recurso al servidor. El contenido del recurso se envía
normalmente en el cuerpo de la solicitud. Un uso habitual de este método era para subir ficheros al servidor.
Como ejemplo añadiremos el método doPut() al servlet ServletTest, de forma que debe recuperar el contenido
del cuerpo de la solicitud y retornarlo como respuesta:
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String respuesta = "Se ha recibido: ";
if (request.getContentType().startsWith("text/plain")) {
byte[] cuerpo = new byte[request.getContentLength()];
request.getInputStream().read(cuerpo);
respuesta += new String(cuerpo);
} else {
respuesta = "Tipo de contenido incorrecto";
}
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(respuesta);
}
}
En este caso el método espera recibir un contenido de texto plano y si no es así retorna un mensaje de error.
Ahora en la página HTML enviamos un texto en el cuerpo de la solicitud:
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("PUT","test",true );
request.setRequestHeader("Content-Type", "text/plain");
request.send("Un texto de ejemplo");
request.onreadystatechange = function() {
if (request.readyState==4) {
document.getElementById("contenido").innerHTML=request.responseText;

78
}
}
A destacar la instrucción request.setRequestHeader("Content-Type", "text/plain"), que especifica el tipo de
contenido que recibirá el servlet.
3.4.4. Solicitudes DELETE HTTP.
Por su parte, el método DELETE debería ser utilizado para solicitar eliminar un recurso del servidor. La
identificación del recurso suele enviarse parte de la información de ruta de la URI, aunque también se puede
utilizar el cuerpo de la solicitud.
Como ejemplo añadiremos el método doDelete() al servlet ServletTest, de forma que debe recuperar un
identificador concatenado con la ruta y retornarlo como respuesta:
protected void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String id = request.getPathInfo();
String respuesta = "Falta el ID";
if (id != null) {
respuesta = "Se debe eliminar el recurso de ID " + id.replace("/", "#");
}
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(respuesta);
}
}
Ahora en la página HTML concatenaremos una ID en la URI:
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("DELETE","test/23",true );
request.send();
request.onreadystatechange = function() {
if (request.readyState==4) {
document.getElementById("contenido").innerHTML=request.responseText;
}
}
</script>
Y si abrimos la página en un navegador el resultado será el siguiente:
Figura 18

3.4.5. Solicitudes OPTIONS HTTP.


El método OPTIONS debe ser utilizado para que el servidor informe de qué métodos HTTP soporta y
retornarlos en una cabecera de respuesta denominada "Allow". El método doOptions() de los servlets ya
implementa esta funcionalidad, así que sólo reescribiremos el código JavaScript:
<script type="text/javascript">
var request = new XMLHttpRequest();
request.open("OPTIONS","test",true );
request.send();
request.onreadystatechange = function() {
if (request.readyState==4) {
document.getElementById("contenido").innerHTML=request.getResponseHeader("Allow");
}

79
}
</script>
Y si abrimos la página en un navegador el resultado será el siguiente:
Figura 19

4. Redirigiendo la respuesta en servlets.


Desde un servlet se dispone de dos técnicas para redirigir la respuesta a otro recurso, en vez de generar la
respuesta como instrucciones HTML: usando el método sendRedirect() o usando un objeto
RequestDispatcher.
El contenedor web proporciona varios mecanismos para gestionar errores y excepciones que se producen
durante la ejecución de los servlets y páginas JSP. Estos mecanismos están basados también en redirecciones
internas del servidor.
4.1. Redirección del navegador.
Un uso habitual de un servlet es procesar datos enviados con la solicitud y tomar decisiones de redirección a
otros recursos del mismo sitio u otro sitio web distinto. La primera técnica de redirección consiste en enviarle
al navegador cliente como respuesta la cabecera de redirección "Location". Esta técnica se puede simplificar
mediante el uso del método sendRedirect(String) de la clase HttpServletResponse.
Por ejemplo, supongamos que en una aplicación web hemos creado una carpeta llamada "informes" con varias
páginas que muestran informes. En la página index.html se ponen enlaces para ver cada informe, tal como se
muestra en la siguiente imagen:
Figura 20

Todos los enlaces comienzan por la palabra informe seguida de una barra y el tipo de informe. Se crea un
servlet que capturará estos enlaces con el patrón "/informe/*":
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletInformes", urlPatterns = {"/informe/*"})
public class ServletInformes extends HttpServlet {

80
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String tipoInforme = request.getPathInfo();
String rutaCompleta = "informes" + tipoInforme + ".jsp";
response.sendRedirect(rutaCompleta);
}
}
El servlet usará el último trozo de información de ruta para determinar la página a la que tiene que redirigir al
navegador. Por ejemplo si ejecutamos la aplicación y pulsamos en el enlace "informe/gestion" se asignarán lo
siguientes valores en el servlet:
tipoInforme = "/gestion"
rutaCompleta = "informes/gestion.jsp"
El método sendRedirect() recibe por tanto
una ruta relativa al sitio web, lo cual puede parecer correcto. Pero
podremos comprobar que el enlace no funciona, y en la barra del navegador se mostrará algo como:
http://localhost:8084/WebAplicacion/informe/informes/....../informes/gestion.jsp………..jsp
Hay que tener en cuenta que al aplicarse una ruta relativa ésta se establece respecto a la ubicación virtual del
propio servlet. Para este ejemplo, a todos los efectos es como si el servlet estuviese ubicado en la carpeta
"informe/" y se correspondiese con un archivo llamado "gestion", y por tanto la redirección se solicita para
"informe/informes/gestion.jsp". Pero esta nueva solicitud del navegador es capturada otra vez por el servlet que
vuelve a generar otra ruta de redirección y así sucesivamente.
Para evitar este tipo de problemas de redirección es mejor crear siempre rutas relativas a la carpeta raíz de la
aplicación web. Esto se consigue comenzando la ruta por una barra / seguida de la ruta de contexto:
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String tipoInforme = request.getPathInfo();
String ruta = request.getContextPath() + "/informes" + tipoInforme + ".jsp";
response.sendRedirect(ruta);
}
Ahora ya tenemos una ruta de redirección que no es relativa a la ubicación virtual del servlet.
Nota.Si invocamos sendRedirect() después de enviar una página dinámica al cliente se generará una
excepción del tipo java.lang.IllegalStateException.
4.2. Coordinando servlets usando «RequestDispatcher».
El uso del método sendRedirect() puede ser adecuado en aquellos casos en los que queremos informar al
navegador cliente de que lo queremos redireccionar. Pero esto obliga al navegador a que tenga que realizar
dos solicitudes para acceder a un recurso final.
4.2.1. Redirecciones internas en el servidor.
Como alternativa a las redirecciones externas, podemos aplicar un redirección interna en el propio servidor,
sin que el navegador cliente se entere. El navegador solicita un recurso y se le devuelve otro recurso
redireccionado internamente, pero a todos los efectos el navegador tiene la sensación de que se le ha devuelto
el recurso solicitado originalmente.
Las redirecciones internas se realizan usando un objeto de tipo javax.servlet.RequestDispatcher. Para obtener
un objeto de esta interfaz disponemos de los métodos:
▪ ServletRequest.getRequestDispatcher(String ruta), que admite rutas relativas al recurso.
▪ ServletContext.getRequestDispatcher(String ruta), que no admite rutas relativas (ruta debe empezar con "/")
Estos dos métodos retornan una instancia que queda asociada a la ruta del recurso. Y ahora disponemos de
dos métodos en el objeto RequestDispatcher:
▪ void forward(ServletRequest request, ServletResponse response)
Redirige la respuesta al recurso indicado en la ruta. Este redireccionamiento se produce en el servidor, sin
que el navegador cliente tenga constancia de ello.
▪ void include(ServletRequest request, ServletResponse response)
Permite incluir el recurso invocado en la ruta como parte de la respuesta actual. Todos los cambios que se
hagan en la cabecera o códigos de estado desde el recurso incluido serán ignorados.
Como ejemplo modificaremos la aplicación previa para realizar redirecciones internas:
package servlet;
import java.io.*;

81
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletInformes", urlPatterns = {"/informe/*"})
public class ServletInformes extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String tipoInforme = request.getPathInfo();
String rutaRaiz = "/informes" + tipoInforme + ".jsp";
RequestDispatcher despachador = request.getRequestDispatcher(rutaRaiz);
despachador.forward(request, response);
}
}
A destacar que ya no necesitamos concatenar la ruta de contexto. Al comenzar la ruta por / el método
getRequestDispatcher() considera ya la ruta como absoluta respecto a la carpeta raíz de la aplicación.
El servlet incluido o redireccionado puede acceder a la información de la petición original a través de diversos
atributos del contexto de la solicitud. Los nombres de los atributos dependen de si se ha invocado el método
forward() o include(), y los mismos pueden ser:
Nombres de atributos para include() Nombres de atributos para forward()
javax.servlet.include.request_uri javax.servlet.forward.request_uri
javax.servlet.include.context_path javax.servlet.forward.context_path
javax.servlet.include.servlet_path javax.servlet.forward.servlet_path
javax.servlet.include.path_info javax.servlet.forward.path_info
javax.servlet.include.query_string javax.servlet.forward.query_string
Los valores de estos atributos son los mismos que los métodos: getRequestURI(), getContextPath(),
getServletPath(), getPathInfo() y getQueryString de la interfaz HttpServletRequest. A estos atributos se acceden
como los atributos regulares, a través del método request.getAttribute(nombreAtributo).
4.2.2. Diferencias entre forward e include.
Para entender la diferencia ente usar el método forward() y el método include() supongamos dos servlets:
Servlet1 y Servlet2. Desde Servlet1 se redirigirá internamente a Servlet2.
El código de Servlet1 es el siguiente:
package servlet;
import java.io.*;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "Servlet1", urlPatterns = {"/servlet1"})
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Se añade contenido general al principio de la respuesta
response.setContentType("text/plain");
try (PrintWriter out = response.getWriter()) {
out.println("Inicio de servlet 1");
// Se redirige al informe concreto, el cual finaliza la respuesta
RequestDispatcher despachador = request.getRequestDispatcher("/servlet2");
despachador.forward(request, response);
// Se añade contenido final a la respuesta
out.println("Final de servlet 1");
}
}
}
Y el código de Servlet2 es el siguiente:
package servlet;
import java.io.*;

82
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "Servlet2", urlPatterns = {"/servlet2"})
public class Servlet2 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.println("Cuerpo de servlet 2");
}
}
Si invocamos Servlet1 obtendremos como resultado en el navegador:
Cuerpo de servlet 2
Es decir, la respuesta se completa en el servlet redirigido mediante forward(), ignorando el contenido previo y
posterior del canal de salida.
Si se cambia forward() por include() se obtiene como resultado en el navegador:
Inicio de servlet 1
Cuerpo de servlet 2
Final de servlet 1
Es decir, el contenido escrito al canal de salida desde el servlet redireccionado forma parte del contenido de la
salida del servlet invocado inicialmente.
4.3. Enviando códigos de estado para condiciones de error.
El protocolo HTTP define códigos de estado para condiciones de error como Recurso no encontrado, Recurso
movido permanentemente, o Acceso no autorizado. La clase HttpServletResponse provee los métodos sendError(int
status_code) y sendError(int status_code, String message), y las constantes SC_XXX para enviar códigos de
estado al cliente.
Por ejemplo, si un usuario no debe acceder a nuestro recurso según el valor de una variable estática podemos
invocar el código siguiente desde el método doPost() o doGet() de un servlet:
public class MiServlet extends HttpServlet {
private static int estado = 0;
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException {
if (estado > 0) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
"Página no accesible en este momento");
} else {
// proceso normal del servlet
}
}
}
Cuando el navegador cliente recibe este código de estado muestra una página de error con el mensaje
apropiado al usuario.
4.4. Captura y procesamiento de excepciones.
Tal como ocurre con cualquier programa Java, los servlets pueden recibir o generar excepciones durante su
ejecución. Estas excepciones se pueden gestionar desde el propio servlet o se puede implementar un
mecanismo en el contenedor web para redirigir un error o una excepción hacia una página de respuesta.
4.4.1. Gestión de excepciones enviando código de error.
Podemos usar una estructura try…catch…finally para captura excepciones y en los bloquescatch enviar un error
al navegador cliente mediante el método response.sendError(tipoError, mensaje). Para los tipos de error
podemos usar las constantes HttpServletResponse.SC_*.
También podemos usar el método response.setStatus(estado, mensaje) para enviar un estado de error al
navegador cliente. (Aunque se considera un método obsoleto para esta funcionalidad.) Lo habitual es usar
solamente setStatus(estado) para retornar los códigos SC_OK o SC_MOVED_TEMPORARILY.
La diferencia entre ambos métodos es que sendError() fuerza al contenedor de servlets a enviar una página de
error, mientras que setStatus() envía un código de estado al navegador sin mostrarlo.

83
Como ejemplo, el siguiente servlet analiza si se recibe un parámetro llamado "dir". Si es así devuelve un código
de OK, sino devuelve un código de error de solicitud mal formada:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "Servlet1", urlPatterns = {"/servlet1"})
public class Servlet1 extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (request.getParameter("dir")==null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,"Falta el parámetro 'dir'.");
} else {
response.setStatus(HttpServletResponse.SC_OK);
}
}
}
Si en un navegador solicitamos "/servlet1" obtendremos la siguiente página de error:
Figura 21

Si solicitamos "/servlet?dir" recibiremos una página en blanco.


4.4.2. Gestión de excepciones mediante redirecciones con «RequestDispatcher».
Como se ha visto, podemos usar RequestDispatcher para redireccionar a otro recurso. En este contexto
podemos capturar una excepción y redireccionar directamente a una página de error mediante los métodos
include() y forward().
Para ilustrar un ejemplo partiremos del siguiente servlet. Este servlet espera un usuario como parámetro y lo
evalúa.
package servlet;
import java.io.*;
import javax.security.sasl.AuthenticationException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletAutentifica", urlPatterns = {"/autentifica"})
public class ServletAutentifica extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String usuario= request.getParameter("usuario");
if (usuario == null) { // miramos si hay un usuario

84
request.getRequestDispatcher("/errores/faltaUsuario.html").forward(request, response);
} else if (!usuario.equals("Pedro")) {
request.getRequestDispatcher("/errores/usuarioNoValido.html").forward(request, response);
} else {
response.sendRedirect("paginaPrivada.jsp");
}
}
}
Si el parámetro "usuario" no existe se redirige a una página con un mensaje personalizado. Si el usuario no es
"Pedro" también se redirige a otra página con un mensaje personalizado.
Para que en el navegador se muestren páginas amigables de error crearemos las dos páginas HTML de error:
PÁGINA «/errores/faltaUsuario.html»:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<b>Falta el usuario, retorne a <a href="index.html">inicio</a></b>
</body>
</html>
PÁGINA «/errores/usuarioNoValido.html»:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
</head>
<body>
<b>El usuario no es válido, retorne a <a href="index.html">inicio</a></b>
</body>
</html>
Aunque éste no es el caso, puede ocurrir que el recurso invocado, a su vez, genere una excepción, la cual es
relanzada por estos dos métodos. Por este motivo se aconseja capturar las posibles excepciones que relance el
método forward():
try {
request.getRequestDispatcher("/errores/faltaUsuario.html").forward(request, response);
} catch (RuntimeException re) {
// realizar las acciones adecuadas y relanzar la excepción
throw re;
} catch(IOException ie) {
// realizar las acciones adecuadas y relanzar la excepción
throw ie;
} catch(ServletException se) {
// extraer la excepción real
Throwable t = se.getRootCause();
// realizar las acciones adecuadas y relanzar la excepción
throw se;
}
4.4.3. Gestión de excepciones declarativamente.
Los servidores web como Tomcat permiten registrar las páginas de error personalizadas de manera
declarativa, de forma que serán invocadas automáticamente cuando se produzca un código de error o una
excepción determinada. Este registro se realiza en el archivo descriptor web.xml. Debemos hacer el mapeado
de error o excepción con la página de error en la sección <error-page>, la cual cuenta con los siguientes
elementos:
<error-code> especifica un código de error Http (como 404). Debe ser único para cada mapeo de error.
<exception-type> especifica el tipo de excepción que se va a mapear. Debe ser único para cada mapeo.
<location> especifica el recurso que será lanzado al generarse la excepción. Debe empezar por /.

85
Los servlets sólo relanzan directamente las excepciones del tipo ServletException, IOException y
RuntimeException o sus subclases. Cualquier otro tipo de excepción deberá ser capturada en un bloque
try…catch y relanzada como una ServletException.
Modificaremos el servlet del ejemplo previo para que la página de error "/errores/faltaUsuario.html" sea
redireccionada cuando se lance una Exception, y que la página de error "/errores/usuarioNoValido.html" sea
redireccionada cuando se devuelva el código de error 403.
package servlet;
import java.io.*;
import javax.security.sasl.AuthenticationException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletAutentifica", urlPatterns = {"/autentifica"})
public class ServletAutentifica extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String usuario= request.getParameter("usuario");
if (usuario == null) { // miramos si hay un usuario
throw new ServletException(new Exception("Falta el usuario"));
} else if (!usuario.equals("Pedro")) {
response.sendError(response.SC_FORBIDDEN); // código 403: usuario no válido
} else {
response.sendRedirect("paginaPrivada.jsp");
}
}
}
Si el parámetro "usuario" no existe se lanza una excepción con un mensaje personalizado; esta excepción es
relanzada a través de una ServletException. Si el usuario no es "Pedro" se devuelve un código de error 403, y si
el usuario es correcto se redirige a una página privada para este usuario.
Ahora registraremos las páginas de error en el fichero descriptor web.xml:
<web-app>
<error-page>
<error-code>403</error-code>
<location>/errores/usuarioNoValido.html</location>
</error-page>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/errores/faltaUsuario.html</location>
</error-page>
</web-app>
También podemos utilizar el editor de NetBeans para registrar las páginas de error:

86
Figura 22

Podemos invocar el servlet para cada situación y comprobar que seremos redirigidos a estas páginas de error.
Nota. Las páginas de error que son lanzadas automáticamente deben tener al menos 512 bytes de tamaño
para que este mecanismo funcione.

PRÁCTICA
01. Crea una aplicación web que muestre un menú de enlaces en su página HTML inicial.
El menú constará de los siguientes enlaces:
¿Cuál es la capital de España?
¿Cuánto suman 5 y 6?
¿Quién escribió el Quijote?
Cada uno de estos enlaces deberá referenciar un mismo servlet. El servlet debe responder dinámicamente con
una página con la respuesta correcta.
02. Crea una aplicación web que permita a un usuario enviar mensajes que serán guardados en un fichero
dentro del sitio web.
El sitio web constará de los siguientes elementos:
- Una página HTML llamada "formularioInicial.html" con un formulario para que el usuario introduzca un
nombre y un comentario.
- Un fichero de texto llamado "comentarios.txt" ubicado en la carpeta raíz de la aplicación. Este fichero
tendrá dos líneas por usuario: una primera línea con un nombre, seguida de otra línea con el comentario.
- Una página HTML llamada "errores.html" que indicará que se produjo un error no controlado, y ofrecerá
un enlace para ir a la página "comentarios.txt".
- El formulario enviará los datos introducidos a un servlet llamado "servlet.ServletInicial"" que guardará el
nombre y comentario en el fichero "comentarios.txt".El servlet, una vez guardados los datos, debe devolver
como respuesta una página indicando que se realizó la operación, y un enlace a la página inicial.
Si se produce alguna excepción en el servlet debe ser gestionada automáticamente por el contenedor web y
redirigir a la página "errores.html".

03. Crea un sitio web con una carpeta llamada "imágenes" ubicada en la carpeta raíz de la aplicación. Mete en
esta carpeta varias imágenes.
Crea un servlet llamado "servlet.SirveImagen" que debe tener asociado el patrón URL "/imagen/*". Cuando
se invoque este servlet podrá indicarse en la parte final de su URL el nombre de un archivo de imagen; por
ejemplo:
http://localhost:8081/imagen/pez.jpg

87
El servlet deberá buscar la imagen en la carpeta de imágenes y si existe enviársela como respuesta al
navegador. Si la imagen no existe deberá enviar un código de error personalizado.

88
UNIDAD 13. GESTIÓN DE SERVLETS Y FILTROS
1. Gestión de sesiones
El concepto de sesión surge por la necesidad de mantener el estado durante la navegación de un cliente por
nuestro sitio web. Las sesiones se asocian a cada cliente y permiten almacenar información que es mantenida
y compartida a través de la navegación de varios recursos del sitio web.
1.1. Proceso de creación de sesiones.
Cada vez que un navegador cliente accede por primera vez a un recurso de nuestra aplicación web, el
contenedor web genera un identificador de sesión para el cliente. Dicho identificador es enviado en la
respuesta al cliente y a partir de este momento cualquier otra solicitud al mismo sitio web reenviará el
identificador de sesión al servidor. Cuando en la segunda solicitud el contenedor web reciba el identificador
de sesión establecerá un objeto de tipo HttpSession asociado al cliente. Este objeto se mantendrá hasta que
cancelemos explícitamente la sesión, se agote su tiempo de existencia, o el cliente deje de navegar por nuestra
aplicación web.
Importante. En circunstancias normales, la sesión se establece para un cliente la segunda vez que accede
a nuestro sitio web, y no en su primer acceso.
Figura 1

1.2. Información sobre la sesión.


Las sesiones de usuario son administradas mediante la clase javax.servlet.http.HttpSession, la cual provee
métodos para trabajar con la información de sesión.
▪ int getId()
Devuelve un identificador único generado para cada sesión.
▪ boolean isNew()
Retorna true siel cliente (navegador) nunca ha visto la sesión, normalmente porque acaba de ser creada
en vez de empezar una referencia a una petición de cliente entrante. Devuelve false para sesiones
preexistentes.
▪ long getCreationTime()
Devuelve la hora, en milisegundos en la que se creó la sesión.
▪ long getLastAccessedTime()
Devuelve la hora, en milisegundos, en que la sesión fue enviada por última vez al cliente.
▪ long getMaxInactiveInterval()
Devuelve la cantidad de tiempo, en segundos, que la sesión debería seguir sin accesos antes de ser
invalidada automáticamente. Un valor negativo indica que la sesión nunca se debe desactivar.
▪ void putValue (String nombre, Object valor)
▪ void putAttribute (String nombre, Object valor)
Crean una variable, si no existía ya, y le asignan un valor.
▪ Object getValue (String nombre)
▪ Object getAttribute (String nombre)
Recuperan el valor de una variable de nombre dado.
▪ String [] getValuesNames()
Retorna un array con los nombres de las variables de sesión
Para obtener la sesión actual y forzar su creación si no existiese se utiliza la siguiente instrucción:

89
HttpSession sesion = request.getSession(true);
Una vez creada una sesión podemos crear atributos de sesión y recuperarlos:
Vector items = new Vector();
sesion.setAttribute("vectorItems", items);
items = (Vector) sesion.getAttribute("vectorItems");
Si es necesario podemos forzar la destrucción de la sesión actual con la instrucción siguiente:
sesion.invalidate();
Importante. Si se invoca algún método para recuperar alguna información del objeto de sesión una vez
destruido se lanzará una excepción del tipo IllegalStateException.
1.3. Tiempo de expiración de una sesión.
En el archivo web.xml podemos establecer el tiempo de espera de una sesión antes de que se destruya por
inactividad. Por ejemplo, para establecer 30 minutos de espera:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
</web-app>
Un valor menor o igual a cero indica que la sesión nunca expirará.
La interfaz HttpSession también provee dos métodos para establecer este valor:
• void setMaxInactiveInterval(int seconds), establece el número máximo de segundos que debe pasar entre
llamadas del cliente antes de que se invalide la sesión (valor negativo para no expirar nunca).
• int getMaxInactiveInterval(), retorna el intervalo de expiración en segundos.
1.4. Identificadores de sesión.
Cada sesión establece un ID en el momento que se crea por la primera petición de un cliente. En ese
momento la sesión se establece en estado "new", de forma que el método HttpSession.isNew() retorna true. La
siguiente petición del cliente hará que la sesión pase al estado "joined" y el método isNew() retorne false.
Existen dos técnicas para soportar las sesiones:
▪ Usando cookies:
El contenedor de servlets usa las cabeceras http para recibir e enviar el id de sesión. Por ejemplo:
Solicitud HTTP POST
POST /servlet/testServlet HTTP/1.0
User-Agent= MOZILLA/1.0
cookie=JSESSIONID=61C4F23524521390E70993E5120263C6
Content-Type: application/x-www.formurlencoded

userid=john
Respuesta HTTP
HTTP/1.1 200 OK
Set-Cookie=JSESSIONID=61C4F23524521390E70993E5120263C6
Content-Type: application/x-www.formurlencoded

<html>
.............................
</html>
La línea JSESSIONID=61C4F23524521390E70993E5120263C6 es guardada en un archivo cookie del disco del
cliente.
▪ Usando reenvíos URL:
El id de sesión es añadido a las URLs de llamada. Por ejemplo, en un hiperenlace añadiríamos:
<a href="/servlet/AccountServlet;JSESSIONID=C084B32241B2F8F060230440C0158114">
Pero no es necesario añadir este identificador a mano, la clase HttpServletResponse provee dos métodos para
añadir el id a una URL:
• String encodeURL(String url), retorna la url con el id añadido.
• String encodeRedirectURL(String url), lo mismo para url usadas con el método sendRedirect().

90
Nótese que JSESSIONID no es enviado como un parámetro de llamada y por tanto no puede ser obtenido
con request.getParameter(). Estos dos métodos sólo añaden el id de sesión si el mecanismo de cookies no es
posible.

2. Gestión de eventos y registro de eventos.


Al igual que en las aplicaciones de escritorio los componentes Java Bean lanzan eventos y podemos
gestionarlos para responder con la ejecución de instrucciones a los mismos, el contenedor web también lanza
eventos relacionados con la aplicación y sus sesiones. Podemos gestionar estos eventos para realizar
inicializaciones y finalizaciones en cada aplicación y sesión.
La técnica para gestionar un evento es parecida a la vista en componentes Java Bean. Se crea una clase
observadora que debe implementar una interfaz determinada, y se registra una instancia de la clase
observadora ante el objeto que lanza el evento. Pero en este caso quien lanza los eventos es el contenedor
web, del cual no disponemos de su instancia. Tal como veremos a continuación, el registro de los
observadores se debe realizar en el archivo descriptor.
La gestión de estos eventos sigue los siguientes pasos:
1) Crear una clase que implemente una o varias interfaces oyentes.
2) Registrar la clase en el archivo web.xml dentro de la sección <listener>.
2.1. Eventos generados por el contexto de aplicación.
Al nivel de aplicación se generan eventos cuando se crea y destruye una aplicación, así como cuando se
modifican atributos de aplicación.
3.1.1. Eventos del arranque y destrucción de la aplicación.
Se usa la interfaz javax.servlet.ServletContextListenercuando queremos ser notificados de la inicialización y
destrucción del contexto de la aplicación. Por ejemplo, podemos crear una conexión al arrancar la aplicación
y destruirla al finalizar mediante esta interfaz. Los dos métodos que declara son:
void contextInitialized(ServletContextEvent sce), que es invocado cuando el contexto se inicializa.
void contextDestroyed(ServletContextEvent sce), que es invocado cuando el contexto se destruye.
La clase ServletContextEvent proporciona un método para obtener una referencia al ServletContext de la
aplicación. El siguiente ejemplo ilustra sobre la creación y cierre de una conexión al inicio y finalización de la
aplicación respectivamente:
package oyentes;
import java.sql.Connection;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ObservadorContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
try {
Connection c = Repositorio.obtenerConexion();
sce.getServletContext().setAttribute("conexion", c);
}catch(Exception e) { }
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
try {
Connection c = (Connection) sce.getServletContext().getAttribute("conexion");
c.close();
}catch(Exception e) { }
}
}
Podemos registrar esta clase observadora en el archivo descriptor de la siguiente manera:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<listener>
<listener-class>oyentes.ObservadorContextListener</listener-class>
</listener>
</web-app>

91
En este ejemplo, al arrancar la aplicación se obtiene una conexión desde una clase repositorio y se guarda en
un atributo del contexto de la aplicación. Esta conexión estará disponible durante la vida de la aplicación y
estará disponible desde cualquier servlet o fichero JSP. Cuando se destruye la aplicación se cierra dicha
conexión.
3.1.2. Eventos de gestión de atributos de aplicación.
Se usa la interfaz javax.servlet.ServletContextAttributeListenerpara recibir notificaciones acerca de los cambios
en la lista de atributos del contexto de aplicación. Sus métodos son:
void attributeAdded(ServletContextAttributeEvent scae), se invoca al añadir un nuevo atributo.
void attributeRemoved(ServletContextAttributeEvent scae), se invoca al quitar un atributo.
void attributeReplaced(ServletContextAttributeEvent scae), se invoca al reemplazar un atributo.
Por ejemplo, podemos controlar si el atributo "conexión" creado previamente es reemplazado y comprobar
en ese caso su disponibilidad:
package oyente;
import java.sql.Connection;
import java.sql.SQLException;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
public class ObservadorContextAttributeListener implements ServletContextAttributeListener {
@Override
public void attributeAdded(ServletContextAttributeEvent event) {
}
@Override
public void attributeRemoved(ServletContextAttributeEvent event) {
}
@Override
public void attributeReplaced(ServletContextAttributeEvent event) {
if (event.getName().equals("conexion")) {
Connection c = (Connection) event.getServletContext().getAttribute("conexion");
try {
if (c==null || c.isClosed()) {
c = Repositorio.obtenerConexion();
event.getServletContext().setAttribute("conexion", c);
}
} catch (SQLException ex) {
}
}
}
}
Una vez registrada esta clase en el archivo descriptor, cada vez que se modifique el atributo "conexión" se
comprueba que no haya quedado a nulo o la nueva conexión esté cerrada.
2.2. Eventos generados por el contexto de las sesiones.
Se generan eventos de sesión cuando se crean y finalizan su tiempo, así como cuando cambia algún atributo
almacenado en la sesión.
2.2.1. Eventos de inicio y fin de una sesión.
Un observador que quiera recibir notificaciones del inicio y finalización de una sesión debe implementar la
interfaz javax.servlet.http.HttpSessionListener.
Esta interfaz define dos métodos:
• void sessionCreated(HttpSessionEvent se), se invoca cuando la sesión es creada.
• void sessionDestroyed(HttpSessionEvent se), se invoca cuando la sesión finalzia su tiempo.
El parámetro de tipo HttpSessionEvent proporciona el método getSession(), que devuelve el objeto HttpSession
que representa la sesión creada.
Por ejemplo, la siguiente implementación nos permite crear en cada sesión un atributo llamado "usuarioActual"
donde posteriormente podemos guardar el nombre del usuario actual de la sesión:
package oyentes;
import javax.servlet.http.HttpSessionEvent;
public class OyenteSesion implements javax.servlet.http.HttpSessionListener{
@Override

92
public void sessionCreated(HttpSessionEvent se) {
se.getSession().setAttribute("usuarioActual", "");
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
}
}
Esta clase debe ser registrada en el fichero web.xml de la siguiente manera:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<listener>
<listener-class>oyentes.OyenteSesion</listener-class>
</listener>
</web-app>
Ahora, desde cualquier servlet o página JSP podemos recuperar el objeto HttpSession actual y leer o escribir
en este atributo con los métodos HttpSession.getAttribute() y HttpSession.setAttribute(), o eliminarlo con el
método HttpSession.removeAttribute().
2.2.2. Eventos de gestión de atributos de sesión.
En el contexto de una aplicación web puede ser interesante saber qué atributos de sesión se van creando y
destruyendo. A veces se puede utilizar el ciclo de vida de un atributo de sesión para desencadenar ciertas
acciones.
Un observador que quiera recibir notificaciones del ciclo de vida de los atributos de sesión debe implementar
la interfaz javax.servlet.http.HttpSessionAttributeListener.
Esta interfaz define tres métodos:
• void attributeAdded(HttpSessionBindingEvent event), es invocado cuando se añade un atributo de sesión.
• void attributeRemoved(HttpSessionBindingEvent event), es invocado cuando se elimina unatributo de sesión.
• void attributeReplaced(HttpSessionBindingEvent event), es invocado cuando un atributo de sesión cambia de
valor.
La clase HttpSessionBindingEvent proporciona los métodos:
• getName(): retorna el nombre asociado al atributo de sesión.
• getValue(): retorna el valor asociado al atributo de sesión.
• getSession(): retorna el objeto HttpSession que referencia la sesión.
La clase oyente que implemente HttpSessionAttributeListener debe ser registrada en el archivo descriptor de la
misma forma vista para la interfaz HttpSessionListener.
Un ejemplo práctico de uso de esta interfaz puede ser el siguiente. Al comenzar una sesión se crea el atributo
"usuarioActual", y varios servlets o páginas JSP puede modificar su valor. Cuando se modifica el usuario actual
queremos recuperar preferencias del usuario guardadas en un almacén de datos y dejarlas disponibles en otro
atributo de sesión llamado "preferencias":
package oyentes;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class OyenteAtributosSesion implements HttpSessionAttributeListener{
@Override
public void attributeAdded(HttpSessionBindingEvent event) {
if (event.getName().equals("usuarioActual")) {
HttpSession sesion=event.getSession();
sesion.setAttribute("preferencias", preferenciasUsuario(sesion.getAttribute("usuarioActual").toString()));
}
}
@Override
public void attributeReplaced(HttpSessionBindingEvent event) {
if (event.getName().equals("usuarioActual")) {
HttpSession sesion=event.getSession();
sesion.setAttribute("preferencias", preferenciasUsuario(sesion.getAttribute("usuarioActual").toString()));
}
}
@Override

93
public void attributeRemoved(HttpSessionBindingEvent event) {
if (event.getName().equals("usuarioActual")) {
event.getSession().removeAttribute("preferencias");
}
}
private String preferenciasUsuario(String usuario) {
return ""; // debe recuperar las preferencias de usuario
}
}
En este ejemplo, cada vez que se crea o reemplaza el valor del atributo usuarioActual se leen las preferencias
del usuario y se guardan en el atributo preferencias. Si se elimina el atributo usuarioActual también se eliminan
de memoria sus preferencias.
2.2.3. Autogestión de objetos de sesión.
A veces puede interesarnos que los propios objetos que almacenamos el atributos de sesión quieran ser
conscientes de cuándo son añadidos o retirados de la sesión. Para ello la interfaz
javax.servlet.http.HttpSessionBindingListener debe ser implementada por aquellas clases cuyos objetos necesitan
recibir notificación de cuándo son añadidos o eliminados de una sesión.
Esta interfaz declara dos métodos:
• void valueBound(HttpSessionBindingEvent event), es invocado cuando un objeto es añadido a la sesión.
• void valueUnbound(HttpSessionBindingEvent event), es invocado cuando el objeto es eliminado de la sesión.
El parámetro event, de tipo HttpSessionBindingEvent es el mismo que el proporcionado en la interfaz
HttpSessionAttributeListener.
Esta interfaz se diferencia de HttpSessionAttributeListener en que la clase que la implementa no tiene que
registrarse en el archivo descriptor, ya que este tipo de eventos son recibidos por el propio objeto que se
añade o elimina de la sesión.
Siguiente con el ejemplo del usuario actual y sus preferencias, podemos crear una clase Usuario que incluya su
información de preferencias, pero que sólo las recupere cuando es añadido a una sesión:
public class Usuario implements HttpSessionBindingListener {
private String nombre;
private String preferencias;
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getPreferencias() {
return preferencias;
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
// código para recupear y asignar las preferencias del usuario
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
preferencias=null;
}
}
En este caso el método valueBound() será invocado cuando un objeto Usuario sea añadido a la sesión, y se
encargará de recuperar la información de preferencias del usuario desde algún almacén de datos.
2.2.4. Migración de sesión.
En un entorno distribuido la sesión debe ser mantenida entre todos los ordenadores que forman parte del
grupo de servidores.
En este tipo de configuración los objetos HttpSession (y sus atributos) son movidos de una máquina virtual a
otra según lo requieran las solicitudes. Por tanto, para cada sesión existe un único objeto HttpSession
compartido por todas las máquinas que forman parte del grupo de servidores.
Sin embargo existe un ServletContex por cada máquina virtual, y un ServletConfig para cada una de las
instancias de los servlets ejecutados en cada máquina virtual.
94
Podemos utilizar un observador que implemente al interfaz javax.servlet.http.HttpSessionActivationListener para
gestionar notificaciones de la migración de la sesión.
Esta interfaz declara dos métodos:
• void sessionDidActivate(HttpSessionEvent se), se invoca justo después que la sesión migra y se activa.
• void sessionWillPassivate(HttpSessionEvent se), se invoca cuando la sesión pasa a migrar.
2.3. Registro de eventos (logging).
Normalmente, usamos System.out para escribir mensajes y depurar información en la consola. En el caso de
las aplicaciones web esto no factible. Por ello la API servlet proporciona un soporte básico para registrar
información de javax.servlet.ServletContext y javax.servlet.GenericServlet (de la cual derivan los servlets). Los
dos métodos que proporcionan estas clases son:
▪ void log(java.lang.String msg), para escribir un mensaje a un archivo de registro.
▪ void log(java.lang.String message, java.lang.Throwable t), para escribir un mensaje y la pila de trazas del
objeto Throwable indicado.
La única diferencia entre los métodos log() proporcionados por GenericServlet y ServletContext es que los
primeros añaden el nombre del servlet al mensaje mientras que ServletContext no lo hace.
El archivo actual usado para los mensajes de registro depende del contenedor de servlet. Muchos
contenedores usan diferente archivos en aplicaciones web diferentes. Tomcat guarda esta información en el
archivo "conf/server.xml", ubicado en su carpeta de instalación. También podemos configurar el archivo de
registro usando un elemento <Logger> en el archivo context.xml, ubicado dentro de la carpeta META-INF de
nuestro proyecto. Los elementos Logger le indican a Tomcat hacia dónde deben ser enviados los registros:
<?xml version="1.0" encoding="UTF-8"?>
<Context antiJARLocking="true" path="/WebAplicacion">
<Logger className="org.apache.catalina.logger.FileLogger"
directory="logs" prefix="localhost_log" suffix=".txt" timestamp="true"/>
</Context>
Lo anterior indica que los registros de Tomcat deben ser enviados al archivo /logs/localhost_log.txt.
El siguiente método doGet() de un servlet usa los métodos de GenericServlet para registrar una excepción:
public void doGet(HttpServletRequest req, HttpServletResponse res) {
try {
// lógica del negocio aquí
} catch(SQLException e) {
// Registramos la excepción
log("Excepción en servlet", e);
// envolvemos la excepción en una ServletException y la relanzamos
throw new ServletException("Excepción envuelta", e);
}
}
Un posible texto generado, en el archivo de registro <tomcat-root>/logs/localhost_log.2001-12-28.txt, por la
excepción podría ser:
2001-12-28 21:48:50 TestServlet: Excepción en servlet
java.sql.SQLException: sql excepción
At cgscwcd.chapter7.TestServlet.doGet (TestServlet.java:46)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:740)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
at org.apache.catalina.servlets.InvokerServlet.serveRequest(
InvokerServlet.java:446)
at org.apache.catalina.servlets.InvokerServlet.doGet(
InvokerServlet.java:180)
…………..

3. Gestión de estado
Un problema común en el desarrollo de una aplicación web es conservar el estado del usuario, o datos
relevante para la aplicación. Normalmente necesitamos conocer (y recordar) información acerca de los
usuarios, acerca de la solicitud en la que se esté trabajando, y a acerca de los datos que deben guardarse. Estos
datos de estado incluyen cosas como el rol del usuario dentro de la aplicación (como director o empleado),
datos que existen como parte de una transacción (como un carro de compra), o datos que deben almacenarse

95
(como el historial de una transacción). Efectivamente, necesitamos gestionar esta estado para proporcionar
una buena experiencia de usuario a nuestro sitio y que el usuario confíe en nuestra aplicación.
3.1. El modelo de hilos del contenedor web
En un entorno real, nuestras aplicaciones web recibirán múltiples peticiones simultáneas sobre los servlets.
Para optimizar los tiempos de respuesta el contenedor de servlets implementa un modelo de hilos de
ejecución para lanzar el código de cada petición de servlets (Multithreaded Servlet Model).
El contenedor de servlets mantiene una cola de hilos de trabajo para servir cada petición (Worker Thread Pool).
También tiene un hilo que actúa de despachador (Dispatcher Thread) para gestionar los hilos de trabajo.
Figura 2

3.1.1. Modelo multi-hilos.


Cuando el contenedor recibe una petición para un servlet, el hilo despachador selecciona un hilo de trabajo
libre para que responda a la petición. El hilo de trabajo ejecuta el método service() de la instancia del servlet.
Si se recibe otra petición, el contenedor elige otro hilo de trabajo para despacharla, sea o no sobre el mismo
servlet. De esta forma, cada petición de un servlet se ejecuta sobre un hilo independiente, y sobre una única
instancia por cada servlet.
3.1.2. Modelo simple de hilos.
Supongamos un servlet que escribe cierta información en un archivo de disco. Si este servlet recibe múltiples
peticiones simultáneas puede llegar a producirse un bloqueo puesto que cada petición requiere que se escriba
sobre el mismo archivo simultáneamente. En estos casos, interesa que las peticiones del servlet sean
secuenciales y no simultáneas.
Para conseguir esta restricción debemos hacer que nuestro servlet implemente la interfaz
javax.servlet.SingleThreadModel. (Esta interfaz no tiene ningún método.)
En este modelo, para mejorar la eficiencia, el contenedor puede crear varias instancias de un mismo servlet,
de forma que cada requerimiento es ejecutado por un hilo de trabajo sobre una instancia distinta. Esto puede
resultar peligroso cuando se trabaja con recursos compartidos.
Como alternativa a implementar SingleThreadModel se puede sincronizar el método service (o los métodos
do_() correspondientes), aunque con esta solución se puede degradar considerablemente el rendimiento de la
aplicación.
3.2. Fundamentos de gestión de estado.
El contenedor web ofrece varios lugares donde se puede almacenar la información de estado y formas
sencillas de acceder a la información de estado. Sin embargo, debe planificarse el uso de estos mecanismos
con cuidado.Si utilizamos la ubicación incorrecta, es posible que no seamos capaces de recuperar un valor
cuando es esperado.Por otra parte, la mala planificación de la gestión del estado se traduce con frecuencia en
un rendimiento inferior.
En general, se debe tener cuidado con el mantenimiento de grandes cantidades de datos de estado, ya que o
bien consume memoria del servidor, si se almacena en el servidor, o ralentizan el traslado de la página web en
el navegador, si está incluido en una página web.
Si necesitamos almacenar los valores de estado, se puede elegir entre almacenamiento de estado del lado
cliente o almacenamiento de estado de lado servidor.

96
3.2.1. Gestión de estado en el lado cliente.
Al almacenar la información de estado en el cliente, se asegura de que no se utilizan los recursos de los
servidores.Sin embargo, hay que tener en cuenta que toda la información de estado del lado cliente debe ser
enviado entre el servidor web y el navegador web, y este proceso puede ralentizar el tiempo de carga de
página. Hay que utilizar almacenamiento de estado del lado del cliente sólo para pequeñas cantidades de
datos.
Las técnicas de almacén estado en el lado cliente involucran el uso de:
• Campos de texto oculto. El elemento HTML <input type= "hidden" /> permite almacenar texto en el
código de la propia página que puede ser recuperado como parte del posteo de un formulario.
•Cookies. Las cookies son pequeños archivos de texto que gestionan los navegadores, y cuyo contenido es
devuelto como parte de la solicitud.
• Almacenes locales. El estándar HTML5 ha introducido un nuevo mecanismo para almacenar datos
grandes en el disco del cliente que sustituyen el uso habitual de cookies. Este mecanismo se gestiona
mediante código script.
•Las cadenas de consulta. Una cadena de consulta es la parte de la URI después del signo de interrogación y
se utiliza a menudo para comunicar los valores de formulario y otros datos al servidor.Puede utilizarse la
cadena de consulta para preservar una pequeña cantidad de datos de la solicitud de una página a otra.Todos
los navegadores admiten cadenas de consulta, pero algunos imponen un límite de 2.083 caracteres en la
longitud de la URL.No debe colocar ninguna información confidencial en las cadenas de consulta, ya que es
visible para el usuario, para cualquier persona observando su sesión, o cualquier persona monitoreando el
tráfico web.
3.2.2. Gestión de estado en el lado servidor.
A menudo, no es solamente práctico almacenar el estado en el cliente. El estado podría implicar más cosas y
así ser demasiado grande para transmitirse entre cliente y servidor. Quizás tenemos un estado que necesite ser
asegurado y aún cifrado no debería ser pasado a través de una red. Además, podemos tener un estado que no
es específico del cliente y sí global para todos los usuarios de la aplicación. En todos estos escenarios
seguimos necesitando almacenar el estado. Si el cliente no es la opción correcta, debemos contemplar al
servidor para la necesaria gestión del estado.
La información de estado almacenada en el lado servidor consume recursos del servidor, por lo que debe
tener cuidado de no abusar del uso de almacenamiento de estado del servidor o tendremos problemas de
rendimiento.
Los siguientes sitios almacenan la información de estado en la memoria del servidor:
• El estado de la solicitud. Se pueden almacenar datos locales creando atributos en el objeto
HttpServletRequest asociado con cada solicitud.
• Estado de aplicación.Se pueden almacenar datos compartidos creando atributos en el objeto
ServletContext asociado con cada aplicación.
• Estado de la sesión.Se pueden almacenar datos durante la navegación por un sitio web creando atributos
en el objeto HttpSessioni asociado con cada sesión.
• Tablas de bases de datos. Si el sitio utiliza una base de datos subyacente, como la mayoría de los sitios lo
hacen, podemos almacenar la información de estado en sus tablas.Este es un buen lugar para almacenar
grandes volúmenes de datos de estado que no se pueden colocar en la memoria del servidor o en el equipo
cliente.
3.3. Uso de campos de texto oculto.
Una de las técnicas más simples para guardar pequeños datos en el lado cliente es el uso de elementos HTML
<input type="hidden" />. Si embebemos estos elementos dentro de un formulario no serán mostrados en la
ventana del navegador, pero el dato que tengan asignado en su propiedad value serán enviado cuando
posteemos los datos del formulario.
Un ejemplo práctico del uso de estos campos de texto ocultos es para guardar una colección de datos que
podamos manipular mediante código script, para después devolverlos al servidor. Haremos una página que
permita editar los datos de perfil de un usuario. La página incluirá un formulario con un campo oculto donde
asignaremos un objeto con los atributos de perfil en formato JSON, también incluirá un lista desplegable para
seleccionar un atributo de perfil, y un campo de edición para editar el contenido del atributo de perfil.
<!DOCTYPE html>
<html>

97
<head>
<title>Perfiles de usuario</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
Nombre del perfil <select id="perfil" onchange="seleccionaPerfil()"></select>
<br>
Valor <input type="text" id="contenido" />
<br>
<button onclick="cambiarPerfil()">Cambiar perfil</button>
<form action="guadarPerfiles" method="post">
<input type="hidden" id="perfiles" name="perfiles"
value='{"Nombre":"","Edad":"","Profesion":"","Ciudad":""}' />
<input type = "submit" value = "Guardar perfiles" />
</form>
<script type="text/javascript">
var selPerfil = document.getElementById("perfil");
var campoPerfiles = document.getElementById("perfiles");
var perfiles = JSON.parse(campoPerfiles.value);
rellenaSelectPerfiles();

// Esta función rellena el elmento <select /> con los atributos de perfil
function rellenaSelectPerfiles() {
var claves = Object.keys(perfiles);
for(var i in claves ) {
var opcionSel = document.createElement("option");
opcionSel.innerHTML = claves[i];
opcionSel.value=claves[i];
selPerfil.appendChild(opcionSel);
}
}
// Esta función actualiza un perfil y el contenido del campo oculto
function cambiarPerfil() {
var txtContenido = document.getElementById("contenido");
perfiles[selPerfil.value] = txtContenido.value;
document.getElementById("perfiles").value = JSON.stringify(perfiles);
}
// Esta función recupera el contenido de un pefil seleccionado
function seleccionaPerfil() {
var perfil = document.getElementById("perfil");
document.getElementById("contenido").value = perfiles[perfil.value];
}
</script>
</body>
</html>
El aspecto de esta página es el siguiente:
Figura 3

98
Cuando se pulse el botón «Guardar perfiles» se posteará el contenido del campo oculto a un servlet asociado
a la acción guadarPerfiles.
3.4. Uso de cookies
Las aplicaciones Web normalmente necesitan hacer un seguimiento de los usuarios entre solicitudes de
página. Estas aplicaciones necesitan asegurarse de que el usuario que hizo la primera solicitud es el mismo que
está haciendo las solicitudes siguientes. Este tipo de seguimiento se realiza por defecto con algo llamado
cookies.
Una cookie es una pequeña cantidad de datos que se escriben en el cliente para ser guardados y después
pasados con las solicitudes a nuestro sitio. Para que las cookies persistan se escriben en archivos de texto en la
máquina del cliente. Estas cookies están pensadas para sobrevivir si el usuario cierra el navegador y después
vuelve a abrirlo. También se puede escribir cookies temporales a la memoria del navegador cliente. Estas
cookies se usan sólo durante una sesión Web dada, y se pierden cunado el navegador se cierra.
Así pues, el uso más habitual de las cookies es identificar a un usuario mientras visita varias páginas dentro de
nuestro sitio. Sin embargo, también podemos usar las cookies para almacenar información de estado u otras
preferencias del usuario.
La siguiente figura ilustra sobre cómo un cliente Web y un servidor usan las cookies. Primero (paso 1), el
cliente Web solicita una página al servidor. Si el cliente no ha visitado el servidor antes, entonces no tiene
ninguna cookie que enviar. Cuando el servidor Web responde a la solicitud (paso 2), el servidor incluye una
cookie en la respuesta; esta cookie es escrita en el navegador del cliente o el sistema de archivos. El cliente
Web entonces envía esta cookie con cada solicitud siguiente a cualquier página del mismo sitio (pasos 3 y 4).
Figura 4

3.4.1. La clase «Cookie».


Para Java, las cookies son objetos de la clase javax.servlet.http.Cookie. Se usa esta clase para crear una cookie
especificando una clave y un valor. Posteriormente se puede enviar la cookie a un navegador cliente. El valor
de una cookie puede identificar de forma aislada a un cliente, y por ello las cookies son usadas normalmente
para la gestión de sesiones. Una cookie tiene un nombre, un valor único, y atributos opcionales como un
comentario, calificadores de rutas y dominios, el tiempo de vida máximo, y un número de versión.
El método getCookies() del objeto request retorna un array de los objetos Cookie guardados en el navegador
cliente. Se puede crear una cookie con el siguiente código:
Cookie c = new Cookie("nombre", "valor");
La clase Cookie incluye los siguientes métodos.
Método Descripción
getComment() Retorna el comentario descriptivo de la cookie, o null si no se ha definido.
getMaxAge() Retorna el tiempo de vida máximo (en segundos) desde que se creó la cookie.
getName() Retorna el nombre de la cookie.
getPath() Retorna el prefijo de todas las url's para las cuales la cookie fue destinada.
getValue() Retorna el valor de la cookie como un string.
setComment(String) Asigna un comentario descriptivo sobre el propósito de la cookie.
setMaxAge(int) Asigna el tiempo de vida máximo (en segundos) de la cookie. Una cookie expira
si pasan los segundos especificados desde que se creó. Si se asigna valor cero la
cookie es eliminada.
setPath(String) Asigna la url de los recursos que pueden acceder a la cookie.
setValue(String) Asigna el valor de la cookie.
3.4.2. Cómo se crea una cookie.
Una aplicación Web crea una cookie enviándola al cliente como una cabecera en una respuesta HTTP. Desde
luego, Java hace la escritura a y la lectura de la colección de cookies una tarea relativamente sencilla. Para
99
añadir una cookie a la colección de cookies y hacer que se escriba en el navegador, se utiliza el método
response.addCookie(). Este método recibe como argumento un objeto Cookie.
Para ver cómo funciona el mecanismo de crear una cookie, vamos a crear un formulario que solicite un
nombre de usuario. El formulario posteará el nombre a un servlet que se encargará de crear una cookie que
almacene este nombre y redirigirá la respuesta a la página inicial del sitio web:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Perfiles de usuario</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<form method="post" action="registraUsuario">
<b>Escribe tu nombre: </b><input type="text" name="nombreUsuario" value="${cookie.usuario}">
<br>
<input type="submit" value="Entra">
</form>
</body>
</html>
A resaltar en el código de esta página el uso de la expresión EL ${cookie.usuario}. Esta expresión permite
recuperar fácilmente el valor asociado a una cookie denominada usuario. Si la cookie no existe no se escribirá
nada en el campo de edición, pero si existe se escribirá el último nombre guardado. Ahora crearemos o
modificaremos la cookie desde el siguiente servlet:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletRegistraUsuario", urlPatterns = {"/registraUsuario"})
public class ServletRegistraUsuario extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String usuario = request.getParameter("nombreUsuario");
Cookie cookie = new Cookie("usuario", usuario);
response.addCookie(cookie);
response.sendRedirect("index.html");
}
}
Importante. Las cookies se crean siempre añdiéndolas en la colección del objeto response, y para que se
almacenen en el cliente debe completarse la respuesta. Si la cookie ya existe en el cliente se sustituirá su
valor.
3.4.3. Cómo controlar el alcance de una cookie.
Las cookies se especifican para un dominio de sitio Web o para un directorio dentro de este dominio. Por
esta razón un navegador no debe enviar nuestras cookies a otro sitio web. Por defecto, los navegadores no
envían nuestras cookies a un sitio Web con un nombre de host diferente. Por tanto sólo tenemos que
controlar el alcance de las cookies de nuestro sitio. Podemos limitar este alcance a un directorio específico de
nuestro servidor Web o expandir el alcance al dominio completo. El alcance de nuestras cookies determina
qué páginas tienen acceso a la información embebida en la cookie. Si limitamos el alcance a un directorio,
sólo los recursos dentro de este directorio tendrán acceso a la cookie. Se controla el alcance de una cookie de
forma individualizada. Para limitar el alcance de una cookie a un directorio debemos asignar la propiedad Path
de la clase Cookie. El siguiente código muestra cómo hacer esto:
cookie.setPath("/mispaginas");

100
Ahora el navegador enviará la cookie a cualquier página de la carpeta "/mispaginas", incluido cualquier servlet
con este patrón URL. Sin embargo, las páginas fuera de esta carpeta no obtendrán la cookie, aunque estén en
el mismo servidor.
Para expandir el alcance de una cookie al dominio completo, debemos asignar la propiedad Domain de la clase
Cookie. El siguiente código muestra esto:
Cookie.setDomain("midominio.com");
Asignando la propiedad Domain a "midominio.com" provocamos que el navegador envíe la cookie a cualquier
página del dominio "midominio.com". Esto puede incluir aquellas páginas que pertenecen al sitio
www.midominio.com, intranet.midominio.com, o private.midominio.com. Similarmente, podemos asignar la
propiedad Domain a un nombre de host completo, limitando la cookie a un servidor específico.
3.4.4. Lectura de cookies.
Siguiendo con el ejemplo previo mostraremos cómo leer el contenido de una cookie. Previamente se había
creado una cookie que guardaba un nombre de usuario. Ahora crearemos un servlet que recuperará ese
nombre y mostrará una página dinámica de saludo.
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletRegistraUsuario", urlPatterns = {"/registraUsuario"})
public class ServletRegistraUsuario extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Cookie cookie = this.getCookie(request, "usuario");
String usuario = cookie == null ? "" : cookie.getValue();
try (PrintWriter out = response.getWriter()) {
out.println("<html><head></head><body>");
out.println("<p>Bienvenido: " + usuario + "</p>");
out.println("</body></html>");
}
}
private Cookie getCookie(HttpServletRequest request, String nombre) {
if (request.getCookies() != null) {
for (Cookie cke : request.getCookies()) {
if (cke.getName().equals(nombre)) {
return cke;
}
}
}
return null;
}
}
Las cookies se leen desde la colección que posee el objeto request. La clase HttpServletRequest sólo ofrece el
método getCookies() para recuperar las cookies; por eso en este ejemplo se ha creado el método getCookie()
para recuperar un cookie por nombre. A destacar que si no hay cookies asociadas el método
request.getCookies() retorna null.
3.4.5. Cómo saber si las cookies están habilitadas en el navegador cliente.
Los navegadores de Internet permiten bloquear el uso de las cookies, por eso es importante asegurarnos
desde código de si podremos o no usar cookies en nuestra aplicación web.
Podemos averiguar si el navegador cliente admite cookies consultando la cabecera "cookie" de la siguiente
forma:
String admiteCookies = request.getHeader("cookie");
if ( admiteCookies == null ) {
// El navegador cliente no admite cookies
}
Si la respuesta de la cabecera devuelve el valor nulo es que el navegador cliente no admite cookies, sino
retorna el identificador de sesión.

101
3.5. Uso de almacenes locales del lado cliente.
Las cookies sólo pueden almacenar una pequeña cantidad de datos, no más de 4 KB. Hay una importante
razón para esta limitación. Las cookies están diseñadas para ser enviadas y recibidas hacia y desde el servidor
web con cada solicitud y respuesta de una página. Si fuesen muy grandes, el ancho de banda requerido podría
impactar negativamente en la respuesta de la aplicación. Sin embargo, así como la web ha evolucionado, el
uso de la cookies también. Cada vez más se utilizan para mucho más que guardar tickets de sesión. Los
cookies han pasado a almacenar datos importantes del perfil de usuario, historiales de página y otros datos.
El mayor peligro es guardar datos de usuario en cookies que puedan ser compartidos intencionalmente entre
sitios web, y recientemente se han convertidos en famosos por traicionar la información personal sin
consentimiento. En la Unión Europea, la legislación reciente obliga a los sitios web alojados en estos países a
notificar a los usuarios si quieren usar cookies, y permitirles desactivarlas.
Otro tema del cual ser conscientes es que los usuarios a menudo abrenvarias pestañas cuando navegan por un
único sitio web. Los datos dentro de la cookies son compartidos entre estas pestañas, pero las cookies no
definen mecanismos de sincronización o concurrencia. Esto puede provocar efectos no deseados si los
valores en una cookie cambian en una pestaña y entonces se utilizan en mitad del proceso de una segunda
pestaña.
En una semántica web incrementada, las cookies son menos útiles, y plantean claramente algunas
preocupaciones significativas si las usamos mal. Las cookies nunca fueron diseñadas como una tecnología de
almacenamiento, y HTML5 introduce nuevos paradigmas de almacén en el lado cliente que cubren mucha de
las limitaciones de las cookies. Estas tecnologías incluyen el API Session Storage y el API Local Storage.
3.5.1. Persistiendo datos usando el almacén de sesión.
El almacén de sesión es un mecanismo de persistencia de los navegadores que permite almacenar datos de
texto sobre el dispositivo donde se ejecuta el navegador. Como su nombre implica, los datos se almacenan
durante la sesión del usuario actual. Cuando el usuario cierra el navegador, el almacén de sesión se vacía
automáticamente. El almacén de sesión es ampliamente soportado y es implementado por la mayoría de
navegadores, incluyendo Internet Explorer, desde 2009. Podemos acceder al almacén de sesión usando la
propiedad sessionStoragedel objeto window en código JavaScript, Podemos saber si el navegador implementa
el almacén de sesión consultado la presencia de esta propiedad, tal como sigue:
if( window.sessionStorage ) {
...
}
El almacén de sesión almacena cada dato de sesión con una clave única; podemos proporcionar la clave
cuando guardamos un valor, y se usa la misma clave para recuperar el dato. El API Session Storage
proporciona tres caminos para almacenar y recuperar datos:
• Las funciones setItem()ygetItem(). La función setItem()recibe una clave y el dato a almacenar. La función
getItem()recibe una clave y retorna el dato asociado. Si la clave no existe retorna el valor null.
sessionStorage.setItem("miClave","algún texto");
var textFromSession = sessionStorage.getItem("miClave");
• Sintaxis de array asociativo. Podemos usar la notación de array para consultar el objeto sessionStorage.
sessionStorage["miClave"] = "algún texto";
var textFromSession2 = sessionStorage["miClave"];
• Seudo-propiedades. Podemos añadir propiedades para cada clave al objeto sessionStorage.
sessionStorage.miClave = "algún texto";
var textFromSession3 = sessionStorage.miClave;
Nota:No es necesario usar el objeto windowpara acceder a sus propiedades. Podemos llamarlas
directamente, porque window es el contexto de llamada por defecto.
Si necesitamos persistir objetos en el almacén de sesión, podemos serializarlos a un string JSON usando la
función JSON.stringify().
Los objetos del almacén de sesión son también accesibles como un array de elementos con un índice de tipo
long. Podemos comprobar la propiedad lengthpara saber cuántas claves hay en al objeto sessionStorage.
Podemos recuperar la clave de cada objeto usando la función key(). Esta información puede ser muy útil para
iterar sobre los objetos, tal como se muestra en el siguiente ejemplo, el cual crea una lista de claves en el
objeto sessionStoragey las muestra en un elemento <div>:
var listDiv = document.getElementById("myList");
for(var i=0; i<sessionStorage.length; i++)

102
{
listDiv.innerHTML += "<br />" + sessionStorage.key(i);
}
Para eliminar un elemento del almacén de sesión se usa el método removeItem().
sessionStorage.removeItem("myKey");
Para vaciar el almacén de sesión se usa el método clear():
sessionStorage.clear();
Recuérdese que si no limpiamos los datos de una sesión, serán limpiados automáticamente cuando el usuario
cierre el navegador. Por consiguiente, el almacén de sesión puede está limitado a aplicaciones que no
necesitan preservar datos de usuario entre sesiones. Aquí es donde el API Local Storage resulta más útil.
3.5.2. Persistiendo datos entre sesiones usando el almacén local.
El almacén local permite almacenar datos sobre el navegador cliente, pero al contrario que el almacén de
sesión, los datos persisten después de que la sesión del usuario ha finalizado. Los datos son almacenados en el
sistema de ficheros del dispositivo que ejecuta el navegador. Se eliminan cuando la aplicación web los borra, o
cuando el usuario solicita que el navegador los borre. Este mecanismo es dependiente del navegador. Los
datos persistentes del almacén local están disponibles entre diferentes páginas, incluso aunque pertenezcan a
sitios web diferentes.
Se puede acceder al almacén local usando la propiedad localStoragedel objeto window. Se puede detectar si el
navegador soporta el almacén local consultado la presencia de esta propiedad, tal como sigue:
if( window.localStorage ) {
...
}
El API Local Storage es muy parecida al API Session Storage. Podemos almacenar y recuperar datos usando
las funciones setItem()ygetItem(), la notación de array, o seudo-propiedades.
localStorage.setItem("myKey","algún texto");
var textData = localStorage.getItem("myKey");
localStorage["myKey"] = "algún texto";
var textData = localStorage["myKey"];
localStorage.myKey = "algún texto";
var textData = localStorage.myKey;
Podemos determinar el número de elementos del almacén local usando la propiedad length, e iterar sobre los
elementos y recuperar datos usando la función key(), tal como se muestra en el siguiente ejemplo:
var listDiv = document.getElementById("myList");
for(var i=0; i<localStorage.length; i++)
{
listDiv.innerHTML += "<br />" + localStorage.key(i);
}
Para eliminar un elemento del almacén se usa el método removeItem().
localStorage.removeItem("myKey");
Para vaciar el almacén local se usa el método clear():
localStorage.clear();
Los objetos almacenados en el almacén local no tienen una restricción de tamaño específica. El almacén es
libre de crecer hasta un límite decidido por el usuario y configurado como una opción del navegador. Esto
hace que sea ideal para almacenar objetos serializados como texto usando la función JSON.stringify().
Usar almacenamiento local en lugar de confiar en las idas y venidas de un servidor tiene un impacto
significativo en la experiencia del usuario. Los datos encontrados sobre el cliente pueden mostrarse
instantáneamente, aumentado la velocidad de respuesta del sitio web.
3.5.3. Gestionando eventos de almacenamiento.
El API de almacenamiento al que se ajustan tanto el almacén de sesión y el local incluye un evento
denominado storage. Podemos usar este evento para notificar a una página web de los cambios de datos
dentro del almacén; se lanza si los datos son modificados.
El siguiente ejemplo muestra cómo suscribir este evento:
function myStorageCallback( e ) {
alert("Clave:" + e.key + " ha cambiado a " + e.newValue);
}
window.addEventListener("storage", myStorageCallback, true);
El objeto de evento pasado a la función manejadora del evento incluye las siguientes propiedades:

103
• key: El nombre de la clave modificada.
• oldValue: El valor original antes de cambiar.
• newValue: El nuevo valor.
• url: El documento cuyo script originó el evento.
• storageArea: Una referencia al almacén que fue cambiado (session o local).
El modelo de eventos permite a las páginas web escuchar eventos de almacenamiento y actualizarse a ellas
mismas cuando los datos cambien. Por ejemplo, en Internet Explore, si tenemos la misma página abierta en
varias pestañas, los datos de cada pestaña pueden sincronizarse para reducir inconsistencias.
Internet Explorer también define el evento storagecommit. Este evento se lanza cuando los datos del almacén
local son escritos a disco.
function myStorageCommitCallback( e ) {
alert("Datos escritos a disco");
}
window.addEventListener("storagecommit", myStorageCommitCallback, true);
3.5.4. Cómo interactúa el servidor con los almacenes locales.
Los servidores web no tienen acceso directo a los almacenes locales. Así como son pasadas las cookies con
los datos de la solicitud, los datos almacenados en sessionStorage y localStorage no son enviados
automáticamente al servidor. Desde código script de las páginas deberemos implementar algún mecanismo
para transmitir los datos de los almacenes locales al servidor.
Por ejemplo, en la página "altaProducto.html" se creará un formulario que permita añadir nombres de
productos a un array de JavaScript:
<!DOCTYPE html>
<html>
<head>
<title>Perfiles de usuario</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<b>Agrege un nombre de producto: </b><input type="text" id="producto">
<br>
<button onclick="agregarProducto()" >agregar</button>
<ul id="listaProductos"></ul>
<button onclick="confirmarProducto()" >confirmar</button>
<script type="text/javascript">
var productos = new Array(); // se crea un array vacío
var listaProductos = document.getElementById("listaProductos");
function agregarProducto() {
var nombre = document.getElementById("producto").value;
productos.push(nombre);
var li = document.createElement("li");
li.innerHTML=nombre;
listaProductos.appendChild(li);
}
function confirmarProducto() {
sessionStorage["productos"] = JSON.stringify(productos);
window.location="confirmarProductos.html";
}
</script>
</body>
</html>
Esta página irá almacenando nombres de productos a un array en código JavaScript. Cuando se pulse el
botón «Confirmar» se almacenará esta array, serializado a JSON, en el almacén de sesión, y se navegará a la
siguiente página "confirmarProducto.html":
<!DOCTYPE html>
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">

104
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<ul id="listaProductos"></ul>
<form method="post" action="confirmarproductos">
<input type="hidden" name="productos" id="lbProductos" />
<input type="submit" value="Aceptar" />
<input type="submit" value="Cancelar" onclick="cancelar()" />
</form>
<script type="text/javascript">
document.getElementById("lbProductos").value = sessionStorage["productos"];
var productos = JSON.parse(sessionStorage["productos"]);
var listaProductos = document.getElementById("listaProductos");
for (var i in productos) {
var li = document.createElement("li");
li.innerHTML = productos[i];
listaProductos.appendChild(li);
}
function cancelar() {
sessionStorage.removeItem("productos");
document.forms[0].action="altaProductos.html";
}
</script>
</body>
</html>
Esta página recupera el array almacenado en la sesión, lo asigna en un campo oculto dentro de un formulario,
y muestra su contenido en una lista no numerada. Al pulsar el botón «Aceptar» se postea el contenido del
campo oculto hacia un servlet. Al pulsar el botón «Cancelar» se elimina el array del almacén de sesión y se
retorna a la página previa.
Figura 5

3.6. Gestión de estado en el servidor usando atributos de ámbito.


Debemos tener en cuenta que en el modelo multi-hilos del contenedor web se trabaja sobre una misma
instancia de servlet. Cada solicitud de un cliente es gestionada mediante un hilo de trabajo que invoca el
método service() del servlet. En aras a conseguir la mayor seguridad posible en los hilos, debemos considerar
el comportamiento y ámbito de las variables o atributos que podemos usar en un servlet:
▪ Las variables de instancia son compartidas por todos los hilos. Si varios hilos modifican a la vez una
variable de objeto pueden crearse problemas de coherencia en los datos.
▪ No ocurre lo mismo con las variables locales del método service(). Cada hilo reserva memoria para la
ejecución de dicho método, de forma que cada hilo trabaja con una copia diferente de las variables locales.

105
▪ Las variables static son compartidas por todas las instancias de un servlet, por ello presentan la misma
problemática que las variables de instancia. Para garantizar hilos seguros, las variables de clase deberían
usarse sólo como si fuesen constantes.
▪ Los atributos de contexto (ServletContext) son accesibles por todos los servlets de una aplicación y sus
correspondientes hilos. Si creamos un atributo para guardar los usuarios accedidos, de forma que algún
servlet añade usuarios y otro los elimina, sería conveniente sincronizar el acceso a dicho atributo (aunque
esto podría provocar un embotellamiento).Deberían usarse para guardar datos compartidos que se
actualizan raramente.
▪ Los atributos de sesión (HttpSession) sólo son accesibles por los hilos que trabajan sobre la misma
sesión. Debemos tener en cuenta que un mismo usuario puede abrir varias ventanas en un navegador y
solicitar recursos simultáneamente sobre la misma sesión, pero sólo un hilo trabajará como máximo sobre
un mismo recurso a este nivel de sesión. En este caso, si aplicamos sincronización sobre el objeto session,
no provocaremos embotellamientos.
▪ Los atributos de solicitud (ServletRequest) son sólo accedidos por un hilo, ya que el contenedor de
servlets crea un nuevo objeto ServletRequest por cada requerimiento recibido. Para garantizar la seguridad
deberíamos usar estos atributos sin salir del alcance del método service().
3.6.1. Compartiendo datos (ámbitos de creación de atributos).
Podemos compartir datos a tres niveles mediante tres clases que nos permiten crear atributos en el
contenedor de servlets: HttpServletRequest, HttpSession y ServletContext. Un atributo es un objeto que se
almacena en uno de estos niveles o contextos y que es accedido mediante un nombre de tipo String.
Cada una de estas clases posee un método setAttribute(String name, Object value) para guardar datos en el
contenedor y un método Object getAttribute(String name) para recuperar los datos.
Figura 6

Importante. No confundir los atributos con los parámetros de solicitud. Los parámetros son enviados en
la URL o el cuerpo de la solicitud, mientras que los atributos se conservan en la memoria del servidor.
Se aplican las siguientes reglas de acceso y visibilidad en cada ámbito:
• HttpServletRequest comparte datos sólo durante la vida de una respuesta a un cliente.
Sólo se pueden recuperar estos atributos desde el servlet que es invocado.
• HttpSession comparte datos mientras un cliente está activo.
Sólo los servlets invocados en un mismo contexto de sesión tienen acceso a los atributos creados en
dicho contexto. Una vez finalizada una sesión se eliminan los atributos creados en su ámbito.
• ServletContext comparte datos durante la vida de una aplicación Web.
Los atributos creados en este ámbito son accesibles desde cualquier servlet de la aplicación
independientemente de la sesión.
Los métodos que permiten manipular atributos en estos tres ámbitos son los siguientes:
• Object getAttribute(String)
Recupera el valor de un atributo cuyo nombre es pasado por argumento.
• void setAttribute(String, Object)
Crea un atributo con el nombre y valor dados. Si ya existe un atributo con el mismo nombre se actualiza
su valor.
• void removeAttribute(String)
Elimina el atributo de nombre dado, si existe.
• Enumeration<String> getAttributeNames()
Retorna una enumeración con todos los nombres de atributos actuales.

106
3.6.2. Uso de los atributos de ámbito.
Veremos ahora un ejemplo práctico del uso de servlets y atributos de contexto para almacenar datos. En este
ejemplo implementaremos una sencilla aplicación de carro de compra que se compondrá de los siguientes
servlets:
• ServletPedidoForm: mostrará un formulario para que el usuario introduzca su número de teléfono y
seleccione un libro para añadir a su compra. El formulario ofrecerá dos botones, uno para agregar un
pedido de libro, y otro para confirmar la compra. Ambos botones harán una solicitud al siguiente servlet.
• ServletProcesaPedido: procesará tanto un nuevo pedido como la confirmación de compra. Si es
invocado para un nuevo pedido lo añadirá a un repositorio de sesión y redirigirá al servlet
ServletPedidoForm. Si es invocado para confirmar la compra guardará los datos de compra y redirigirá al
siguiente servlet.
• ServletCompra: recuperará los datos de la compra actual y los mostrará en una tabla. También mostrará
un enlace para retornar al ServletPedidoForm.
Figura 7

Primero crearemos dos clases de la lógica del negocio. La clase Pedido encapsulará los datos de un pedido de
libros:
package servicio;
public class Pedido {
private String libro;
private int unidades;
public Pedido() {
}
public Pedido(String libro, int unidades) {
this.libro = libro;
this.unidades = unidades;
}
public String getLibro() {
return libro;
}
public void setLibro(String libro) {
this.libro = libro;
}
public int getUnidades() {
return unidades;
}
public void setUnidades(int unidades) {
this.unidades = unidades;
}
}
Y una clase llamada Repositorio gestionará una lista de libros disponibles y la persistencia de los pedidos
realizados.
package servicio;
import java.util.*;
public class Repositorio {
private final static String[] LIBROS = {"Libro1", "Libro2", "Libro3", "Libro4"};
private List<Pedido> pedidos = new ArrayList<>();

107
public List<String> getLibrosSinPedir() {
List<String> libros = new ArrayList<String>(Arrays.asList(LIBROS));
for (Pedido pedido : pedidos) {
if (libros.contains(pedido.getLibro()))
libros.remove(pedido.getLibro());
}
return libros;
}
public List<Pedido> getPedidos() {
return pedidos;
}
public void addPedido(Pedido pedido) {
pedidos.add(pedido);
}
}
Por simplicidad esta clase persistirá los datos en colecciones de memoria. Cada instancia de Repositorio
gestionará una compra, de manera que varios usuarios de nuestro sitio web pueden estar trabajando con
instancias de esta clase sin interferir entre ellas. Para conseguir esto se instanciará un objeto Repositorio en el
ámbito de la sesión actual.
El servlet ServletPedidoForm se invocará ante la solicitud de la URL "pedidoForm.html" y devolverá un
formulario de pedido de libros:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import servicio.Repositorio;
@WebServlet(name = "ServletPedidoForm", urlPatterns = {"/pedidoForm.html"})
public class ServletPedidoForm extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Se deja disponible una instancia del repositorio en la sesión
Repositorio repositorio = (Repositorio) request.getSession(true).getAttribute("repositorio");
if (repositorio==null) {
repositorio = new Repositorio();
request.getSession(true).setAttribute("repositorio", repositorio);
}
// Se intenta recuperar un atributo de request llamado "telefono"
String telefono = (String) request.getAttribute("telefono");
if (telefono == null) telefono = "";
// Se genera la respuesta
response.setContentType("text/html; charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
out.println("<title>Pedidos de libros</title>");
out.println("</head>");
out.println("<body>");
out.println("<form method='post' action='procesarPedido'>");
out.println("<table>");
out.println("<tr>");
out.println("<td>Teléfono de contacto</td>");
out.println("<td><input type='text' name='telefono' value='" + telefono + "' /></td>");
out.println("</tr>");
out.println("<tr>");
out.println("<td>Libro</td>");
out.println("<td>");

108
out.println("<select name='libro'>");
for (String libro : repositorio.getLibrosSinPedir()) {
out.println("<option>"+libro+"</option>");
}
out.println("</select>");
out.println("</td>");
out.println("</tr>");
out.println("<tr>");
out.println("<td>Unidades</td>");
out.println("<td><input required min='1' type='number' name='unidades' value='1' /></td>");
out.println("</tr>");
out.println("<tr>");
out.println("<td><input type='submit' value='Agregar pedido' name='agregar' /></td>");
out.println("<td><input type='submit' value='Confirmar pedidos' name='confirmar' /></td>");
out.println("</tr>");
out.println("</table>");
out.println("</form>");
out.println("</body>");
out.println("</html>");
}
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
El formulario que devuelve este servlet muestra una lista desplegable con sólo aquellos libros que todavía no
han sido seleccionados.
El servlet ServletProcesaPedido se encarga de procesar tanto un pedido como la confirmación de la compra:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import servicio.Pedido;
import servicio.Repositorio;
@WebServlet(name = "ServletProcesaPedido", urlPatterns = {"/procesarPedido"})
public class ServletProcesaPedido extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Se recupera el repositorio de la sesión.
Repositorio repositorio = (Repositorio) request.getSession().getAttribute("repositorio");
String telefono = request.getParameter("telefono");
if (request.getParameter("agregar") != null) { // Hay que agregar un pedido
String libro = request.getParameter("libro");
int unidades = Integer.parseInt(request.getParameter("unidades"));
request.setAttribute("telefono", telefono);
Pedido pedido = new Pedido(libro, unidades);
// Se recupera el repositorio de la sesión y se añade el pedido
repositorio.addPedido(pedido);
// Se redirige al formulario pasando un atributo de request
request.setAttribute("telefono", telefono);
// y se vuelve al formulario
request.getRequestDispatcher("/seleccionLibro.html").forward(request, response);
} else { // Se ha confirmado
// .... algún código para registrar el pedido ......
// Se muestra una página de confirmación pasando el teléfono
request.setAttribute("telefono", telefono);
request.getRequestDispatcher("/confirmacion").forward(request, response);

109
}
}
}
Después de añadir un pedido redirige otra vez al servlet ServletPedidoForm, pero pasándole un atributo de
solicitud con el teléfono. De esta forma en el formulario se podrá mantener el teléfono introducido
previamente. Si se produce la confirmación de la compra se redirige al servlet ServletCompra, al que pasa
también el teléfono como un atributo de solicitud.
El servlet ServletCompra se encarga de confirmar la compra, mostrando los pedidos acumulados en el
repositorio. Recupera el repositorio del ámbito de la sesión y el teléfono del ámbito de la solicitud.
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import servicio.Pedido;
import servicio.Repositorio;
@WebServlet(name = "ServletCompra", urlPatterns = {"/confirmacion"})
public class ServletCompra extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Se recupera el repositorio y el teléfono del ámbito de la solicitud
Repositorio repositorio = (Repositorio) request.getSession().getAttribute("repositorio");
request.getSession().removeAttribute("repositorio"); // Se quita de la sesión.
String telefono = (String) request.getAttribute("telefono");
// Se genera la respuesta
response.setContentType("text/html; charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println("<!DOCTYPE html>");
out.println("<html>");
out.println("<head>");
out.println("<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>");
out.println("<title>Confirmación de compra</title>");
out.println("</head>");
out.println("<body>");
out.println("<table>");
out.println("<tr>");
out.println("<th>Libro</th>");
out.println("<th>Unidades</th>");
for (Pedido pedido : repositorio.getPedidos()) {
out.println("<tr>");
out.println("<td>" + pedido.getLibro() + "</td>");
out.println("<td>" + pedido.getUnidades() + "</td>");
out.println("</tr>");
}
out.println("</table>");
out.println("<h3>Su compra se ha procesado. Recibirá un aviso en el número " + telefono + "</h3>");
out.println("<a href='seleccionLibro.html'>Realizar otra compra</a>");
out.println("</form>");
out.println("</body>");
out.println("</html>");
}
}
}
Las páginas generadas por estos servlets y su navegación se ilustran a continuación:

110
Figura 8

4. Filtros
En general, un filtro es un objeto que intercepta los mensajes entre un origen de datos y un destino de datos,
y que decide cuáles deben pasar de un lado al otro.
Para las aplicaciones web, los filtros son componentes que residen en el servidor web y que filtran los
requerimientos y respuestas que son pasados entre un cliente y un recurso.
Figura 9

Cuando el contenedor de servlets recibe un requerimiento para un recurso, primero mira si hay un filtro
asociado con el recurso. Si es así, remite el requerimiento al filtro. El filtro, después de procesar el
requerimiento, hace una de estas tres cosas:
• Genera la respuesta por sí mismo y se la envía al cliente.
• Pasa el requerimiento (modificado o no) al siguiente filtro asociado al recurso (si lo hay) o al recurso si es
el último filtro.
• Reenvía el requerimiento a un recurso diferente.
Una vez generada la respuesta, ésta pasa otra vez por el mismo conjunto de filtros en el orden inverso. Cada
filtro puede modificar la respuesta.
111
Se pueden usar filtros para:
• Autentificar usuarios.
• Conversión de imágenes.
• Compresión y/o encriptación de datos.
• Reconocer elementos.
• Filtrar los eventos lanzados por los recursos.
• Encadenar tipos de documento MIME.
4.1. Implementación de filtros.
Todos los filtros implementan la interfazjavax.servlet.Filter, que define tres métodos: init(), doFilter(), y
destroy().
Como ejemplo, en el proyecto web de NetBeans crearemos un filtro que capturará cualquier solicitud del sitio
web y simplemente mostrará un mensaje de saludo.
4.1.1. Cómo añadir un filtro a un proyecto web.
Comenzaremos añadiendo un filtro al proyecto "WebAplicación" usando la plantilla que incorpora NetBeans.
Al ser clases de Java, igual que los servlets, los filtros deben crearse en el nodo «Source packages». Sobre este
nodo hay que abrir el menú contextual y pulsar sobre «Nuevo|Filter». Si no aparece la opción «Filter» hay que
seleccionar la opción «Otros» y en la categoría «Web» seleccionar la plantilla «Filter».
Figura 10

En el cuadro de diálogo «Nuevo Filtro» pondremos como nombre de la clase "FiltroSaludo", y la ubicaremos
en un nuevo paquete con el nombre "filtro".

112
Figura 11

Tras pulsar el botón «Siguiente», el asistente nos ofrece las opciones para configurar y registrar el filtro en el
contenedor web.
4.1.2. Registro de un filtro en el archivo descriptor.
Los filtros se registran en el fichero descriptor de la aplicación de una forma parecida a los servlets. En el
cuadro de diálogo de «Nuevo Filtro» simplemente tenemos que marcar la opción «Add information to
deployment descriptor (web.xml)». Si existe el fichero /WEB-INF/web.xml se registrará la información que
establezcamos en este asistente, pero si no existe el filtro no será registrado en el contenedor web.
Figura 12

113
Para configurar un filtro debemos proporcionar un nombre de filtro (como este nombre se utiliza para
información interna de registro podemos utilizar el propio nombre de la clase), y un mapa de filtro. Este
mapa de filtro es la ruta de la URI que asociará las solicitudes con el filtro. Pulsando el botón «Nuevo» se
accede al asistente «Filter Mapping». Para este ejemplo se ha asignado /*, lo que implica que cualquier URI se
asociará al filtro.
Figura 13

Podemos ver que en este asistente se puede asociar también un filtro con un servlet concreto, y que permite
seleccionar opciones de redirección REQUEST, FORWARD, INCLUDE y ERROR. Hay que tener en cuenta que un
filtro captura inicialmente solo la solicitudes externas (REQUEST), procedentes normalmente desde un
navegador. Pero hemos visto que también se puede realizar solicitud de redirección internas. Si queremos que
un filtro responda a redirecciones internas debemos marcar las opciones FORWARD, INCLUDE y/o ERROR.
Tras aceptar pulsar el botón «Siguiente», el siguiente cuadro de diálogo permite crear parámetros de
inicialización para el filtro. Esto parámetros son similares a los parámetros de inicialización de los servlets.

114
Figura 14

Tras pulsar el botón «Finalizar» se creará el filtro y quedará registrado en el fichero descriptor. El fichero
web.xml quedará como sigue:
<?xml version="1.0" encoding="UTF-8"?>
<web-app …………………. >
<filter>
<filter-name>FiltroSaludo</filter-name>
<filter-class>filtro.FiltroSaludo</filter-class>
</filter>
<filter-mapping>
<filter-name>FiltroSaludo</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Como se ve, la clase del servlet se registra con una etiqueta <filter>, donde se especifica el nombre del filtro y
su nombre de clase cualificado (incluyendo los paquetes donde está contenida).
La etiqueta <filter-mapping>se encarga de mapear un filtro declarado en <filter> con su mapa (la ruta a la que
quedará asociado). Pero un mismo filtro puede tener asociados varios elementos <filter-mapping>, esto quiere
decir que podemos asociar un filtro con varios patrones URL, de forma análoga a lo que se puede hacer con
un servlet.
Las sub-etiquetas que admite <filter-mapping> son las siguientes:
<filter-name>: Identifica el filtro a asociar.
<url-pattern>: Se utiliza para aplicar el filtro a las peticiones que concuerden con el patrón URL.
<servlet-name>: Se utiliza para aplicar el filtro a todas las peticiones que se realicen a un nombre de servlet
asociado.
<dispatcher>: Se utiliza para determinar el modo de invocación del filtro. Puede tomar los valores:
REQUEST, FORWARD, INCLUDE, o ERROR.
Sólo puede existir un subelemento <url-pattern> o <servlet-name> dentro un mismo <filter-mapping>.
4.1.3. Registro de un filtro mediante anotaciones.
Desde el JDK 7 también se pueden registrar los filtros mediante anotaciones en la propia clase. Para
conseguir esto, en el cuadro de diálogo de «Nuevo Filtro» simplemente tenemos que dejar desmarcada la
opción «Add information to deployment descriptor (web.xml)».

115
Figura 15

Para ese ejemplo, en el fichero "FiltroSaludo.java"se añadirán las siguientes anotaciones sobre la clase:
package filtro;
@WebFilter(filterName = "FiltroSaludo", urlPatterns = {"/*"})
public class FiltroSaludo implements Filter {
……………….
}
La anotación javax.servlet.annotation.WebFilter posee los siguientes atributos:
Atributo Descripción
filterName Un string con el nombre del filtro. Este nombre es usado internamente por el
contenedor web y se corresponde con la etiqueta <filter-name>.
urlPatterns Un array de strings con los mapas URL asociados con el filtro.
initParams Un array de objetos @WebInitParam con parámetros de inicialización del filtro.
asyncSupported Un valor booleano que indica si el filtro soporta operaciones asíncronas.
smallIcon Un string con la ruta con un icono pequeño para representar el filtro.
largeIcon Un string con la ruta con un icono grande para representar el filtro.
description Un string con una descripción larga del filtro.
displayName Un string con una descripción corta del filtro.
4.1.4. Funcionalidad del filtro.
La plantilla generada por NetBeans para crear filtros incluye mucho código de depuración. Para comprender
cómo funcionan los filtros simplificaremos este código:
package filtro;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;

116
@WebFilter(filterName = "FiltroSaludo", urlPatterns = {"/*"})
public class FiltroSaludo implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig) {
}
}
Como se puede ver, el código para crear un filtro es similar al utilizado para crear un servlet, con las siguientes
diferencias:
• El método init()es invocado por el contenedor web para indicar al filtro que está siendo usado dentro de
un servicio. El contenedor web llamará a este método sólo una vez después de instanciar el filtro, y debe
completarse para que el filtro pueda realizar su trabajo. Al igual que con los servlets, el contenedor web no
pondrá el filtro dentro del servicio si este método lanza una ServletException o no finaliza su ejecución en el
tiempo establecido por el contenedor web.
Importante. Si el método inti() lanza una ServletException impedirá que pueda ejecutarse la aplicación
web, puesto que los filtros deben desplegarse al inicio de la aplicación.
Este método incluye un parámetro del tipo FilterConfig, el cual nos da acceso a los parámetros de
inicialización del filtro. La interfaz javax.servlet.FilterConfig proporciona los siguientes métodos:
- String getFilterName(), devuelve el nombre del filtro especificado en el archivo web.xml.
- String getInitParameter(String), devuelve el valor del parámetro de inicialización especificado.
- Enumeration getInitParameterNames(), devuelve una instancia Enumeration con los nombres de los
parámetros de inicialización.
- ServletContext getServletContext(), devuelve la instancia de ServletContext asociada a la aplicación web.
• El método destroy()es invocado por el contenedor web para indicar que el filtro ha sido quitado del
servicio. Este método es sólo llamado una sola vez una vez que el filtro ha sido eliminado. Una vez
invocado este método, las nuevas invocaciones al filtro provocarán que se cree una nueva instancia.
• El método doFilter() es el encargado de aplicar la funcionalidad del filtro. A diferencia del método service()
de los servlets recibe un parámetro request de tipo ServletRequest, que es la clase base de HttpServletRequest,
puesto que los filtros no se restringen sólo a filtrar servlets http. Ocurre algo similar con el parámetro
response.
• El método doFilter()también incluye un tercer parámetro del tipo FilterChain. La interfaz
javax.servlet.FilterChain tiene un único método:
- void doFilter(ServletRequest, ServletResponse), pasa la petición al próximo componente en la cadena,
otro filtro o el recurso original.
De momento el filtro FiltroSaludo simplemente redirige la solicitud sin realizar ninguna modificación. La
instrucción:
chain.doFilter(request, response);
Se encarga de redirigir la solicitud para que sea procesada por otro filtro o se complete.
Si queremos que el filtro pare la solicitud debe retornar el contenido de la respuesta de forma dinámica, de
forma similar a como lo haría un servlet:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
response.setContentType("text/html; charset=UTF-8");
try (PrintWriter out=response.getWriter()) {
out.println("<html>");
out.println("<body>");
out.println("<h1>Saludos, un filtro ha interceptado la solicitud</h1>");
out.println("</body>");
out.println("</html>");
}

117
}
Ahora, cualquier solicitud (de una recurso existente o no existente) que se realice al sitio web será capturada
por el filtro, el cual devolverá un página con un mensaje.
4.2.¿Cómo configurar cadenas de filtros?
Las cadenas de filtros pueden ser configuradas utilizando múltiples elementos <filter-mapping>. Cuando el
contenedor de servlets recibe una petición, éste busca todos los mapeos de filtros que concuerdan la URI con
el patrón URL. Éste se considera el primer conjunto de filtros a aplicar al recurso. Luego se buscan todos los
mapeos de filtros con el nombre del servlet correspondiente. Éste se convierte en el segundo conjunto de
filtros en la cadena de filtros. En ambos conjuntos, el orden de los filtros se corresponde al orden de
declaración de los mismos.
El contenedor primero invoca a los filtros del primer conjunto antes que los del segundo.
Por tanto, para asignar un conjunto de filtros a un recurso debemos establecer las URIs apropiadas tanto en
el recurso como en los filtros. Supongamos un servlet con dos URIs:
Servlet Uris
servlet1 /sun/sun/Servlet1
*.sun
Y tres filtros, registrados en el orden indicado, con las siguientes URIs:
Filtros Uris
FiltroA servlet1
FiltroB /sun/sun/*
FiltroC /sun/*
En la siguiente tabla se muestras ejemplos de varios requerimientos del servlet y los filtros que se aplican:
Uri del requerimiento Orden de aplicación de los filtros Casa con filtro url Se invoca al servlet
sun/sun/Servlet1 FiltroB, FiltroC y FiltroA /sun/sun/* y sun/* SI
sun/Servlet1 FiltroC /sun/* NO
aaa.sun FiltroA SI
sun/sun/sun.sun FiltroB, FiltroC y FiltroA /sun/sun/* y sun/* SI

4.3. Ejecución de filtros.


La especificación de Servlets 2.4 permite invocar servlets de las siguientes maneras:
• Como un resultado del método RequestDispatcher.forward().
• Como un resultado del método RequestDispatcher.include().
• En páginas de error.
Un filtro por defecto solo es invocado con peticiones entrantes (desde el cliente). Pero el registro del filtro
permite especificar los modos de invocación de un filtro: REQUEST, FORWARD, INCLUDE, o ERROR.
• Un valor REQUEST activa el filtro para solicitudes desde el cliente. REQUEST es la opción por defecto.
• Un valor INCLUDE activa el filtro para solicitudes despachadas desde una llamada a include().
• Un valor FORWARD activa el filtro para solicitudes despachadas desde una llamada a forward().
• Un valor ERROR activa el filtro para recursos llamados desde manejadores de error.
Se pueden usar de 0 a 4 subelementos <dispatcher> dentro de un elemento <filter-mapping>. Por defecto (si
no se especifica ningún subelemento <dispatcher>) los filtros sólo se aplican para peticiones entrantes a la
aplicación. Es decir, solo el valor REQUEST es aplicado por defecto.
Si el contenedor no puede encontrar el recurso solicitado por una petición, ningún filtro correspondiente a la
misma se invoca. Los filtros solo se invocan si existe el recurso original.
4.4.¿Cómo procesar los requerimientos y modificar las respuestas?
Una de las características más potentes de los filtros es poder modificar las condiciones de la solicitud y los
resultados de la respuesta. Para hacer esto el filtro puede utilizar los parámetros de tipo ServletRequest y
ServletResponse que recibe en el método doFilter().

118
4.4.1. Cómo modificar la solicitud.
Supongamos que un filtro debe capturar las solicitudes a un servlet que recibe los datos de un cliente desde
un formulario en la página "altacliente.html". El filtro analizará los parámetros de solicitud, los validará, y en la
medida de lo posible intentará normalizarlos al formato apropiado.
El servlet puede ser el siguiente:
package servlet;
import java.io.*;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletAltaCliente", urlPatterns = {"/altacliente"})
public class ServletAltaCliente extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int codigo = Integer.parseInt(request.getParameter("codigo"));
String nombre = request.getParameter("nombre");
// ....Código para almacenar los datos del cliente.....
response.sendRedirect(request.getContextPath() + "/altaCliente.html");
}
}
Este servlet recupera el código y nombre de un cliente posteados desde un formulario, y los almacena.
Después vuelve a redirigir a la página del formulario para realizar más altas.
El filtro FiltroAltaCliente se encargará de validar que se reciban datos en el código y el nombre y si no es así
devolverá una excepción. También normalizará el nombre eliminando espacios en blanco innecesarios.
import java.io.IOException;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
@WebFilter(filterName = "FiltroAltaCliente", servletNames = {"ServletAltaCliente"})
public class FiltroAltaCliente implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String codigo = request.getParameter("codigo");
String nombre = request.getParameter("nombre");
if (codigo==null || codigo.equals("") || nombre==null || nombre.equals(""))
throw new ServletException("Faltan datos");
// ... se debe normalizar el nombre ....
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig filterConfig) {
}
}
En el método doFilter() primero se recuperan los parámetros y se comprueba que existan y tengan datos. Si no
es así se lanza una excepción. La cuestión es que antes de seguir encadenando la solicitud con
chain.doFilter(request, response), debemos modificar el valor del parámetro "nombre". Pero esto no es algo que
se puede realizar directamente, puesto que los objetos HttpServletRequest no ofrecen ningún método para
modificar el contenido de los parámetros de solicitud, sólo ofrece métodos para recuperar los parámetros.
La estrategia a seguir para conseguir nuestro objetivo es cambiar el objeto request original por otro donde
podamos reescribir los métodos que devuelvan los parámetros de solicitud. Para facilitar la creación de
duplicados de los objetos request y response, Java provee cuatro clases envolventes:
javax.servlet.ServletRequestWrapper
javax.servlet.ServletResponseWrapper
javax.servlet.HttpServletRequestWrapper
javax.servlet.HttpServletResponseWrapper

119
Todas estas clases tienen un constructor que recibeun objetorequest o response y delegan todas las llamadas a
métodos a estos objetos. Extendiendo estas clases, y rescribiendo algunos de sus métodos, podemos
personalizar el proceso de los requerimientos y modificar las respuestas.
Por ejemplo, la siguiente clase se encarga de normalizar el parámetro "nombre" reescribiendo todos los
métodos que permiten recuperar los parámetros de solicitud.
public class RequestModificado extends ServletRequestWrapper {
public RequestModificado(ServletRequest request) {
super(request);
}
@Override
public String getParameter(String name) {
if (name.equals("nombre")) {
String nombre = super.getParameter("nombre");
return nombre==null? null : nombre.trim();
}
return super.getParameter(name);
}
@Override
public String[] getParameterValues(String name) {
if (name.equals("nombre")) {
String[] nombre = super.getParameterValues("nombre");
return nombre==null? null : new String[] {nombre[0].trim()};
}
return super.getParameterValues(name);
}
@Override
public Map<String, String[]>getParameterMap() {
Map<String, String[]> mapa=super.getParameterMap();
String[] nombre = mapa.get("nombre");
if (nombre!=null) {
mapa.put("nombre", new String[] {nombre[0].trim()});
}
return mapa;
}
}
Ahora podemos agregar el código adecuado en el método doFilter() del filtro para sustituir al parámetro
request:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String codigo = request.getParameter("codigo");
String nombre = request.getParameter("nombre");
if (codigo == null || codigo.equals("") || nombre == null || nombre.equals("")) {
throw new ServletException("Faltan datos");
}
chain.doFilter(new RequestModificado(request), response);
}
4.4.2. Cómo modificar la respuesta.
El proceso para modificar la respuesta es análogo al de la solicitud. Podemos usar las clases envolventes
HttpServletRequestWrapper y HttpServletResponseWrapper para sustituir al parámetro response reescribiéndole
los métodos adecuados. Aunque no es tan habitual como en el caso de la solicitud, puede ser interesante en
determinados casos derivar el canal de salida de la respuesta a un búfer en memoria y desde el filtro recuperar
el contenido de dicho búfer para componer una respuesta personalizada.
El siguiente ejemplo muestra cómo crear una clase envolvente que permite derivar el canal de salida hacia un
ByteArrayOutputStream:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ResponseWrapper extends HttpServletResponseWrapper {
public ResponseWrapper(ServletResponse response) {

120
super(response);
}
private ByteArrayOutputStream bufer = new ByteArrayOutputStream();
private PrintWriter pwBufer = new PrintWriter(bufer);
private ServletOutputStream outBufer = new ServletOutputStream() {
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener writeListener) {
}
@Override
public void write(int b) throws IOException {
bufer.write(b);
}
};
@Override
public PrintWriter getWriter() {
// retorna nuestro PrintWriter modificado que escribe a un array de bytes
return pwBufer;
}
@Override
public ServletOutputStream getOutputStream() {
// retorna el stream que escribe a un array de bytes lo que enviemos como respuesta
return outBufer;
}
// retorna el contenido de la respuesta como una array de bytes
public byte[] toByteArray() {
return bufer.toByteArray();
}
}
Pero lo habitual en filtros que modifican la respuesta es realizar redirecciones o recuperar recursos solicitados
desde código y procesar una respuesta directa. Por ejemplo, supongamos el caso de una aplicación web con
una carpeta "imagenes" que contiene archivos de imagen. La aplicación web permite solicitar directamente las
imágenes al navegador, pero queremos crear un filtro que devuelva la imagen solicitada incrustada en una
página web:
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
@WebFilter(filterName = "FiltroImagen", urlPatterns = {"/imagenes/*"})
public class FiltroImagen implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpres = (HttpServletRequest) request;
response.setContentType("text/html; charset=UTF-8");
String rutaImagen = httpres.getContextPath() + httpres.getServletPath();
try (PrintWriter out = response.getWriter()) {
out.println("<html>");
out.println("<body style='background-color: lightblue'>");
out.println("<img src='" + rutaImagen + "'></img>");
out.println("<html>");
out.println("</body>");
out.println("</html>");
}
}
@Override
public void destroy() {

121
}
@Override
public void init(FilterConfig filterConfig) {
}
}
Este filtro captura la solicitud de la imagen y devuelve dinámicamente una página html con un elemento <img
/> que incrusta la imagen en la página. Sin embargo, si probamos este filtro solicitando una de las imágenes
de la carpeta veremos que nos devuelve la página web pero no renderiza bien la imagen.
El problema se debe a que cuando el filtro devuelve una página con una elemento <img /> incrustado, el
navegador hará una solicitud sobre el fichero referenciado en el atributo src. Como se trata de una imagen de
la carpeta "imágenes", esta solicitud volverá a ser procesada por el filtro, lo cual provocará llamadas recursivas.
Para evitar este efecto podemos diferenciar las invocaciones añadiendo un parámetro de consulta a la ruta del
atributo src:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (request.getParameter("filtro") == null) {
HttpServletRequest httpres = (HttpServletRequest) request;
response.setContentType("text/html; charset=UTF-8");
String rutaImagen = httpres.getContextPath() + httpres.getServletPath();
try (PrintWriter out = response.getWriter()) {
out.println("<html>");
out.println("<body style='background-color: lightblue'>");
out.println("<img src='" + rutaImagen + "?filtro'></img>");
out.println("<html>");
out.println("</body>");
out.println("</html>");
}
} else {
chain.doFilter(request, response);
}
}
El filtro evalúa ahora la existencia de un parámetro de la cadena de consulta. La invocación directa de la
imagen no tiene ese parámetro y se devuelve la página html. La solicitud del elemento <img /> incluye el
parámetro y se devuelve directamente la imagen.

PRÁCTICA
01. Crea un servlet denominado servlet.ConVidaTasada. Este servlet, cada vez que sea creado o destruido por
el contenedor Web, deberá escribir en el archivo de log del contenedor web el momento (fecha y hora) en
que fue creado y el momento en que fue destruido.

02. Crea un sitio web usando páginas html y servlets.


Este sitio web debe presentar primero una página HTML generada dinámicamente desde un servlet (llamado
ServletIndex) con un formulario para que el usuario escriba su nombre y un enlace a la págian
"preferencias.html". La página intentará recuperar de una cookie el nombre de último usuario guardado y lo
mostrará en el formulario.
El formulario debe postear el nuevo nombre al sevlet "ServletAutentifica", el cual lo guardará en la cookie.
Este servlet debe redirigir a la página "preferencias.html", donde el usuario podrá editar sus preferencias.
Estas preferencias incluyen el color de fondo de la página y un idioma preferido. Estas preferencias deben ser
almacenadas en el almacén local del navegador y mostradas en la página si se guardaron previamente.

122
123
UNIDAD 14. LA TECNOLOGÍA JSP
1. Fundamentos de JSP
Aunque los servlets son la tecnología base para construir aplicaciones web con Java, presentan varios
inconvenientes:
• Los servlets no se invocan directamente, responden a solicitudes que casan con sus patrones URL.
• Los servlets integran en un mismo método la invocación de código de la lógica del negocio del código que
genera la presentación de páginas. Esto hace difícil aplicar patrones de diseño que separan ambas
funcionalidades.
• Cuando un servlet debe responder con la generación de páginas dinámicas complejas se hace difícil
mantener y modificar su contenido.
Como solución a estos problemas surge la tecnología JSP (Java Server Pages). Una página JSP es un fichero
de texto que combina el lenguaje de marcas HTML/XML y el lenguaje de programación Java para generar
una respuesta dinámica ante la petición de un cliente. Las páginas JSP son usadas por el contenedor de
servlets para generar dinámicamente el código fuente de un servlet.
Básicamente, un archivo JSP es un archivo de texto con la extensión .jsp que contiene: texto, etiquetas
HTML, y construcciones JSP dinámicas (código jsp entre delimitadores <% y %>)
1.1. Sintaxis de las páginas JSP.
Si en NetBeans agregamos una página JSP a un proyecto web obtendremos el siguiente contenido:
Figura 1

Si comparamos este contenido con el de la página index.html podemos comprobar que en la práctica el
fichero JSP tiene el contenido de una página HTML, excepto por la instrucción:
<%@page contentType="text/html" pageEncoding="UTF-8"%>
Esta instrucción no pertenece a la sintaxis HTML, sino que es una sintaxis propia de JSP. En este caso
corresponde a una directica de compilación que determina el tipo de contenido que genera este fichero JSP.
La sintaxis JSP se embebe por el medio de la sintaxis HTML definiendo diversos bloques de código con
funcionalidades diversas. Los bloques JSP son analizados y evaluado por un proceso del contenedor web
denominado motor JSP. El motor JSP genera a partir del código HTML y el código JSP instrucciones para
crear el código fuente de un servlet. La URI del fichero JSP queda asignada como patrón URL de este servlet
dinámico.
A continuación se indican las etiquetas JSP que podemos embeber con el código HTM y su significado:
Etiqueta JSP Significado Sintaxis
Directiva Especifican instrucciones que son ejecutadas por el motor <%@ Directivas %>
JSP.
Declaración Declara y define métodos y variables a nivel de instancia. <%! Declaraciones %>

124
Scriptlet Incluye variables e instrucciones a nivel local. <% código Java %>
Expresión Escribe algo en el código HTML evaluando una expresión <%= una expresión
de Java. %>
Acción Provee instrucciones para el motor JSP. <jsp:nombreAcción />
Comentario Se usa para documentación y comentarios. <%-- algún texto --%>
1.1.1. Directivas JSP.
Una directivaafecta a la estructura general de la clase servlet. Normalmente tienen la siguiente forma:
<%@ directiva atributo="valor" %>
Sin embargo, también podemos combinar múltiples selecciones de atributos para una sola directiva, de esta
forma:
<%@directiva atributo1="value1" atributo2="value2" ... atributoN="valueN" %>
Existen tres directivas JSP, y proporcionan al motor JSP información general acerca de las páginas. Estas
directivas son:
•<%@page %>, informa al motor JSP acerca de varias propiedades de la página. Se utiliza para importar
clases, personalizar la superclase del servlet, etc. A continuación se explican algunos de sus atributos:
<%@ page import="java.util, java.util.*" %>, para importar clases.
<%@ page contentType="text/plain" %>, para especificar el tipo MIME de la salida.
<%@ page isThreadSafe="true|false" %>, para indicar si el procesamiento del servlet es normal o que
debe implementar SingleThreadModel.
<%@ page session="true|false" %>, para indicar si deben usarse o no sesiones.
<%@ page buffer= "sizekb|none" %>, especifica el tamaño del buffer para el objeto out. El valor por
defecto es específico del servidor, debería ser de al menos 8kb.
<%@ page autoflush= "true|false" %>, un valor de true (por defecto) indica que el buffer debería
descargase cuando esté lleno. Un valor de false, raramente utilizado, indica que se debe lanzar una
excepción cuando el buffer se sobrecargue.
<%@ page extends= "package.class" %>, indica la superclase del servlet que se va a generar. Debemos
usarla con extrema precaución, ya que el servidor podría utilizar una superclase personalizada.
<%@ page info= "message" %>, define un string que puede ser recuperado mediante el método
getServletInfo().
<%@ page errorPage= "url" %>, especifica una página JSP que se debería mostrar si se produce
cualquier excepción o error no capturado en la página actual.
<%@ page isErrorPage="true|false" %>, indica si la página actual actúa o no como página de error de
otra página JSP. El valor por defecto es false.
<%@ page language="java" %>, especifica el lenguaje a utilizar.
•<%@include %>, permite insertar el contenido de un fichero en el momento que el fichero JSP es
traducido al servlet. Su sintaxis completa es:
<%@include file="url relativa" %>
La dirección URL es relativa a la página JSP de referencia. Los contenidos del fichero incluido son
analizados como texto normal JSP, y así pueden incluir código HTML, elementos de script, directivas y
acciones.
•<%@taglib %>,permite referencias librerías de etiquetas personalizadas.
1.1.2. Declaraciones.
Los bloques declarativos declaran variables y métodos que se convierten en miembros de la clase del servlet
dinámico generado. Por ejemplo,
<%! int count = 0; %>
crea una variable de instancia que es inicializada a cero sólo la primera vez que el motor JSP carga la página.
1.1.3. Scriptlets.
Son fragmentos de código Java que se copian tal cual en el método de invocación del servlet dinámico
generado. Por ejemplo,
<% int count = 0; %>
declara una variable local de método, que es inicializada a cero cada vez que la página es invocada.
1.1.4. Expresiones.
Se utiliza este bloque para renderizar en la respuesta el resultado de una expresión de Java. Por ejemplo,
<%= "valor = " + 5 %>
equivale a la instrucciónout.print("valor = 5"); que se encarga de escribir en la página resultante el texto:

125
valor = 5.
La expresión siempre será evaluada, según su tipo, antes de generar la salida. Y es importante dejar un espacio
entre el signo igual y la expresión posterior.
Nota. La expresión dentro de <%= %> debe evaluarse a un valor de tipo primitivo o a un objeto. Si se
evalúa a un objeto se escribirá en la salida lo que retorne su método toString().Nótese que la expresión
NO debe finalizarse con punto y coma.
1.1.5. Acciones o etiquetas JSP.
Son comandos que obligan al motor JSP a realizar ciertas tareas durante la ejecución de la página. La siguiente
tabla resume la sintaxis de las acciones disponibles:
Acción Sintaxis Interpretación
include <jsp:includepage="url" /> Incluye un fichero en el momento en que la página es
solicitada.
forward <jsp:forwardpage="url"/> Reenvía la petición a otra página.
useBean <jsp:useBean> Encuentra o crea un objeto JavaBean.
</jsp:useBean>
setProperty <jsp:setProperty /> Asigna propiedades a un JavaBean.
getProperty <jsp:getProperty/> Recupera el valor en una propiedad de un JavaBean.
plugin <jsp:plugin> Genera etiquetas OBJECT o EMBED, apropiadas al tipo de
</jsp:plugin> navegador, pidiendo que se ejecute un applet usando el
Java Plugin.
El uso de estas acciones se irá explicando en secciones posteriores.
1.1.6. Comentarios.
Los comentarios JSP se encapsulan entre etiquetas <%-- y --%>. Para evitar confusiones no deberían
mezclarse los comentarios JSP con los comentarios HTML y de código Java.
<%@ page contentType="text/html" pageEncoding="UTF-8" %>
<html>
<body>
<!-- comentario HTML (es enviado a la salida) -->
<%-- comentario JSP (no es enviado a la salida) --%>
</body>
</html>
1.1.7. Importación de paquetes y clases para un JSP.
Se utiliza la directiva @page para generar instrucciones import en tiempo de traslación del JSP al servlet
dinámico. Por ejemplo, si en un scriptlet queremos instanciar un objeto java.util.Date podemos importar la
clase de dos formas:
<%@ page import="java.util.Date" %>
<html>
<body>
<%
java.util.Date fecha1 = new java.util.Date();
Date fecha2 = new Date();
%>
</body>
</html>
En la primera instrucción de asignación de un Date se utiliza la importación explícita escribiendo la ruta de
paquetes a la que pertenece la clase. En la siguiente instrucción se utiliza directamente la clase Date, y en este
caso se produce una importación implícita determinada por el atributo import de la directiva <%@page %>.
Para importar un paquete entero se utiliza:
<%@ page import="java.util.*" %>
Si queremos realizar varias importaciones se pueden utilizar varias directivas @page o una única directiva
@page con uno o varios atributos import:
<%@ page import="java.text.*" %>
<%@ page import="java.sql.* , java.util.Date" %>
<%@ page import="java.uitl.Math" import="java.util.ArrayList" %>

126
Nota. Varias importaciones en una misma directiva @page se separan con comas dentro de cada atributo
import.
1.2. Páginas JSP como documentos XML.
Para dar más uniformidad al código de las páginas JSP, donde se mezclan etiquetas HTML con bloques
scriptlet, la especificación JSP define una sintaxis alternativa basada en el estándar XML.
Un ejemplo de sintaxis XML para las páginas JSP es el siguiente:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="1.2">
<html>
<body>
<jsp:directive.page language="java" />
<jsp:declaration> int count = 0; </jsp:declaration>
<jsp:scriptlet> count++; </jsp:scriptlet>
<jsp:text> ¡Hola! Eres el visitante número </jsp:text>
<jsp:expression> count </jsp:expression>
</body>
</html>
</jsp:root>
A continuación se resume esta sintaxis XML para los diversos elementos JSP:
• Expresión JSP.
<jsp:expression> expresión de Java</jsp:expression>
Evalúa una expresión de código Java y la renderiza en la respuesta.
• Scriptlet.
<jsp:scriptlet> instrucciones de Java</jsp:scriptlet>
Las instrucciones de este bloque de insertan dentro del código del método _jspService() del servlet.
• Declaración JSP.
<jsp:declaration>declaración de variables y métodos de Java</jsp:declaration>
El código de este bloque se inserta cómo código en el cuerpo de la clase del servlet. .
• Directiva page.
<jsp:directive.page atributo="valor"/>
Define una directiva para la generación del código fuente del servlet dinámico. Los atributos que admite
son:
import="paquetes.Clase" Genera instrucciones import.
contentType="MIME-Type" Establece el tipo de contenido de la página.
isThreadSafe="true|false" Establece el modelo de hilos de la página.
session="true|false" Establece si se gestionarán sesiones.
buffer="sizekb|none" Establece el tamaño del búfer para el objeto out.
autoflush="true|false" Establece si el búfer de respuesta debe descargarse automáticamente.
extends="paquetes.Clases" Establece la superclase del servlet dinámico.
info="message" Establece información sobre la página.
errorPage="url" Establece la página de error de esta página.
isErrorPage="true|false" Establece que es una página de error.
language="java" Establece el lenguaje del código.
• Directiva include.
<jsp:directive.include file="url"/>
Incluye el código de un fichero del sistema local cuando la página se traslada al servlet dinámico. La url se
interpreta siempre como una ruta relativa.
Los comentarios y acciones JSP tienen la misma sintaxis en el formato XML.
1.3. Ciclo de vida de las páginas JSP.
La primera vez que un cliente solicita la página JSP, el motor JSP la analiza y genera el fichero fuente de una
clase de servlet. Este fichero fuente es compilado para crear la clase del servlet dinámico, de la cual se creará
una instancia que atenderá la solicitud. Esta instancia puede retornar como respuesta una página HTML.
En las siguientes peticiones, el motor JSP verifica el tiempo de expiración de la página y la correspondiente
instancia de servlet para determinar si la página es nueva y si tiene que ser trasladada otra vez. Si el servlet
dinámico es válido se invoca directamente para servir la respuesta.

127
La clase servlet generada desde el JSP implementa la interfaz javax.servlet.jsp.HttpJspPage, que extiende la
interfaz javax.servlet.jsp.JspPage. La interfaz JspPage declara los métodos jspInit() y jspDestroy(), mientras que
HttpJspPage declara el método _jspService(). Estos métodos son conocidos como los métodos del ciclo de
vida de las páginas JSP , y son equivalentes a los métodos init(), service() y destroy() de los servlets normales.
Figura 2

En total se diferencian siete fases en el ciclo de vida de un JSP:


1) Traslación de la página: la página JSP es leída, reconocida y validada, y se crea un archivo fuente de Java
conteniendo la correspondiente clase servlet.
2) Compilación de la página: el archivo Java es compilado.
3) Carga de la clase: la clase compilada es cargada en memoria.
4) Creación de la instancia: se crea una instancia HttpJspPage de la clase del servlet.
5) Ejecución de jspInit(): este método es llamado una sola vez para realizar las inicializaciones necesarias (las
incluidas en las declaraciones JSP).
6) Ejecución de _jspService(): este método es llamado para cada requerimiento.
7) Ejecución de jspDestroy(): este método es llamado cuando el contenedor de servlets decide dejar al
servlet fuera de servicio.
Podemos forzar la pre-compilación de una página JSP desde el navegador sin ejecutarla con el parámetro
jsp_precompile:
http://localhost:8080/pagina.jsp?jsp_precompile=true
Hay que tener en cuenta que el prefijo jsp está reservado y no debe usarse para nombrar parámetros definidos
por el usuario.
1.4. Proceso de traslación de JSP a Servlet.
Como se ha explicado, la primera vez que se invoca un JSP debe generar y compilar el código fuente de una
clase de tipo HttpJspBase. Podemos ver el contenido de esta clase usando los asistentes de NetBeans.
Por ejemplo, si añadimos una página index.jsp a nuestro proyecto web y la ejecutamos, se creará el fichero
index_jsp.java. Podemos ver el contenido de esta clase usando la opción «Ver servlet» del menú contextual
haciendo clic sobre el nodo «index.jsp».

128
Figura 3

Las reglas que aplica el motor JSP para crear el código fuente de este servlet son las siguientes:
•Las directivas JSP son usadas por el motor JSP para generar algunas instrucciones de compilación. Por
ejemplo, el atributo import de la directiva page provoca instrucciones import en el fichero fuente.
• Todas las instrucciones de los bloques declarativos JSP<!% %> son trasladas tal cual como instrucciones
a nivel de instancia de la clase generada.
•Las instrucciones de los bloques scriptlets <% %>se trasladan tal dentro del método _jspService() en el
mismo orden.
• Todas las expresiones JSP son enviadas al método _jspService() como instrucciones out.write().
• Las acciones JSP son reemplazadas por llamadas a las clases específicas.
• Los comentarios JSP son ignorados.
• Cualquier otro texto formará parte del método _jspService(), siendo encapsulado como argumento de
out.write().
Vamos a analizar algunas de estas reglas para entender qué podemos hacer y qué no en los bloques JSP de la
página.
• Las directivas JSP generan instrucciones de compilación en la clase del servlet. Esto quiere decir que el
valor de sus atributos no será evaluado como parte del código del servlet, y por tanto sólo podemos usar
valores literales.
Pongamos el caso de la directiva include. Si incluimos el siguiente código en un JSP se producirá un error:
<% String pageURL = "copyright.html"; %>
<%@ include file="<%= pageUrl%>" %>
En el atributo file se asigna una expresión JSP, la cual debe ser evaluada en el contexto de ejecución del
servlet. Este contexto todavía no está establecido cuando se generan las instrucciones de compilación.
Sin embargo, las acciones JSP generan código que será ejecutado por el servlet. Por tanto el siguiente
código será válido:
<% String pageURL = "copyright.html"; %>
<jsp:include page="<%= pageURL%>" />
• Puesto que los símbolos <%@, <%!, <%=, <%, %>, <%-- y --%> forman parte de la sintaxis JSP,
debemos usar los caracteres de escape HTLM (&lt; para < y &rt; para >) para poder generarlos como salida.
El siguiente ejemplo ilustra su uso:
<%@page info="Un ejemplo de uso de los caracteres ' \" \\ <\% y %\> " %>
<body>
<!-- Genera la salida: Un ejemplo de uso de los caracteres ' " \ &lt;% %&gt;% -->
<%= getServletInfo()%>
<br />
El elemento de apertura de un scriptlet es &lt;%
<br />

129
El elemento de cierre de un scriptlet es %&gt;
<br />
<%= "El elemento de apertura de un scriptlet es &lt;%"%>
<br />
<%= "El elemento de cierre de un scriptlet es %&gt;"%>
</body>
1.5. Variables y objetos implícitos.
Si analizamos el código generado para el método _jspService() de un servlet dinámico podemos ver que se
definen unas constantes y variables locales:
public void _jspService(final javax.servlet.http.HttpServletRequest request,
final javax.servlet.http.HttpServletResponse response)
throws java.io.IOException, javax.servlet.ServletException {
final javax.servlet.jsp.PageContext pageContext;
javax.servlet.http.HttpSession session = null;
final javax.servlet.ServletContext application;
final javax.servlet.ServletConfig config;
javax.servlet.jsp.JspWriter out = null;
final java.lang.Object page = this;
javax.servlet.jsp.JspWriter _jspx_out = null;
javax.servlet.jsp.PageContext _jspx_page_context = null;
……………………….
}
Estas variables y constantes son inicializadas para poder acceder a los siguientes objetos:
Variable Clase o interfaz Objeto
application javax.servlet.ServletContext Referencia el contexto de la aplicación web.
session javax.servlet.http.HttpSession Referencia la sesión actual.
request javax.servlet.http.HttpServletRequest El parámetro del requerimiento actual.
response javax.servlet.http.HttpServletResponse El parámetro de respuesta actual.
out javax.servlet.jsp.JspWriter Referencia al canal de salida para la respuesta.
page java.lang.Object Referencia la instancia actual (this).
pageContext javax.servlet.jsp.PageContext Referencia el contexto de la página.
config javax.servlet.ServletConfig Referencia la configuración del servlet.
Al ser parámetros, constantes y variables locales del método _jspService(), todos estos objetos están
disponibles en los bloques scriptlet de las páginas JSP.
Por ejemplo, el siguiente código de una página JSP incluye un formulario para introducir el nombre de
usuario y guardarlo en un atributo de sesión. El formulario se postea sobre la misma página, de forma que al
inicio se intenta recuperar el parámetro con el nombre y guardarlo en la sesión. En el campo de texto se
asigna el nombre posteado.
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%
// Se intenta recuperar el parámetro tuNombre
String nombre = request.getParameter("tuNombre");
if (nombre==null)
nombre = "";
else
session.setAttribute("tuNombre", nombre);
%>
<form method="POST">
Tu nombre <input type="text" name="tuNombre" value="<%= nombre %>" />
<input type="submit" value="guardar" />

130
</form>
<a href="seguir.jsp">seguir</a>
</body>
</html>
Figura 4

Si además, a la directiva <%@page %> añadimos el atributo isErrorPage="true" se creará la variable local
exception:
Throwable exception = (Throwable) request.getAttribute("javax.servlet.jsp.jspException");
El uso de esta variable será analizado en el capítulo de redirección a páginas de error.
1.6. Inicialización de los JSP.
Ya que en el fondo, un JSP es un servlet, podemos modificar la inicialización de un JSP incluyendo
parámetros de inicialización y reescribiendo los métodos de su ciclo de vida.
1.6.1. Configuración de parámetros de inicialización.
Podemos configurar parámetros de inicio en nuestro JSP virtualmente de la misma forma que se hace con un
servlet. La única diferencia es que debemos añadir un elemento <jsp-file> dentro de la etiqueta <servlet> del
archivo descriptor.
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<servlet>
<servlet-name>MiJsp</servlet-name>
<jsp-file>/index.jsp</jsp-file>
<init-param>
<param-name>email</param-name>
<param-value>[email protected]</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>MiJsp</servlet-name>
<url-pattern>/index.jsp</url-pattern>
</servlet-mapping>
</web-app>
Y ahora podemos recuperar el valor del parámetro email dentro de la página"index.jsp" de varias formas:
<%@page language="java" contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<% out.println("Mi email es "+ this.getServletConfig().getInitParameter("email")); %>
<br/>
<%= "Mi email es " + this.getInitParameter("email") %>
<br/>
Mi email es ${pageContext.servletConfig.getInitParameter("email")}
</body>
</html>

131
Esta página muestra tres líneas con el mismo contenido:
Mi email es [email protected]
Mi email es [email protected]
Mi email es [email protected]
La primera línea se genera mediante un scriptlet, la segunda mediante una expresión JSP, y la tercera mediante
una expresión EL, de las cuales se hablará posteriormente.
1.6.2. Rescritura del método «jspInit».
También podemos realizar inicializaciones en nuestro JSP rescribiendo el método jspInit(), de forma que el
contenedor de servlets invocará este método al comienzo del ciclo de vida de nuestra página. El método
rescrito será invocado desde método init() del servlet dinámico, de forma que en tiempo de ejecución estarán
disponibles los objetos ServletConfig y ServletContext del servlet. Esto significa que podremos invocar los
métodos getServletConfig() y getServletContext() dentro del código de jspInit().
El siguiente ejemplo muestra cómo rescribir el método jspInit() dentro de un scriptlet declarativo de nuestra
página.
<%@page language="java" contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%!
private String correo;
public void jspInit() {
ServletConfig sConfig = this.getServletConfig();
correo = sConfig.getInitParameter("email");
}
%>
<% out.println("Mi email es " + correo);%>
</body>
</html>
1.7. Contexto de página.
Ya se ha explicado que los servlets permiten almacenar datos en tres ámbitos de gestión de estado: aplicación,
sesión y requerimiento. La tecnología JSP añade un nuevo ámbito de página. Por tanto, todos los objetos
implícitos existen en uno de estos cuatro niveles:
• Los objetos del nivel de aplicación (variable application) son compartidos por todos los componentes de
la aplicación web durante la vida de ésta. Estos objetos son mantenidos por una instancia de la clase
ServletContext.
• Los objetos del nivel de sesión (variable session) son compartidos por todas las peticiones de un mismo
usuario de sesión mientras el cliente esté activo. Estos objetos son mantenidos por una instancia de la clase
HttpSession.
• Los objetos del nivel de requerimiento(parámetro request) son compartidos por todos los componentes
que procesan el requerimiento actual, mientras éste está siendo servido. Estos objetos son mantenidos por
una instancia de la clase HttpServletRequest.
• Los objetos del nivel de página (variable pageContext) son accesibles sólo en la unidad de traslación en
los cuales son definidos. No existen fuera del proceso de un requerimiento en su traslación a servlet. Estos
objetos son mantenidos por una instancia de una subclase de la clase abstracta PageContext. A este nivel se
usan los JavaBeans.
La clase PageContext proporciona los siguientes métodos:
• void setAttribute(String name, Object object, int scope), para asignar un atributo.
• Object getAttribute(String name, int scope), para recuperar un atributo.
• void removeAttribute(String name, int scope), para destruir el objeto referenciado por un atributo
• Enumeration getAttributeNamesInScope(int scope), enumera los atributos de un nivel dado.
En el parámetro scope de estos métodos debemos usar la constante APPLICATION_SCOPE, SESSION_SCOPE,
REQUEST_SCOPE o PAGE_SCOPE.

132
• Object findAttribute(String name), busca por orden un atributo en página, requerimiento, sesión y
aplicación.
• int getAttributesScope(String name), obtiene el nivel en el cual se define un atributo dado.
Como se puede ver, los métodos de PageContext permiten acceder a atributos definidos en cualquier nivel o
ámbito. Crear y recuperar un atributo en el contexto de página es tan sencillo como:
<%
Float valor1 = new Float(42.5); %>
pageContext.setAttribute("unValor1", valor1);
out.println( pageContext.getAttribute("unValor1") );
%>
Para asignar y recuperar un atributo en el contexto de sesión:
<%
Float valor2 = new Float(22.4);
pageContext.setAttribute("unValor2", valor2, PageContext.SESSION_SCOPE);
out.println( pageContext.getAttribute("unValor2", PageContext.SESSION_SCOPE) );
// lo cual es idéntico a: <%= session.getAttribute("unValor2") %>
%>
Para asignar y recuperar un atributo en el contexto de aplicación:
<%
Float valor3 = new Float(22.4);
pageContext.setAttribute("unValor3", valor3, PageContext.APPLICATION_SCOPE);
out.println( pageContext.getAttribute("unValor3", PageContext.APPLICATION_SCOPE) );
// lo cual es idéntico a: <%= aplication.getAttribute("unValor2") %>
%>
Si no sabemos en qué contexto existe un atributo podemos usar el método findAttribute(). Este método
retorna null si no encuentra el atributo en ningún contexto, o bien retorna su valor en el primer contexto
donde exista.
<%= pageContext.findAttribute("unValor2") %>
El método busca el atributo primero en el contexto de página, si no lo encuentra lo busca en el contexto de
solicitud, después en el de sesión y por último en el de aplicación.
1.8. Manejar excepciones en páginas JSP.
Al hablar de servlets habíamos explicado el mecanismo de gestión automática de errores, el cual permite al
contenedor web redirigir la respuesta a una página personalizada de error si se producía una excepción en la
ejecución del código o si se enviaba un código de error con el método sendError().
Las páginas JSP amplían este mecanismo con las siguientes opciones:
• Podemos crear una página JSP de error personalizado asignando el atributo isErroPage="true" en la
directiva <%@page %>. Esto provoca que en el servlet dinámico se cree la variable local exception, la cual
contendrá una referencia a la excepción que invoque esta página de error. Esta variable se define así:
Throwable exception = (Throwable) request.getAttribute("javax.servlet.jsp.jspException");
• Podemos establecer en cada página JSP quién es su página de error personalizada asignándola en el
atributo errorPage de la directiva <%@page %>.
Para ilustrar este mecanismo crearemos primero una página JSP de error:
<%-- PÁGINA error.jsp --%>
<%@page isErrorPage="true" contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>Se ha producido un error</h1>
<div><%= exception==null? "Error desconocido." : exception.toString() %></div>
<a href="index.jsp">Retornar</a>
</body>
</html>

133
En esta página se establece el atributo isErrorPage="true", lo que permitirá el uso de la variable exception para
mostrar el mensaje de la excepción producida.
Ahora crearemos una clase de repositorio para almacenar las preferencias de un usuario:
package servicio;
import java.util.*;
public class Repositorio {
private Map<String, String> preferencias = new HashMap<>();
public void addPreferencia(String nombre, String valor) {
if (nombre.isEmpty() || valor.isEmpty())
throw new RuntimeException("Faltan los datos de la preferencia");
preferencias.put(nombre, valor);
}
public Set<String> nombresPreferencias() {
return preferencias.keySet();
}
public String getPreferencia(String nombre) {
return preferencias.get(nombre);
}
}
El método addPreferencia() lanzará una excepción si los datos están vacíos. A continuación, la página
"index.jsp" mostrará un formulario para añadir una preferencia de usuario a un repositorio que se creará en un
atributo de sesión:
<%-- PÁGINA index.jsp --%>
<%@page import="servicio.Repositorio"%>
<%@page errorPage="error.jsp" contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%
String nombre = request.getParameter("nombre");
String valor = request.getParameter("valor");
if (nombre!=null && valor!=null) {
Repositorio repositorio = (Repositorio) session.getAttribute("repositorio");
if (repositorio==null) {
repositorio = new Repositorio();
session.setAttribute("repositorio", repositorio);
}
repositorio.addPreferencia(nombre, valor);
}
%>
<form method="POST">
Preferencia <input type="text" name="nombre" />
<br />
Valor <input type="text" name="valor" />
<br />
<input type="submit" value="guardar" />
</form>
</body>
</html>
El formulario se postea sobre la misma página, por ello al inicio se ejecuta un scriptlet que recupera los datos
de preferencia y los añade a un repositorio guardado en un atributo de sesión. El código garantiza que se
creará el repositorio la primera vez que se use.

134
Figura 5

2. Reutilización de componentes web


Respecto a los JSP, reutilizar un componente web significa incluir su contenido o la salida de otro
componente web en una página JSP. Eso se puedo realizar de dos modos: estáticamente o dinámicamente.
2.1. Inclusión estática.
Una inclusión estática implica que el contenido de otro archivo es incluido en un archivo JSP en el momento
de su traslación a servlet.
Por ejemplo, si en varias páginas queremos incluir una cabecera común, podemos crear el siguiente fichero:
<%-- FICHERO cabecera.jsp --%>
<div class="cabecera">
<h2>CABECERA DE LA EMPRESA</h2>
</div>
Como se hará una inclusión estática literal del contenido, en este fichero se prescinde de las etiquetas HTML
que definen cabeceras y cuerpo de la página.
Ahora en cualquier otra página de la aplicación aplicaremos la inclusión de la cabecera:
<%-- FICHERO index.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8 %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<jsp:directive.include file="cabecera.jsp" />
<div class="cuerpo">
CUERPO DE LA PÁGINA
</div>
</body>
</html>
El archivo incluido puede tener instrucciones HTML, JSP y XML, o ser un simple fichero de texto. El
contenido formará parte del archivo JSP, junto al resto de su propio código, en la posición donde se hace la
inclusión.
La inclusión estática implica que:
• El valor del atributo file de la directiva include no puede ser una expresión que deba evaluarse, debe ser
una URL literal.

135
• Además, esta URL no puede recibir parámetros; es decir, "miURL?x=4" no es una ruta válida para la
directiva include.
2.2. Inclusión dinámica.
Una inclusión dinámica implica que cuando la página JSP es requerida, envía a su vez un requerimiento a otro
recurso, y la respuesta de ese recurso es incluida en la respuesta de la página JSP. Se utilizan las acciones
<jsp:include> y <jsp:forward> para implementar la inclusión dinámica. Su sintaxis completa es:
<jsp:include page="url relativa" flush="true" />
<jsp:forward page="url relativa" />
El recurso demandado en la url puede ser un componente estático o dinámico, incluido un servlet. La url del
recurso puede obtenerse en tiempo de ejecución evaluando una expresión. Por ejemplo, es válido:
<% String paginaURL = "otro.jsp" %>
<jsp:include page="<%=paginaURL%>" />
Funcionalmente, <jsp:include /> es equivalente al método RequestDispatcher.include(). Esta acción delega
temporalmente el proceso de respuesta al recurso incluido; una vez que el recurso finaliza, transfiere de nuevo
el control a la página que lo incluye. Por ejemplo, si creamos las siguientes páginas JSP:
<%-- FICHERO index2.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>CONTENIDO DE INDEX2</h1>
</body>
</html>
Esta página será incluida en la siguiente página:
<%-- FICHERO index.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>CABECERA DE INDEX</h1>
<jsp:include page="index2.jsp" />
<h1>PIE DE INDEX</h1>
</body>
</html>
Si ejecutamos la página index.jsp obtendremos la siguiente salida en un navegador:
CABECERA DE INDEX
CONTENIDO DE INDEX2
PIE DE INDEX
Es decir, obtenemos la respuesta esperada de una inclusión.
Funcionalmente, la acción<jsp:forward> es equivalente al método RequestDispatcher.forward(). Esta acción
delega totalmente el proceso de respuesta al recurso redirigido. Si en el ejemplo previo sustituimos
<jsp:include page="index2.jsp" /> por <jsp:forward page="index2.jsp" />, en el navegador obtendremos:
CONTENIDO DE INDEX2
Es decir, la página redireccionada index2.jsp sustituye totalmente la salida generada desde index.jsp. Pero esto
no quiere decir que no se ejecute cualquier código de index.jsp previo a la acción <jsp:forward />,
simplemente se ignora el contenido del canal de salida.
En ambas acciones es posible pasar parámetros al recurso redireccionado o incluido. Por ejemplo, podemos
crear un fichero que declare una sección para incluir una cabecera con una imagen y el nombre de nuestra
empresa.
<%-- FICHERO cabecera.jsp --%>

136
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<body>
<table>
<tr>
<td><img src='<%= request.getParameter("imagen") %>'/></td>
<td><h1><%= request.getParameter("empresa") %></h1></td>
</tr>
</table>
</body>
</html>
Este fichero JSP recupera dos parámetros de solicitud: la ruta de una imagen y el texto de la empresa.
Desde una página podemos hacer la inclusión de esta cabecera pasando valores a los parámetros de solicitud:
<%-- FICHERO index.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<jsp:include page="cabecera.jsp">
<jsp:param name="imagen" value="logo.gif" />
<jsp:param name="empresa" value="EMPRESA S.A." />
</jsp:include>
<h1>CUERPO DE LA PÁGINA</h1>
</body>
</html>
Hay que tener en cuenta que, además, el recurso incluido o redireccionado compartirá los objetos request y
response de la página, y por tanto tendrá acceso también a los atributos del ámbito de solicitud.
Como conclusión: el recurso incluido o redireccionado y la página JSP que lo llama comparten el mismo
contexto de solicitud.
2.3. Cabeceras y pies de página automáticos.
En el elemento <jsp-config> del archivo descriptor podemos habilitar o deshabilitar diversas configuraciones
para grupos de páginas. Una de estas configuraciones permite establecer una cabecera y pie para grupos de
páginas que serán insertados automáticamente.
Podemos utilizar el editor de NetBeans para crear un nuevo grupo de páginas. Para ello hay que abrir el
fichero web.xml y pulsar la pestaña «Páginas». En la categoría «JSP Property Groups» hay que pulsar el botón
«Add JSP Property Group».

137
Figura 6

En la imagen previa se ha creado un grupo de páginas que incluye a todas las páginas del sitio web (asignando
/* en «URL Pattern(s)»). Se ha incluido una página llamada "cabecera.jsp" como página a incluir en la cabecera
de cada página del grupo, y una página llamada "pie.jsp" como página a incluir en el pie de cada página del
grupo. El código en el fichero web.xml será el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<jsp-config>
<jsp-property-group>
<display-name>Todas las páginas</display-name>
<url-pattern>/*</url-pattern>
<el-ignored>false</el-ignored>
<scripting-invalid>false</scripting-invalid>
<is-xml>false</is-xml>
<include-prelude>/cabecera.jsp</include-prelude>
<include-coda>/pie.jsp</include-coda>
</jsp-property-group>
</jsp-config>
</web-app>
Las páginas "cabecera.jsp" y "pie.jsp" serán incluidas en tiempo de traslación, y por tanto sólo deben incluir
aquel código que queramos embeber, sin etiquetas <html> o <body>. Para este ejemplo usaremos la página
"cabecera.jsp" para incluir estilos CSS:
<%-- FICHERO cabecera.jsp --%>
<style type="text/css">
.menu {
position: fixed;
top: 0%;
left: 0%;
height: 100%;
width: 25%;
background-color: lightblue;
}
.cuerpo {
position: fixed;

138
top: 0%;
left: 25%;
height: 100%;
width: 75%;
background-color: aliceblue;
}
</style>
La página de pie se reservará para un menú de enlaces:
<%-- FICHERO pie.jsp --%>
<div class='menu'>
Menú de enlaces.
</div>
Ahora cualquier página del sitio web tendrá este aspecto:
Figura 7

Otras propiedades que podemos configurar en un grupo de páginas son:


• <el-ignored />, especifica si deben ignorarse o no las expresiones EL de la página. Estudiaremos estas
expresiones EL posteriormente.
• <scripting-invalid>, especifica si la página no debe admitir bloques scriptlet. Un valor a true provocará que
la página genere un error si evalúa cualquier bloque JSP.
• <is-xml>, especifica si debe obligarse a utilizar la sintaxis XML para cualquier bloque JSP.
Si queremos aislar grupos de páginas para aplicarles propiedades distintas bastará con ubicarlas en carpetas
diferentes, y modificar apropiadamente el elemento <url-pattern>/carpeta/*</url-pattern>.
2.4. Inclusión de recursos externos.
El elemento HTML<object> permite también incluir recursos externos dentro de nuestras páginas JSP.
Dependiendo del tipo de recurso puede ser tratado como una imagen, como un navegador embebido, o
como un recurso externo que será procesado por un complemento (como por ejemplo un applet).
El elemento <object> posee los siguientes atributos propios:
data Especifica la dirección del recurso. Debe ser una URL válida.
type Especifica el tipo de recurso. Debe ser un tipo MIME válido.
name Especifica un nombre válido que puede ser usado en el contexto del navegador.
usemap Si el objeto representa una imagen, especifica un mapa de imagen asociado con la imagen.
Es ignorado si el objeto no representa una imagen.
form Se usa para asociar explícitamente un formulario con el objeto.
Los atributos data y type son excluyentes, se debe especificar uno u otro.
En el siguiente ejemplo se muestra cómo embeber un applet de Java dentro de una página usando este
elemento <object>.
<object type="application/x-java-applet">
<param name="code" value="MiAppletClass">
<p>Falta el complemento para ejecutar applets, o está desactivado.</p>
</object>
En el siguiente ejemplo, una página HTML llamada "reloj.html" es embebida dentro de otra:
<html>
<body>

139
<object data="reloj.html"></object>
</body>
</html>
En este último ejemplo, se muestra cómo usar un complemento en HTML (en este caso el complemento
Flash para mostrar un fichero de vídeo). La etiqueta proporciona un subelemento <video> para mostrar el
vídeo en aquellos navegadores que soportan Flash, y un enlace <a> al vídeo para aquellos navegadores que
no soportan Flash.
<object type="application/x-shockwave-flash">
<param name=movie value="http://video.example.com/library/watch.swf">
<param name="allowfullscreen" value="true">
<param name="flashvars" value="http://video.example.com/vids/315981">
<video controls src="http://video.example.com/vids/315981">
<a href="http://video.example.com/vids/315981">Ver vídeo</a>
</video>
</object>

3. Uso de JavaBeans en páginas JSP


Los JavaBeans son componentes de software independientes, portables y reusables que se usan para
ensamblar otros componentes y aplicaciones.
El motor JSP actúa como un contenedor de beans, de forma que una clase que siga las siguientes
convenciones puede ser usada como un JavaBean en páginas JSP:
• La clase debe tener un constructor público sin argumentos.
• Cada propiedad de la clase debe ser accedida por un método setter y/o getterpúblicos, siguiendo la
convención de nombrado de los Java Beans.
3.1. Uso de los JavaBeans.
En las páginas JSP se usan normalmente clases JavaBeans para instanciar objetos que recojan los datos
posteados por un formulario. Un uso habitual de este escenario incluirá los siguientes pasos:
1) En una página JSP crearemos un formulario HTML, dándole un nombre a cada elemento.
2) Creamos una clase bean en un fichero .java, definiéndole tantas propiedades (con métodos getter y
setter) como parámetros posteará el formulario.
3) En la página que recibe los datos del formulario, añadiremos una etiqueta <jsp:useBean /> para crear y
asignar una instancia del bean.
4) Añadiremos una o varias etiquetas <jsp:setProperty> para recoger los valores del formulario (el bean
necesita un método set correspondiente a cada parámetro de solicitud).
5) Añadiremos una etiqueta <jsp:getProperty> para recuperar y mostrar los datos desde el bean (el bean
necesita un método get correspondiente a cada parámetro de solicitud).
6) Si necesitamos realizar más procesos sobre los datos del usuario, usamos el objeto requesty la instancia
del bean dentro del código de un scriptlet.
El siguiente ejemplo usa un formulario para introducir un nombre, y como respuesta nos saludan después del
formulario. Los datos del formulario los recogeremos en el bean UsuarioBean. La acción del formulario será
llamar a la propia página, y por ello condicionaremos el uso del bean a recibir previamente datos a través del
parámetro de llamada nombreUsuario.
A continuación se muestra el código de la clase UsarioBean:
package bean;
public class UsuarioBean implements java.io.Serializable {
private String nombreUsuario;
public String getNombreUsuario() {
return nombreUsuario;
}
public void setNombreUsuario(String nombreUsuario) {
this.nombreUsuario = nombreUsuario;
}
}
Ahora el fichero con el formulario:
<%-- FICHERO index.jsp --%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>

140
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<%-- EL FORMULARIO EMPIEZA AQUÍ --%>
<h1>¿Cómo te llamas?</h1>
<form>
<input type="text" name="nombreUsuario">
<input type="submit" value="Envíar">
</form>
<%-- EL FORMULARIO ACABA AQUÍ --%>
<%-- SI RECIBIMOS PARÁMETRO RELLENAMOS EL BEAN E INCLUIMOS EL SALUDO --%>
<% if (request.getParameter("nombreUsuario") != null) { %>
<jsp:useBean id="mibean" class="bean.UsuarioBean" />
<jsp:setProperty name="mibean" property="*" />
<h2>¡HOLA, <jsp:getProperty name="mibean" property="nombreUsuario" />! </h2>
<% }%>
</body>
</html>
En este ejemplo, la etiqueta <jsp:useBean> se encarga de instanciar un objeto del tipo bean.UsuarioBean. La
etiqueta <jsp:setProperty> se encarga de poblar las propiedades de este objeto con cualquier parámetro de
solicitud que coincida en nombre con una propiedad. Y la etiqueta <jsp:getProperty> se encarga de recuperar
el valor de la propiedad nombreUsuario y mostrarla en la página.
3.2. Acciones JSP para usar JavaBeans.
Veamos ahora con mayor detalle el funcionamiento de estas etiquetas.
3.2.1. La acción «useBean».
La acción <jsp:useBean /> nos permite cargar y utilizar un JavaBean en la página JSP. Su sintaxis habitual es
la siguiente:
<jsp:useBean id="nombre" class="paquetes.Clase" scope="page" />
Donde
•id, especifica el nombre de la variable a la que se referirá el objeto bean. Como <useBean /> genera una
variable de java con el nombre especificado, éste debe ser un nombre válido de variable.
•class, especifica la clase de la variable a la que se referirá el objeto bean. Como <useBean /> utiliza técnicas
de reflexión, debe ponerse el nombre completo de la clase (con su ruta de paquetes).
•scope, indica el contexto en el que el bean debería estar disponible. Tiene cuatro posibles valores: page (es
el valor por defecto, e indica que el bean está disponible sólo para la página actual), request (está disponible
sólo en la solicitud actual), session (está disponible en la sesión actual), y application (está disponible en todas
las páginas que compartan el mismo ServletContext).
Cuando el motor JSP evalúa esta etiqueta se ejecutan los siguientes pasos:
1) Si existe un atributo de ámbito con el nombre especificado en id, lo recupera y lo asigna a una variable
local de Java llamada igual que el nombre especificado en id.
2) Si no existe el atributo en el ámbito especificado, instancia la clase bean y crea un atributo en el ámbito y
una variable de Java con el nombres especificado en el id.
3) Si es la primera vez que se instancia el bean ejecuta el cuerpo del elemento <jsp:useBean />.
Por ejemplo, para el siguiente elemento:
<jsp:useBean id="mibean" class="bean.UsuarioBean">
</jsp:useBean>
El código Java generado en el servlet dinámico es el siguiente:
bean.UsuarioBean mibean = null;
mibean = (bean.UsuarioBean) _jspx_page_context.getAttribute("mibean", PageContext.PAGE_SCOPE);
if (mibean == null) {
mibean = new bean.UsuarioBean();
_jspx_page_context.setAttribute("mibean", mibean, PageContext.PAGE_SCOPE);
}

141
3.2.2.¿Cómo inicializar las propiedades de los beans?
Los beans son instanciados por el motor JSP usando un constructor sin argumentos, por ello no se pueden
inicializar sus propiedades durante la instanciación. Sin embargo, si tenemos este código:
<jsp:useBean id="mibean" scope="session" class="bean.UsuarioBean" >
<%
mibean.setNombreUsuario("Pedro");
%>
<%-- o bien --%>
<jsp:setProperty name="mibean" property="nombreUsuario" value="Pedro" />
</jsp:useBean>
Ocurre lo siguiente: si al nivel de sesión existe el bean mibean, el cuerpo de la acción <jsp:useBean /> es
ignorado; si no existe el bean, es instanciado a nivel de sesión y se ejecuta el código del cuerpo (en este caso
asignamos valor a la propiedad nombreUsuario de una de las manera indicadas).
Por tanto, dentro del cuerpo de la acción useBean podemos escribir cualquier código que será ejecutado la
primera vez que se instancie el bean.
3.2.3. Cambiar las propiedades usando «setProperty».
Se usa la acción <jsp:setProperty /> para asignar valores a las propiedades de los beans. Usa estos atributos:
name es el nombre del bean tal como es identificado en el atributo id dejsp:useBean.
property es el nombre de la propiedad a la cual se quiere asignar valor.
value es el valor que se quiere asignar.
param es el nombre del parámetro disponible en el HttpServletRequest del cual se tomará el valor.
Por ejemplo, en el siguiente elemento:
<jsp:setProperty name="mibean" property="nombreUsuario" value="Pedro" />
Se inicializa la propiedad nombreUsuario del bean mibean al valor "Pedro". El valor puede ser un literal o una
expresión que pueda evaluarse. Si queremos asignar la propiedad desde un parámetro de solicitud se utiliza el
atributo param:
<jsp:setProperty name="mibean" property="nombreUsuario" param="usuario" />
En este caso se asigna la propiedad nombreUsuario del bean mibean con el valor del parámetro de solicitud de
nombre usuario, si es que existe.
Si no se indica un atributo value ni param, el motor JSP busca un parámetro de solicitud con el mismo
nombre que la propiedad del bean y usa su valor.
Nota.Los parámetros con más de un valor son convertidos en un array, y por tanto debemos recuperarlo
en una propiedad de tipo array.
Si queremos rellenar automáticamente todas las propiedades del bean a través de parámetros de llamada
podemos usar:
<jsp:setProperty name="mibean" property="*" />
El modo de funcionar de esta acción es la siguiente:
• Se recuperan todos los nombres de parámetros de invocación.
• Por cada nombre de parámetro de invocación:
- En la clase del objeto especificado en el atributo name se busca un método accesor que se corresponda
con el nombre. Por ejemplo, si el parámetro se llama "propiedad1", se busca el método accesor
"setPropiedad1".
- Si se encuentra el método accesor se invoca sobre el objeto, y se le pasa como argumento el valor del
parámetro.
Si es necesario este valor es convertido al tipo del argumento del método accesor. Para realizar la
conversión de tipos se utiliza un editor de propiedades (java.beans.PropertyEditor) registrado en la clase
java.beans.PropertyEditorManager. Existen editores por defecto para los tipos primitivos de Java (boolean,
byte, short, int, long, float y double), y para las clases java.lang.String, java.awt.Color, y java.awt.Font.
Si no se encuentra el editor de propiedades correspondiente se lanza una excepción.
El siguiente ejemplo de código muestra cómo crear un editor de propiedades personalizado para procesar
datos del tipo java.util.Date pasados desde un formulario en el formato año-mes-día.
package bean;
import java.beans.PropertyEditorManager;
import java.beans.PropertyEditorSupport;
import java.text.SimpleDateFormat;
import java.text.ParseException;

142
public class EditorDateUTC extends PropertyEditorSupport {
public void setAsText(String fecha){
Date d = null;
try {
d= new SimpleDateFormat("yyyy-MM-dd").parse(fecha);
} catch (ParseException ex) {
}
super.setValue(d);
}
public String getAsText() {
Date d = (Date) super.getValue();
if (d == null) {
return "";
} else {
return new SimpleDateFormat("yyyy-MM-dd").format(d);
}
}
}
Podemos registrar esta clase en una clase oyente del inicio de la aplicación:
package oyentes;
import bean.EditorDateUTC;
import java.beans.PropertyEditorManager;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ObservadorContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
PropertyEditorManager.registerEditor(java.util.Date.class, EditorDateUTC.class);
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
}
}
3.2.4. Acceder a las propiedades usando «getProperty».
Se usa la acción <sp:getProperty />para recuperar los valores de las propiedades de los beans. Por ejemplo:
<jsp:getProperty name="mibean" property="nombreUsuario" />
Esta acción genera el valor de la propiedad como salida de la página JSP, pero no utiliza un editor de
propiedades. De hecho esta acción es equivalente a:
<% out.print(mibean.getNombreUsuario()); %>
Esto también implica que no puede usarse <jsp:getPropety /> como expresión de evaluación. Por ejemplo, el
siguiente código da error al ejecutarlo dentro de un fichero JSP:
<jsp:include page='<jsp:getProperty name="mibean" property="nombreUsuaro" />.txt' />
Para este caso deberemos utilizar:
<jsp:include page='<%= mibean.getNombreUSuario() %>.txt' />
3.3. Serialización de JavaBeans.
Podemos mantener la existencia de un JavaBean utilizando el mecanismo de serialización de objetos de Java.
Para ello, la clase del bean debe implementar la interfaz java.io.Serializable. Entonces podemos guardarlo en
un archivo usando la clase java.io.ObjectOutputStream.
Los archivos donde se copia un bean serializado deben tener extensión .ser. Y el archivo debe ubicarse dentro
del directorio raíz de la aplicación (normalmente se hará en /WEB_INF/classes o un subdirectorio del mismo).
El siguiente ejemplo de código guarda un objeto UsuarioBean (que es una clase serializable) en el archivo
"bean1.ser" dentro de la carpeta "WEB-INF/classes/misBeans":
<%@ page import="bean.UsuarioBean, java.io.* " %>
<%
String mensaje=null;
//Creamos una instancia.
UsuarioBean mibean = new UsuarioBean();
String rutaRelativa = "/WEB-INF/classes/misBeans/bean1.ser";
String rutaReal = application.getRealPath(rutaRelativa);

143
//Serializamos el objeto en el archivo
try (ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(rutaReal)) {
oout.writeObject(mibean);
oout.close();
mensaje = "Se ha guardado el bean en " + rutaReal;
} catch(Exception e) {
mensaje = "Error: no se pudo guardar el bean";
}
%>
<html>
<body>
<h3><%= message %></h3>
</body>
</html>
Para recuperar el objeto serializado, podemos usar ahora el siguiente código:
<%@ page import="SerializableBean" %>
<html>
<body>
<jsp:useBean id="elBean" type="UsuarioBean" beanName="misBeans.bean1" />
</body>
</html>
En el atributo beanName de la etiqueta <jsp:useBean> se asigna la ruta del archivo serializado como si fuese
una clase más de Java ubicada dentro de la carpeta "/WEB-INF/classes" y el paquete "misBeans" (nótese que no
se añade la extensión .ser). Es necesario especificar el atributo type con la clase del bean o bien una superclase
o bien una interfaz que implemente la clase del bean.
3.4. Uso de beans instanciados desde páginas JSP en Servlets.
Las acciones JSP no se pueden utilizar en el código Java de un servlet, pero si podemos trabajar con las
instancias creadas desde una acción useBean. A fin de cuentas, la acción useBean recupera o instancia objetos
en los ámbitos del contenedor web, y estos ámbitos están disponibles también en los servlets.
Como ejemplo de esto, veremos cómo implementar una sencilla aplicación de carro de compra usando
páginas JSP y servlets. Esta aplicación incluirá dos beans, primero una clase Compra para almacenar la fecha y
descripción de una compra:
package bean;
import java.text.*;
import java.util.Date;
public class Compra implements java.util.Serializable {
private static final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
private Date fecha;
private String descripcion;
public Date getFecha() { return fecha; }
public void setFecha(Date fecha) { this.fecha = fecha; }
public String getFechaStr() { return fecha == null ? "" : dateFormat.format(fecha); }
public String getDescripcion() { return descripcion; }
public void setDescripcion(String descripcion) { this.descripcion = descripcion; }
}
Por comodidad se ha incluido una propiedad de solo lectura fechaStr que retorna la fecha en el formato "año-
mes-día".
Y una clase ServcioCompra para persistir las compras en cada sesión.
package bean;
import java.util.*;
public class ServicioCompra {
private List<Compra> compras = new ArrayList<>();
public void addCompra(Compra compra) {
this.compras.add(compra);
}
public List<Compra> getCompras() {
return compras;
}
public void persistirCompras() {

144
// código para almacenar las compras.
}
}
Figura 8

La página "altaCompras.jsp" presentará el formulario para realizar una compra, para mostrar las compras
acumuladas y para confirmar las compras.
<%@page import="bean.Compra"%>
<%@page contentType="text/html" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<jsp:useBean id="servicio" class="bean.ServicioCompra" scope="session" />
<form action="procesaCompra.jsp" method="POST">
<table>
<tr>
<td>Fecha</td><td><input type="date" name="fecha" value="" /></td>
</tr>
<tr>
<td>Descripción</td><td><textarea name="descripcion"></textarea></td>
</tr>
<tr>
<td><input type="submit" value="Agregar" /></td><td></td>
</tr>
</table>
</form>
<h2>Compras realizadas</h2>
<table border="1">
<thead>
<tr><th>Fecha</th><th>Descripción</th></tr>
</thead>
<tbody>
<jsp:scriptlet>for (Compra c : servicio.getCompras()) {</jsp:scriptlet>
<tr>
<td><jsp:expression>c.getFechaStr()</jsp:expression></td>

145
<td><jsp:expression>c.getDescripcion()</jsp:expression></td>
</tr>
<jsp:scriptlet>}</jsp:scriptlet>
</tbody>
</table>
<br />
<form action="confirmar"><input type="submit" value="Confirmar compras" /></form>
</body>
</html>
La primera vez que se ejecute esta página se creará una instancia de la clase bean.ServicioCompra en el ámbito
de sesión. En este formulario los datos serán posteados a la página "procesaCompra.jsp", la cual recuperará los
datos mediante un bean y los insertará en el servicio de compras.
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<jsp:useBean id="servicio" class="bean.ServicioCompra" scope="session" />
<jsp:useBean id="compra" class="bean.Compra" />
<jsp:setProperty name="compra" property="*" />
<jsp:scriptlet>servicio.addCompra(compra);</jsp:scriptlet>
<jsp:forward page="altaCompras.jsp" />
</body>
</html>
Esta página recupera la instancia del servicio del ámbito de sesión, e instancia un objeto bean.Compra que
puebla con los parámetros de solicitud. Inserta la nueva compra en el servicio y vuelve a redirigir a la página
"altaCompras.jsp".
Cuando se pulsa el botón de confirmar un servlet persistirá las compras y las eliminará de la sesión.
package servlets;
import bean.ServicioCompra;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
@WebServlet(name = "ServletConfirmar", urlPatterns = {"/confirmar"})
public class ServletConfirmar extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession sesion = request.getSession();
ServicioCompra servicio = (ServicioCompra) sesion.getAttribute("servicio");
servicio.persistirCompras();
sesion.removeAttribute("servicio");
request.getRequestDispatcher("/altaCompras.jsp").forward(request, response);
}
}

4. JSTL y el lenguaje EL
Como se ha visto, las acciones JSP permiten manipular beans y sus propiedades, además de realizar
inclusiones y redirecciones de páginas. Pero se echa en falta acciones que implementen estructuras de
programación, como bucles, selecciones, etc.
La API JSTL responde a la demanda de los desarrolladores proporcionando un nuevo conjunto de acciones
JSP personalizadas para realizar tareas comunes en casi todas las páginas JSP, incluyendo procesamiento
condicional, internacionalización, acceso a bases de datos y procesamiento XML. Esto acelera el desarrollo de
los JSP eliminando la necesidad de elementos de scripting y los inevitables y difíciles de encontrar errores de

146
sintaxis, y liberando el tiempo anteriormente gastado en desarrollar y aprender trillones de acciones
personalizadas específicas del proyecto para estas tareas comunes.
4.1. Las librerías JSTL.
JSTL especifica un conjunto de librerías de etiquetas basadas en el API JSP 1.2. Hay cuatro librerías de
etiquetas independientes, y cada una contiene acciones personalizadas dirigidas a un área funcional específica.
Esta tabla lista cada librería con su prefijo de etiqueta recomendado y la URI por defecto:
Librería Prefijo URI por defecto Descripción
Core c Contiene acciones para las tareas rutinarias.
http://java.sun.com/jsp/jstl/core
XML Processing x http://java.sun.com/jsp/jstl/xmlContiene acciones para procesamiento
XML.
I18N &Formatting fmt http://java.sun.com/jsp/jstl/fmt Contiene acciones para globalizar y
localizar las aplicaciones Web.
Database Access sql http://java.sun.com/jsp/jstl/sql Contiene acciones para leer y modificar
información almacenada en una base de
datos.
El entorno de desarrollo dispone de estas librerías y nos permite añadirlas a nuestra aplicación accediendo a
su biblioteca.
Figura 9

Para usar las acciones de una librería JSTL, debemos declarar la librería en cada página usando una directiva
taglib. Por ejemplo, para usar las etiquetas Core es necesaria la siguiente etiqueta:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
4.2. El Lenguaje de Expresiones JSTL.
Además de las librerías de etiquetas, JSTL define un llamado Lenguaje de Expresiones (EL). EL es un
lenguaje para acceder a datos de varias fuentes en tiempo de ejecución. Su sintaxis es considerablemente más
amigable que la de Java, que es el único lenguaje soportado directamente por la especificación JSP. Todas las
acciones JSTL reconocen expresiones EL en sus valores de atributos, y se podrían desarrollar acciones
personalizadas para que hicieran lo mismo. EL ha sido incorporado dentro de la última versión de la
especificación JSP para mejorar su uso en el acceso a datos sobre el lenguaje Java. Así, podremos usar
expresiones EL en un valor de atributo de una acción, o directamente embebido en el código HTML de una
página JSP.

147
Importante. Las expresiones EL no forman parte del lenguaje Java, y por tanto no pueden usarse dentro
del código de un scriptlet.
4.2.1. Sintaxis de expresiones EL.
Las expresiones EL fueron pensadas inicialmente para acceder a los datos disponibles en los diversos
contextos de una aplicación web, incluyendo los parámetros de solicitud, cookies, cabeceras y atributos de
ámbito. Cuando recupera un objeto proporciona una sintaxis abreviada para acceder a sus propiedades.
En general, EL presenta tres sintaxis para recuperar la propiedad de un objeto accesible:
${unObjeto.unaPropiedad}
${unObjeto["unaPropiedad"]}
${unObjeto[variableConElNombreDeLaPropiedad]}
Como se ve aquí, una expresión EL siempre debe estar encerrada entre los caracteres ${ y }. Las dos primeras
expresiones acceden a una propiedad llamada unaPropiedad en un objeto representado por una variable
llamada unObjeto.
Nota. Acceder a la propiedad unaPropiedad de un objeto unObjeto, implica que la clase del objeto
unObjeto debe poseer un método público denominado getUnaPropiedad().
La tercera expresión accede a una propiedad con un nombre contenido en un variable, esta sintaxis se puede
utilizar con cualquier expresión que evalúe el nombre de la propiedad.
El operador de acceso a array también se usa para datos representados como una colección de elementos
indexados, como un array de Java o una colección de tipo java.util.List:
${unaLista[2]}
${unaLista[unaVariable + 1]}
EL está pensado para acceder a variables definidas como atributos en algún contexto, pero no está penado
para recuperar variables de Java. Por ejemplo, si definimos variables y atributos en un scriptlet:
<jsp:scriptlet>
Integer var1 = 34;
application.setAttribute("var2", "Aplicación");
session.setAttribute("var2", "Sesión");
</jsp:scriptlet>
Podemos recuperar el atributo "var2" con una expresión EL:
${var2} <%-- lo encuentra en el contexto de sesión y escribe el valor "Sesión" –%>
Pero no podemos recuperar la variable "var1":
${var1} <%-- retorna el valor null y no escribe nada --%>
En el primer caso, la expresión EL busca un atributo de nombre "var2" en los contextos en el siguiente orden:
solicitud (request), página (pageContext), sesión (session) y aplicación (application).
En el segundo caso, la expresión EL busca un atributos de nombre "var1" en los cuatro contextos, y como no
la encuentra retorna el valor nulo y no escribe nada.
4.2.2. Operadores y literales en expresiones EL.
Además de los operadores de propiedad y elemento array y los operadores aritméticos, relacionales, y lógicos,
hay un operador especial para comprobar si un objeto está "vacío" o no puede ser usado en una expresión EL
(empty). La siguiente tabla lista todos los operadores que podemos usar en expresiones EL:
Operador Descripción
. Accede a una propiedad
[] Accede a un elemento de un array/lista
() Agrupa una subexpresión
+ Suma
- Resta o negación de un número
/ ó div División
% ó mod Módulo (resto)
== ó eq Comprueba Igualdad
!= ó ne Comprueba desigualdad
< ó lt Comprueba menor que
> ó gt Comprueba mayor que
<= ó le Comprueba menor o igual que
>= ó gt Comprueba mayor o igual que
&& ó and Comprueba AND lógico

148
|| ó or Comprueba OR lógico
! ó not Complemento binario booleano
empty Comprueba un valor vacío (null, string vacío, o una colección vacía)
Lo que no encontraremos en EL son sentencias como asignaciones, if/else, o while. Para este tipo de
funcionalidades en JSP se usan las acciones de la librería Core de JSTL; y EL no está pensado para utilizarse
como un lenguaje de programación de propósito general, sino como un lenguaje de acceso a datos.
Por supuesto, los literales y las variables también son parte del lenguaje. EL proporciona los siguientes
literales, similares a los que proporcionan JavaScript, Java, y otros lenguajes similares:
Tipo de literal Descripción
String Encerrado con comillas simples o dobles. Una comilla del mismo tipo dentro del
string puede ser escapada con una barra invertida: ( \' en un string encerrado con
comillas simples; \" en un string encerrado con comillas dobles). El caracter de barra
invertida debe ser escapado como \\ en ambos casos.
Entero Un signo opcional (+ o -) seguido por dígitos entre 0 y 9.
Coma flotante Lo mismo que un literal entero, excepto que usa un punto como separador de la parte
fraccional y que se puede especificar un exponente con e o E, seguido por un literal
entero.
Booleano true o false.
Nulo null.
Cualquier objeto en uno de los ámbitos de JSP (página, solicitud, sesión o aplicación) se puede utilizar como
una variable en una expresión EL. Por ejemplo, si tenemos un bean con una propiedad primerNombre en el
ámbito de la solicitud bajo el nombre cliente, la siguiente expresión EL representa el valor de la propiedad
primerNombre del bean.
${cliente.primerNombre}
Pero no se para aquí. EL también hace que la información de la solicitud y la información general del
contenedor esté disponible como un conjunto de variables implícitas:
Variable Descripción
param Un mapa con todos los parámetros de la solicitud como un sólo valor string para cada
parámetro.
paramValues Un mapa con todos los valores de los parámetros de la solicitud como un array de
valores string por cada parámetro.
header Un mapa con todas las cabeceras de solicitud como un sólo valor string por cada
cabecera.
headerValues Un mapa con todos los valores de cabecera de la solicitud como un array de valores
string por cada cabecera.
cookie Un mapa con todas las cookies de la solicitud en un sólo ejemplar de
javax.servlet.http.Cookie por cada cookie.
initParam Un mapa con todos los parámetros de inicialización de la aplicación en un sólo valor
string por cada parámetro.
pageContext Un ejemplar de la clase javax.servlet.jspPageContext.
pageScope Un mapa con todos los atributos en el ámbito de la página.
requestScope Un mapa con todos los atributos en el ámbito de la solicitud.
sessionScope Un mapa con todos los atributos en el ámbito de la sesión.
applicationScope Un mapa con todos los atributos en el ámbito de la aplicación.
Las cinco primeras variables implícitas de la tabla nos ofrecen acceso a los valores de parámetros, cabeceras y
cookies de la solicitud actual. Aquí hay un ejemplo de cómo acceder a un parámetro de solicitud llamado
listType y a la cabecera User-Agent:
${param.listType}
${header["User-Agent"]}
En este caso debemos usar la sintaxis de array para la cabecera, porque el nombre incluye un guion; con la
sintaxis de propiedad, sería interpretado como la expresión variable header.User menos el valor de una
variable llamada Agent.

149
La variable initParam proporciona acceso a los parámetros de inicialización que se definen en el fichero
web.xml de la aplicación. La variable pageContext tiene varias propiedades que proporcionan acceso al objeto
servlet que representa la solicitud, la respuesta, la sesión y la aplicación, etc.
Las cuatro últimas variables son colecciones que contienen todos los objetos de cada ámbito específico.
Podemos usarlas para limitar la búsqueda de un objeto en sólo un ámbito en lugar de buscar en todos ellos, lo
que está por defecto si no se especifica ningún ámbito. En otras palabras, si hay un objeto llamado cliente en
el ámbito de sesión, estas dos primeras expresiones encuentran el mismo objeto, pero la tercera vuelve vacía:
${customer}
${sessionScope.customer}
${requestScope.customer}
Todas las acciones JSTL aceptan expresiones EL como valores de atributo, excepto para los atributos var y
scope, porque estos valores de atributo podrían usarse para verificar el tipo en el momento de la traducción
en una futura versión. Se pueden usar una o más expresiones EL en el mismo valor de atributo, y el texto fijo
y las expresiones EL se pueden mezclar en el mismo valor de atributo:
Nombre: <c:out value="${cliente.Nombre}" />
<c:out value="Nombre: ${cliente.Nombre}" />
Nota. Debe tenerse en cuenta que si una expresión EL retorna un string que contiene etiquetas HTML,
estas etiquetas serán procesadas por el navegador y no se escribirán en la salida de la página. Por ejemplo,
si tenemos la siguiente expresión:
${"las etiquetas <b></b> se usan para negritas"}
En la página se escribirá:
lasetiquetas se usan para negritas
4.2.3. Acceso a arrays y colecciones mediante EL.
Las expresiones EL consideran a cualquier colección de elementos como si fuese un mapa. Esto es evidente
para colecciones que implementen la interfaz Map. Pero es que este principio también vale para los arrays y
listas indexadas.
Por ejemplo, supongamos los siguientes objetos:
<%
String[] color = {"azul", "rojo", "verde"}; // un array de strings
List<Integer> lista = Arrays.asList(1, 2, 3, 4); // una lista de números
// Los guardamos en atributos de página
pageContext.setAttribute("color", color);
pageContext.setAttribute("lista", lista);
%>
Podemos usar expresiones EL para recuperar los elementos del array con dos sintaxis.
${color[0]}
${color["1"]}
La primera expresión utiliza la sintaxis habitual de uso de arrays: se recupera el primer color (azul) indexando
el array con un índice entre corchetes. La segunda expresión recupera el segundo color (rojo) usando el índice
"1" como si los elementos del array tuviesen asociada una clave de tipo string, cuyo valor es su posición
dentro del array.
Podemos utilizar estas dos sintaxis también para recuperar los elementos de la lista:
${lista[0]}
${lista["1"]}
Si ahora declaramos una colección de tipo mapa:
<%
HashMap<String, Integer> secuencia = new HashMap<String, Integer>();
secuencia.put("uno", 1);
secuencia.put("dos", 2);
pageContext.setAttribute("secuencia", secuencia);
%>
Podemos acceder al contenido del mapa con la siguiente expresión:
${secuencia}
Obteniendo como resultado:
{uno=1, dos=2}

150
Se muestra el contenido del mapa en formato de pares clave=valor. Para facilitar el acceso a los valores, las
expresiones EL pueden interpretar las claves como si fuesen propiedades del mapa. De esta manera podemos
recuperar el primer valor del mapa con las expresiones:
${secuencia.uno}
${secuencia["dos"]}
Pero cuidado con la siguiente expresión:
${secuencia[uno]}
El resultado sería nulo en este caso, puesto que la expresión EL buscaría un atributo llamado uno en algún
contexto y recuperaría su valor como clave para el mapa secuencia.
Aquí conviene hacer notar que con los arrays no podemos usar la sintaxis de objeto puesto que los índices
numéricos no son nombres válidos de propiedades. De la misma forma, si el mapa tuviese claves numéricas
sólo podríamos usar la sintaxis de array asociativo para acceder a los valores:
${secuencia.1} <%-- ERROR DE SINTAXIS --%>
4.2.4. Acceso a objetos personalizados mediante EL.
Como se ha visto, las expresiones EL permiten recuperar propiedades de objetos. Pero veamos ahora todo lo
que esto implica. Supongamos creada la siguiente clase:
package bean;
import java.util.Date;
public class Persona {
private String nombre;
private Date fecha;
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public Date getFecha() {
return fecha;
}
public void setFecha(Date fechaNacimiento) {
this.fecha = fecha;
}
}
Tal como está definida la clase bean.Persona posee dos propiedades: nombre y fecha.
Y ahora creamos un objeto Persona en un atributo de sesión con el identificador p y asignamos sus
propiedades:
<jsp:useBean id="p" class="bean.Persona" scope="session" />
<jsp:setProperty name="p" property="nombre" value="Pedro" />
<jsp:setProperty name="p" property="fecha" value="<%= new java.util.Date() %>" />
Podemos recuperar el objeto p mediante una expresión EL o mediante una expresión JSP:
${p}
<%= session.getAttribute("p") %>
Obteniendo como resultado en ambos casos que se escriba en la página:
bean.Persona@42211bec
Este resultado es así porque cuando una expresión EL evalúa un objeto escribe lo que devuelve el método
toString() del objeto. Si queremos recuperar el nombre de la persona p podemos rescribir el método toString()
de la clase Persona, o podemos acceder a la propiedad nombre mediante la expresión EL:
${p.nombre}
Pero aún más. Las expresiones EL permiten encadenar invocaciones a propiedades. Por ejemplo, si
recordamos que la clase java.util.Date tiene un método de instancia llamado getTime() podemos utilizar la
siguiente expresión EL para escribir los milisegundos de la fecha.
${p.fecha.time}
En su última especificación, las expresiones EL también permiten invocar los métodos públicos del objeto.
Por ejemplo, podemos asignar las propiedades mediante expresiones EL:
${p.setNombre("Juan")}

151
4.2.5. Expresiones EL que utilizan atributos no existentes.
Las expresiones EL son lo suficientemente flexibles y poderosas para intentar no lanzar excepciones cuando
se utilizan atributos y valores no existentes. Para ilustrar esto supongamos que no existen dos atributos
llamados foo y bar en ningún contexto.
La siguiente tabla muestra el resultado de usar estos dos atributos no existentes:
Expresión EL Resultado Explicación
${null}
${foo}
${foo[bar]} Las expresiones nulas no escriben nada en la salida.
${bar[foo]}
${foo.bar}
${7 + foo} 7
${7 / foo} Infinite En expresiones aritméticas los atributos desconocidos o nulos
${7 - foo} 7 son tratados como cero.
${7 % foo} Lanza una excepción
${7 < foo} false
${7 == foo} false
${foo == foo} true
${7 != foo} true En expresiones lógicas, los atributos desconocidos son
${true and foo} false tratados como false.
${true or foo} true
${not foo} true

4.3. Configuración del archivo descriptor.


Por defecto, desde Tomcat 5, la interpretación del lenguaje EL está habilitada en las páginas JSP. Si queremos
deshabilitar el lenguaje EL o incluso la interpretación de scriptlets, debemos editar el archivo descriptor
web.xml de la aplicación.
En el elemento <jsp-config> del archivo descriptor podemos habilitar o deshabilitar diversas configuraciones
para grupos de páginas. La sintaxis general es la siguiente:
<web-app>
<jsp-config>
<jsp-property-group>
<description>Todas las páginas</description>
<url-pattern>*.jsp</url-pattern>
<page-encoding>ISO-8859-1</page-encoding>
<el-ignored>false</el-ignored>
<scripting-invalid>false</scripting-invalid>
</jsp-property-group>
</jsp-config>
</web-app>
La etiqueta <jsp-property-group> declara un grupo de páginas. La etiqueta <el-ignored> activa (false) o
desactiva (true) la interpretación de las expresiones EL. La etiqueta <scripting-invalid>activa (false) o desactiva
(true) la utilización de bloques scriptlets.
Si se desactiva la interpretación del lenguaje EL con la etiqueta <el-ignored>true</el-ignored>, entonces
cualquier expresión EL insertada directamente en la página será mostrada como un texto literal.
Si se desactiva la interpretación de código scriptlet con la etiqueta <scripting-invalid>false</scripting-invalid>,
cualquier bloque de código java provocará un error en la página.
4.4. Librería Core JSTL.
La librería Core JSTL incluye el grupo de etiquetas que suplen las instrucciones de programación de Java. La
tabla siguiente describe someramente estas etiquetas:
Etiqueta Descripción
c:if Implementa una estructura selectiva. Si se cumple la condición asignada en el atributo test se
evalúa el cuerpo, sino no se avalúa.
choose Implementa una estructura selectiva similar a la estructura switch de Java.

152
c:catch Captura las excepciones producidas en el cuerpo. El valor de la excepción es asignado en el
atributo opcional var, el cual define un atributo del ámbito de página.
forEach Implementa un bucle para iterar sobre un rango o una colección de elementos.
forTokens Implementa un bucle para iterar sobre un texto con caracteres delimitadores.
set Crea o modifica una variable en el ámbito especificado (por defecto a nivel de página) con un
valor especificado.
remove Elimina una variable especificada en el nivel indicado.
import Recuperar el contenido de un recurso.
out Expone el resultado de evaluar una expresión sobre la página.
redirect Redirige la respuesta de la página a una Url especificada.
url Normaliza una Url.
4.4.1. Acciones de selección.
La acción <c:if> nos permite incluir, o procesar, condicionalmente una parte de una página, dependiendo de
la información durante la ejecución. El siguiente ejemplo incluye un saludo personal si el usuario es un
visitante anterior, según lo indica la presencia de una cookie con el nombre del usuario:
<c:if test="${!empty cookie.nombreUsuario}">
Bienvenido otra vez ${cookie.nombreUsuario.value}"
</c:if>
El valor del atributo test es una expresión EL que verifica si la cookie existe. El operador empty combinado
con el operador "not" (!) significa que evalúa a true si la cookie no existe, haciendo que el cuerpo del
elemento sea procesado.
Aunque la acción c:if es similar a la estructura if de Java, no posee una parte opcional else. La acción
<c:choose>permite agrupar una o más acciones <c:when>, cada una de las cuales permite especificar una
condición booleana diferente. Las acción<c:choose> verifica cada condición en orden y sólo se ejecutará el
cuerpo de la primera acción <c:when>cuya condición se evalúe a true. El cuerpo de<c:choose> también
puede contener una acción<c:otherwise>, cuyo cuerpo sólo se procesará si ninguna de las condiciones de los
<c:when> es true.
Por ejemplo, el siguiente código evalúa el valor de un parámetro de solicitud para determinar a qué página
redirigir.
<c:choose>
<c:when test='${param.dir eq "a"}'>
<jsp:forward page="paginaA.jsp" />
</c:when>
<c:when test='${param.dir eq "b"}'>
<jsp:forward page="paginaB.jsp" />
</c:when>
<c:otherwise>
<jsp:forward page="otraPagina.jsp" />
</c:otherwise>
</c:choose>
Como se puede ver, la estructura de c:when es similar a una estructura switch.
4.4.2. Captura de excepciones.
Para capturar excepciones se utiliza la acción <c:catch />. Posee la siguiente sintaxis:
<c:catch var="error">
<%-- código que pueda lanzar una excepción --%>
</c:catch>
Donde el atributo var indica el nombre de una variable de tipo Throwable, que será creada en el ámbito de
página y que contendrá la excepción generada en el cuerpo de la etiqueta.
Después de la etiqueta <c:catch /> podemos evaluar el valor de la variable error mediante una expresión EL.
Como ejemplo supongamos una página que recupera un parámetro de solicitud llamado contador, el cual
debe convertir a un entero. Si no existe el parámetro o no es convertible se mostrará un mensaje:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

153
<title>JSP Page</title>
</head>
<body>
<c:catch var="ex">
<% int contador = Integer.parseInt(request.getParameter("contador"));%>
El valor de contador es <%= contador %>
</c:catch>
<c:if test="${!empty ex}">
Falta el contador o no es convertible
</c:if>
</body>
</html>
4.4.3. Visualizar valores de expresiones.
Para visualizar los valores de una expresión se utiliza la acción <c:out />, como en el siguiente ejemplo:
Hay <c:out value="${producto.stockActual}" escapeXml="true" default="0" /> productos en el stock.
Donde el atributo value especifica la expresión a visualizar, el atributo escapeXml indica si hay que aplicar
códigos de escape a los caracteres <, >, & y . (punto), y el atributo default indica un valor por defecto si la
expresión no puede evaluarse.
4.4.4. Crear, modificar y eliminar variables de ámbito.
Para crear una variable en uno de los contextos (request, page, session o application) se usa la acción <c:set />:
<c:set var="idCliente" value="${param.numeroCliente}" scope="session" />
Donde el atributo var indica el nombre de la variable a crear o modificar, el atributo value especifica una
expresión que evalúa al valor que se quiere asignar, y el atributo scope indica el contexto en el que se define la
variable.
También se puede usar <c:set /> para asignar el valor de la propiedad de un objeto, de forma parecida a
como lo hace la acción <jsp:setProperty />:
<jsp:useBean id="persona1" class="bean.Persona" />
<%-- Asignamos la propiedad "nombre"--%>
<c:set target="${persona1}" property="nombre" value="Juan Pérez" />
En este caso, el atributo target establece el objeto que queremos modificar, y el atributo property establece
una propiedad del objeto especificado en target.
Si el objeto especificado en target es de tipo java.util.Map, el atributo property especificará una clave para el
mapa, y el atributo value el valor asociado a dicha clave.
<%-- Creo un objeto de tipo HashMap y le añado un elemento con la clave "uno" y el valor 1 --%>
<jsp:useBean id="mapa1" class="java.util.HashMap" />
<c:set target="${mapa1}" property="uno" value="1" />
También podemos asignar el cuerpo de la etiqueta<c:set> a una variable. El siguiente código crea una
variable en el ámbito de página llamado contenidoCelda con el contenido del cuerpo de la etiqueta <c:set />.
<c:set var="contenidoCelda">
<td>
<c:out value="${miCelda}"/>
</td>
</c:set>
Para eliminar una variable de un ámbito se utiliza la acción <c:remove />:
<c:remove var="unaVariable">
4.4.5. Bucle para colecciones.
Se utiliza la acción c:forEach para recorrer cualquier tipo de colección de datos o un rango de valores.
El siguiente código permite iterar sobre un rango de valores entre 1 y 10:
<c:forEach begin="1" end="10" var="i">
<p>Iteración ${i}</p>
</c:forEach>
En el atributo var se declara un nombre de variable que estará disponible dentro del cuerpo de la etiqueta, y
que en cada iteración tomará valores desde el valor inicial establecido en el atributo begin hasta el valor final
establecido en el atributo end.
Pero la gran potencia de esta etiqueta es su capacidad para iterar sobre cualquier tipo de colección. El
siguiente fragmento itera sobre la colección de cabeceras de la solicitud:
<c:forEach var="cabecera" items="${header}">
<p>${cabecera.key} = ${cabecera.value}</p>

154
</c:forEach>
La expresión EL para el valor header obtiene el mapa de cabeceras. Cada elemento de un mapa es un objeto
de tipo java.util.Map.Entry, el cual posee las propiedades key y value.
Para ilustrar todas las posibilidades que ofrece esta acción, extenderemos el ejemplo de iteración para
procesar sólo un conjunto de cabeceras por cada página solicitada, añadiendo enlaces "Anterior" y "Siguiente"
en la misma página.
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:set var="lineas" value="4" />
<ul>
<c:forEach var="cabecera" items="${header}"
begin="${param.inicial}" end="${param.inicial + lineas - 1}" step="1">
<li>${cabecera.key} = ${cabecera.value}</li>
</c:forEach>
</ul>
<a href="?inicial=${param.inicial-lineas}">Anterior</a>
<a href="?inicial=${param.inicial+lineas}">Siguiente</a>
</body>
</html>
La acción <c:set>define la variable lineas para indicar cuántas cabeceas se visualizarán por página. El
<c:forEach> en este ejemplo toma los mismos valores para los atributos items y var como antes, pero hemos
añadido dos nuevos atributos:
• E atributo begin toma el índice (base 0) del primer elemento de la colección a procesar. Aquí se selecciona
al valor de un parámetro de solicitud llamadoinicial. Para la primera solicitud, este parámetro no está
disponible, por eso la expresión se evaluará a 0; en otras palabras, la primera fila.
• El atributo end especifica el índice del último elemento de la colección a procesar. Aquí lo hemos
seleccionado al valor del parámetro inicial más lineas menos uno. Para la primera solicitud, cuando no existe
el parámetro de la solicitud, este resultado es 3, por eso la acción iterará la primera vez sobre los índices del
0 al 3.
• El atributo step especifica el paso de iteración. Un valor mayor que 1 hará que el bucle se salte elementos
de la colección.
Luego añadimos los enlaces "Anterior" y "Siguiente", donde realmente se define el parámetro de solicitud
inicial, incrementándolo y decrementándolo apropiadamente.
Si probamos esta página se verá que realmente página las cabeceras. Pero si en la primera página pulsamos el
enlace "Anterior" se producirá un error. Esto es porque el atributo begin no admite valores negativos. Para
evitar esto podemos condicionar la aparición de este enlace:
<c:if test="${param.inicial > 0}">
<a href="?inicial=${param.inicial-lineas}">Anterior</a>
</c:if>
Si avanzamos más allá de la última página de cabeceras se mostrará una página vacía pero no se producirá
error. Aún así condicionaremos también la aparición de este enlace teniendo en cuenta el tamaño de la
colección:
<c:if test="${param.inicial + lineas < header.size()}">
<a href="?inicial=${param.inicial+lineas}">Siguiente</a>
</c:if>
4.4.6. Iteración sobre textos.
Al igual que al clase StringTokenizer, la acción <c:forTokens /> permite iterar sobre trozos de un string
delimitados por algún carácter especificado. En siguiente ejemplo muestra el uso de esta etiqueta:
<c:forTokens items="uno-dos-tres-cuatro" delims="-" var="token">
${token}
</c:forTokens>

155
Al igual que la etiqueta c:forEach, c:forTokens también posee atributos begin, end y step.
4.4.7. Procesar URLs (Reescritura URL).
La acción <c:url /> permite normalizar una URL relativa del sitio web convirtiéndola en una URL absoluta
respecto a la carpeta raíz, si empiezan con una barra inclinada. Esto es interesante para no tener problemas al
realizar redirecciones externas.
Además esta acción ofrece una funcionalidad más interesante aún. La url normalizada aplica reescritura URL
para asegurar el mantenimiento de las sesiones aunque están desactivadas las cookies en el navegador cliente.
Como ejemplo de aplicación modificaremos los enlaces "Anterior" y "Siguiente" del ejemplo previo:
<c:if test="${param.inicial > 0}">
<c:url var="urlAnterior" value="">
<c:param name="inicial" value="${param.inicial-lineas}" />
</c:url>
<a href="${urlAnterior}">Anterior</a>
</c:if>
<c:if test="${param.inicial + lineas < header.size()}">
<c:url var="urlSiguiente" value="">
<c:param name="inicial" value="${param.inicial+lineas}" />
</c:url>
<a href="${urlSiguiente}">Siguiente</a>
</c:if>
La acción <c:url> soporta un atributo var, usado para especificar la variable que contendrá la URL codificada,
y un atributo value para contener la URL a codificar. Si no se especifica el atributo var, la URL codificada es
renderizada en la salida.
Se pueden especificar parámetros string para solicitar la URL usando acciones <c:param>. Los caracteres
especiales en los parámetros especificados por elementos anidados son codificados (si es necesario) y luego
añadidos a la URL como parámetros string de la consulta. El resultado final se pasa a través del proceso de
reescritura de URL, añadiendo un ID de sesión si está desactivado el seguimiento de sesión usando cookies.
4.4.8. Redirecciones externas.
La acción <c:redirect> permite redirigir externamente a otro recurso en la misma aplicación Web, en otra
aplicación Web o en un servidor diferente, aplicando reescritura URL sobre la url. Por ejemplo, podemos
evaluar una variable de contexto para redirigir a una u otra página:
<if test="${condicion}">
<c:redirect url="/pagina1.jsp" />
</c:if>
<c:redirect url="/pagina2.jsp" />
La acción c:redirect es equivalente a response.sendRedirect(encodeRedirectURL(url)).
4.4.9. Importar y ejecutar recursos.
La acción <c:import> es una acción más flexible que la acción estándar <jsp:include>. Podemos usarla para
incluir contenido desde recursos dentro de la misma aplicación Web, desde otras aplicaciones Web en el
mismo contenedor, o desde otros servidores, usando protocolos como HTTP y FTP.
Por ejemplo, supongamos definido el siguiente servlet:
package servlets;
import java.io.*;
import java.text.DateFormat;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.*;
@WebServlet(name = "FechaActual", urlPatterns = {"/fechaactual"})
public class FechaActual extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(DateFormat.getDateInstance().format(new Date()));
}
}
}

156
El servlet FechaActual simplemente responde escribiendo la fecha actual en el canal de salida. Este servlet
tiene asociado como patrón URL el recurso "fechaactual".
Ahora, en un JSP, podemos utilizar la etiqueta <c:import /> sobre este servlet de dos maneras:
<c:import url="fechaactual" /> <%-- se escribe directamente la fecha --%>
<br />
<c:import url=" fechaactual " var="fecha" /> <%-- se guarda la fecha en una variable --%>
${fecha} <%-- y se muestra --%>
Como resultado de la ejecución de este JSP se genera una página HTML que muestra en dos líneas distintas la
fecha actual.
Si queremos mostrar el contenido de un archivo de texto dentro de una página JSP podemos utilizar también
<c:import />.
Contenido del archivo de texto: <br />
<c:import url="un_archivo.txt" />
La etiqueta <c:import /> también admite el paso de parámetro con la etiqueta anidada <c:param />. Por
ejemplo:
<c:import url="fechaactual" />
<c:param name="nombre del parámetro" value="valor del parámetro" />
</c:import>
Los parámetros son concatenados a la URL del recurso importado como parámetros de solicitud GET, y
pueden ser recuperados desde un servlet con el método HttpRequest.getParameter().
4.5. Librería Formatting.
La librería fmt incluye un conjunto de acciones para simplificar la internacionalización, principalmente cuando
páginas compartidas se usan para varios idiomas. La tabla siguiente describe someramente sus etiquetas:
Etiqueta Descripción
bundle Carga un grupo de recursos de idiomas para ser utilizado por las demás etiquetas que
contenga en su cuerpo. El atributo basename debe contener el nombre común del grupo
de recursos de idioma.
formatDate Da formato a un valor de fecha de acuerdo a la cultura establecida. El formato de fecha
puede ser establecido mediante los atributos opcionales dateStyle, pattern, y timeStyle.
formatNumber Da formato a un número de acuerdo a la cultura establecida. El formato de número
puede ser establecido mediante los atributos opcionales currencyCode, groupingUsed,
currencySymbol, maxFractionDigits, maxIntegerDigits, minFractionDigits, minIntegerDigits, y
pattern.
message Recupera el valor asociado a una clave del archivo de recursos de idioma para el idioma
establecido.
parseDate Similar a formatDate para extraer el valor de fecha de un string.
parseNumber Similar a formatNumber para extraer el valor numérico de un string.
requestEncoding Establece la codificación de caracteres por defecto.
setBundle Estable un grupo de archivos de recursos de idioma para ser utilizado por etiquetas
posteriores.
setLocale Estable el idioma y/o región por defecto.
setTimeZone Establece la zona horaria.
timeZone Determina la zona horaria dentro del cuerpo de la etiqueta.
4.5.1. Internacionalización y formateo.
Los grandes sitios web normalmente necesitan complacer a los visitantes de todo el mundo, y servir el
contenido sólo en un idioma no es suficiente. Para desarrollar un sitio que proporcione una elección de
idiomas, tenemos dos opciones:
• Escribir un conjunto de páginas para cada idioma.
• Escribir un conjunto compartido de páginas que ponga el contenido en diferentes idiomas desde fuentes
externas.
Frecuentemente terminaremos con una mezcla de estas técnicas, usar páginas separadas para la mayoría de las
grandes cantidades de contenido estático y páginas compartidas cuando la cantidad de contenido sea pequeña
pero dinámica (por ejemplo, una página con unas pocas etiquetas fijas mostradas en diferentes idiomas y
todos los demás datos que vengan desde una base de datos).

157
Preparar una aplicación para varios idiomas se llama internacionalización (comúnmente abreviado i18n ) y
hacer que el contenido esté disponible para un idioma específico se llama localización (o l10n ). Para hacer
esto necesitamos considerar otras cosas además del idioma: cómo se formatean las fechas y números entre los
diferentes países, e incluso dentro de los países. También podríamos necesitar adaptar los colores, las
imágenes y otro contenido no textual. El término localidad se refiere a un conjunto de reglas y contenido
aceptable para una región o cultura.
4.5.2. Formateo de fechas y números sensible a la localidad.
Primero veamos cómo formatear apropiadamente fechas y números. Este ejemplo formatea la fecha actual y
un número basándose en las reglas de la localidad por defecto:
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<h1>Formateando con la localidad por defecto</h1>
<jsp:useBean id="now" class="java.util.Date" />
Fecha: <fmt:formatDate value="${now}" dateStyle="full" />
Numero: <fmt:formatNumber value="${now.time}" />
</body>
</html>
La primera línea es la directiva taglib para la librería JSTL que contiene las acciones de formateo e
internacionalización. El prefijo por defecto, usado aquí, es fmt. Para obtener un valor a formatear, se crea un
objeto java.util.Date que representa la fecha y hora actuales, y lo graba como una variable llamada now.
La acción JSTL <fmt:formatDate> formatea el valor de la variable now asignado al valor del atributo value
usando el tipo de expresión EL. El atributo dateStyle le dice a la acción cómo debe formatear la fecha.
Podemos usar cualquiera de los valores default, short, medium, long, o full para el atributo dateStyle. El tipo de
formateo que representa cada uno de estos tipos depende exactamente de la localidad. Para la localidad
English, full resulta en un string como "Thursday, August 29, 2002". En este ejemplo sólo hemos formateado la
parte de la fecha, pero también podemos incluir la parte de la hora (o formatear sólo la hora), y definir reglas
de formateo personalizadas tanto para la fecha como para la hora en lugar de los estilos dependientes de la
localidad:
<fmt:formatDate value="${now}" type="both" pattern="EEEE, dd MMMM yyyy, HH:mm" />
El atributo pattern toma un patrón de formateo personalizado del mismo tipo que la clase
java.text.SimpleDateFormat. El patrón usado aquí resulta en "Thursday, 29 August 2002, 17:29" con la localidad
English.
Para escanear un string y obtener la fecha que contiene se usa la acción parseDate:
<fmt:parseDate value="${stringConFecha}" pattern="MM dd, YYYY" var="parsedDate" />
Esta acción extrae la fecha contenida en una variable stringConFecha según el patrón especificado y la asigna
en una variable denominada parsedDate.
La acción <fmt:formatNumber> soporta atributos similares para especificar cómo formatear un número
usando estilos dependientes de la localidad para números normales, valores de moneda, o un porcentaje, así
como usar patrones personalizados de diferentes tipos. Por ejemplo:
<fmt:formatNumber value="1000.001" pattern="#,#00.0#"/>
Provoca que el número se muestre como 1,000.00 para la localidad English.
De forma análoga a la acción parseDate, si queremos escanear un número a partir de un string usaremos la
acción parseNumber del siguiente modo:
<fmt:parseNumber value="${stringConNumero}" type="currency" var="parsedNumber"/>
4.5.3. Usar JSTL para seleccionar la localidad.
Volviendo a la cuestión principal: ¿cómo se determina la localidad para formatear las fechas y los números? Si
usamos las acciones JSTL exactamente como en este ejemplo, sin hacer nada más, el formateo de localidad se
determina comparando las localidades especificadas en un cabecera de solicitud llamada Accept-Language con
las localidades soportadas por el entorno de ejecución Java.

158
La cabecera, si está presente, contiene una o más especificaciones de localidades (en la forma de código de
idioma y posiblemente un código de país), con información sobre su prioridad relativa. El usuario utiliza las
configuraciones del navegador para definir qué especificaciones de localidades enviar. Las localidades de la
solicitud se comparan en orden de prioridad con las ofrecidas por la máquina virtual Java, y se selecciona la
que mejor coincida.
Si no se encuentra una correspondencia, la acción de formateo busca la llamada selección de configuración de
la localidad predefinida. Una selección de configuración es un valor seleccionado por un parámetro de
contexto en el fichero web.xml de la aplicación o por una acción JSTL o una sentencia Java en uno de los
ámbitos JSP. Para seleccionar esta localidad en el fichero web.xml, incluiremos estos elementos:
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.fallbackLocale</param-name>
<param-value>de</param-value>
</context-param>
Con esta selección, se usará la localidad German (especificada por el código de idioma "de" como valor de
parámetro) si ninguna de las localidades especificadas por la solicitud está soportada por el entorno Java.
JSTL también nos permite seleccionar una localidad por defecto para la aplicación que podremos sobrescribir
cuando lo necesitemos. Esto nos da control total sobre la localidad a utilizar, en lugar de dejarlo a la
configuración del navegador del visitante. La localidad por defecto también es una selección de configuración.
Se puede especificar con un parámetro de contexto en el fichero web.xml de esta forma:
<context-param>
<param-name>javax.servlet.jsp.jstl.fmt.locale</param-name>
<param-value>en</param-value>
</context-param>
Esta selección establece English como la localidad por defecto, resultando que en la anterior página JSP
siempre formateará la fecha y los números de acuerdo a las reglas inglesas.
Para sobrescribir la configuración de la localidad por defecto, podemos usar la acción <fmt:setLocale>, que
selecciona la localidad por defeco dentro de un ámbito JSP específico. Aquí tenemos un ejemplo que
selecciona la localidad por defecto del ámbito de la página, basándose en una cookie que sigue la pista de la
localidad seleccionada por el usuario en una visita anterior:
<h1>Formateando con una localidad puesta por setLocale</h1>
<c:if test="${!empty cookie.preferredLocale}">
<fmt:setLocale value="${cookie.preferredLocale.value}" />
</c:if>
<jsp:useBean id="now" class="java.util.Date" />
Fecha: <fmt:formatDate value="${now}" dateStyle="full" />
Número: <fmt:formatNumber value="${now.time}" />
Aquí, primero hemos usado la acción JSTL <c:if> para comprobar si con la solicitud se ha recibido una
cookie llamada preferredLocale. Si es así, la acción <fmt:setLocale> sobrescribe la localidad para la página
actual seleccionando la variable de configuración de la localidad en el ámbito de la página. Si lo queremos,
podemos seleccionar la localidad para otro ámbito usando el atributo scope. Finalmente, se formatean la fecha
y el número de acuerdo a las reglas de la localidad especificada por la cookie, o la localidad por defecto, si la
cookie no está presente.
4.5.4. Generar texto localizado.
Aunque formatear fechas y número es importante cuando se localiza una aplicación, el contenido de texto es,
por supuesto, incluso más importante. JSTL está basado en Java, por eso trata con el soporte genérico de i18n
de la plataforma Java. Cuando viene con texto, este soporte está basado en lo que se llama un paquete de
recursos. En su forma más simple, un paquete de recursos está representado por un fichero de texto que
contiene claves y un valor de texto para cada clave. Este ejemplo muestra un fichero con dos claves (hello y
goodbye) y sus valores:
Archivo «labels.properties»
hello=Hola
goodbye=Adiós
Las distintas localidades se soportan creando ficheros separados para cada una de ellas, con un nombre de
fichero que incluye el nombre de la localidad. Por ejemplo, si el fichero del paquete de recursos anterior
representa la localidad Español, debería almacenarse en un fichero llamado labels_es.properties. El paquete de

159
recursos para la localidad Swedish sería almacenado en un fichero llamado labels_sv.properties, donde sv es el
código de idioma. La parte fija del nombre de fichero, labels de este ejemplo, se llama el nombre base del
paquete de recursos, y se combina con una especificación de localidad (como un código de idioma) para
encontrar el paquete de recursos para una localidad específica. Todos los paquetes de recursos incluyen las
mismas claves; sólo difiere el texto dependiendo de la localidad. Los ficheros deben situarse en un directorio
que sea parte del classpath del contenedor, normalmente el directorio WEB-INF/classes de la aplicación.
La acción JSTL que añade texto desde un paquete de recursos a una página es <fmt:message>. El paquete a
utilizar puede especificarse de varias formas. Una es anidar las acciones <fmt:message> dentro del cuerpo de
una acción <fmt:bundle>:
<fmt:bundle basename="labels">
<fmt:message key="hello" />y<fmt:message key="goodbye" />
</fmt:bundle>
En este caso, la acción <fmt:bundle> localiza el paquete de la localidad que es la correspondencia más cercana
entre la selección de configuración de la localidad (o las localidades en el cabecera Accept-Language, si no hay
localidad por defecto) y los paquetes de recursos disponibles para el nombre base especificado. Las acciones
anidadas obtienen el texto desde el paquete por la clave asociada.
Al igual que con las acciones de formateo, podemos establecer un paquete por defecto, para toda la
aplicación, con un parámetro de contexto o con la acción <fmt:setBundle> o la clase Config para un ámbito
JSP específico:
<fmt:setBundle basename="labels"/>
……………….
<fmt:message key="hello" />y<fmt:message key="goodbye" />
Después de que se haya definido un paquete por defecto, podemos usar acciones <fmt:message>
independientes dentro del ámbito donde se estableció el valor por defecto.

PRÁCTICA
01. Crea una página JSP que tenga un contenido parecido al siguiente:

La lista desplegable permitirá elegir un tipo de usuario. Cuando se seleccione un usuario y se pulse el botón
[Actualizar] se debe almacenar en una cookie el tipo se usuario seleccionado y se debe recargar la página. El
usuario Todos debe poder visualizar las tres líneas, el usuario Directivo debe ver sólo la primera línea, el
usuario Coordinador sólo la segunda línea, y el usuario Subordinado sólo la tercera línea.
La primera vez que se cargue la página debe recuperarse el tipo de usuario de la cookie, si existe, sino se
establecerá por defecto el tipo Todos.
La cookie no debe existir durante más de 1 día.

02. Crea una página JSP llamada "inicio.jsp" que tenga un contenido parecido al siguiente:

La página permitirá un tipo de dato, introducir un valor del tipo seleccionado y seleccionar una operación.
Cuando se pulse el botón [Realizar operación] deberá invocarse otra página "procesa.jsp" que calculará la
solución. Utiliza un JavaBean para postear los datos y capturarlos en la página "procesa.jsp".

160
La página de proceso deberá gestionar errores si el valor o la operación no se corresponden con el tipo de
dato. Para ello hay que crear otra página llamada "error.jsp" que debe mostrar un mensaje correspondiente
al tipo de error producido y un enlace para retornar a la página inicial.
Aplica un mecanismo que funcione de la siguiente manera: cuando la página "procesa.jsp"detecte un error
en los datos deberá lanzar una excepción. La excepción generada debe provocar la carga inmediata de la
página "error.jsp". Si no hay error debe redirigirse a la página inicial pasándole el resultado como un atributo
de solicitud.

03. Crea un sitio web que simule un asistente de dos pasos para seleccionar un producto. El sitio mostrará
una página para cada paso con la opción de visualizar una ayuda.

El sitio Web constará de las siguientes páginas JSP:


Ayuda.jsp: Contendrá las instrucciones de ayuda.

161
Paso1.jsp: Contera el asistente del paso 1.
Paso2.jsp: Contera el asistente del paso 2.
Las páginas Paso1.jsp y Paso2.jsp contienen un cuadro de verificación (checkbox) que cuando esté marcado
deberá embeber el contenido de la página Ayuda.jsp. Para conseguir esto, al pulsar sobre el checkbox deberá
refrescarse la página para mostrar u ocultar la ayuda. Embebe el contenido de la página de ayuda usando
acciones JSP.
Como ves en las imágenes no hay un botón de tipo "submit" para provocar el refresco de la página. Se puede
gestionar el cambio de estado del checkbox para provocar un submit desde código script.
Los enlaces siguiente» y «anterior en las páginas permitirán navegar a la página siguiente o anterior. El estado
del checkbox deberá mantenerse entre páginas. Esto quiere decir que si en la primera página el checkbox está
marcado (y por tanto aparece la ayuda), al navegar a la segunda página su checkbox también deberá estar
marcado (y se mantendrá por tanto la ayuda). Esto implica usar otro tipo de variable para controlar el estado
del checkbox, puesto que la variable local usada en el ejemplo previo no pervive entre llamadas a páginas.
El enlace finalizar no es necesario que navegue hasta ninguna otra página.

162
UNIDAD 15. ETIQUETAS PERSONALIZADAS
1. Fundamentos de librerías de etiquetas
En esta unidad veremos cómo crear nuestras propias librerías de etiquetas o acciones personalizadas, al estilo
de las librerías JSTL.
Se recomienda crear librerías de etiquetas para encapsular la lógica del negocio de nuestra aplicación, y poder
invocarla desde las páginas JSP mediante la sintaxis de acciones.
1.1. Conceptos básicos sobre etiquetas.
La sintaxis para crear etiquetas personalizadas es similar a la utilizada en las acciones JSP y sigue el formato
XML. El código que tendrá que ser ejecutado cuando se utilice la etiqueta podrá ser escrito como código Java
o como código JSP.
Existen dos formas de crear etiquetas personalizadas:
1) Creando una clase manejadora de la etiqueta (Tag Handler). Esta clase debe implementar la interfaz
SimpleTag, Tag, IterationTag, o BodyTag del paquete javax.servlet.jsp.tagext. Estas interfaces proporcionan
unos métodos que determinan el comportamiento de la etiqueta.
2) Creando un fichero de tag (Tag File). El fichero de tag es un archivo que permite escribir el código de la
etiqueta mediante acciones JSP.
Podemos agrupar las etiquetas en librerías de etiquetas. Esto es obligado en el caso de los Tag Handler y
opcional en el caso de los Tag File. De esta forma podemos usar la sintaxis <lib:act />, donde lib identifica la
librería y act la etiqueta.
Para definir la librería se utiliza un archivo descriptor de librería (TLD), donde se asocia cada etiqueta de
tipo Tag Handler con la clase Java que la implementa, y cada etiqueta de tipo Tag File con su ubicación.
En las páginas JSP, antes de usar las etiquetas, debemos referenciar las librerías mediante una URI en una
directiva <%@ taglib %>.
1.2. Declaración de las librerías de etiquetas en las páginas JSP.
Para usar una librería de etiquetas TLD en una página JSP debemos declararla con la siguiente directiva:
<%@ taglib prefix="test" uri="simpleLib" %>
Donde el atributo uri (con valor "simpleLib"), identifica al archivo descriptor (siempre deben tener extensión
tld) y test es el prefijo con el cual serán llamadas las etiquetas de esa librería.
Si queremos usar sintaxis XML, esta declaración debemos incluirla en el elemento <jsp:root>:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:test="simpleLib" version="1.2" >
... CONTENIDO DE LA PÁGINA ...
</jsp:root>
Si además queremos forzar que se genere contenido XHTML deberemos usar la versión 2.0 de la siguiente
forma:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:test="simpleLib" version="2.0" >
... CONTENIDO DE LA PÁGINA ...
</jsp:root>
Para usar una librería de etiquetas de tipo Tag File en una página JSP debemos declararla con la siguiente
directiva.
<%@ taglib prefix="test" tagdir="/WEB-INF/tags" %>
Donde el atributo tagdir indica la carpeta donde están ubicados los tag files que forman parte de la librería.
1.3. El archivo descriptor TLD.
Este archivo de tipo XML contiene toda la información referente a una librería de etiquetas. Tiene el
siguiente formato:
<?xml version="1.0" encoding="ISO-8859-1">
<!DOCTYPE taglib PUBLIC"-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
"http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd" >
<taglib>
<tlib-version>1.0</tlib-version> <!-- versión de la librería -->
<jsp-version>1.2</jsp-version> <!-- versión JSP -->
<short-name>test</short-name> <!-- el prefijo que usa esta librería -->
<uri> simpleLib</uri> <!-- uri para identificar la librería -->

163
<!-- Parte de declaración de etiquetas -->
<tag>
<name>etiqueta1</name> <!-- el nombre de la etiqueta -->
<tag-class>sampleLib.Etiqueta1Tag</tag-class> <!-- la clase manejadora de la etiqueta -->
<body-content>empty</body-content> <!-- tipo de contenido del cuerpo -->
<description></description> <!-- la descripción de la etiqueta -->
<attribute> <!-- atributos de la la etiqueta -->
<name>user</name>
<required>false</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
<tag>
....................
</tag>
</taglib>
Este fichero se divide en dos partes. La primera parte establece las propiedades generales de la librería, donde
hay que destacar la uri de la librería y su nombre corto.
Las etiquetas que se utilizan en esta parte son:
Etiqueta Significado
tlib-version Especifica la versión de la librería.
jsp-version Especifica la versión mínima del motor JSP que es compatible con la librería.
short-name Especifica el prefijo que usará esta librería en las páginas JSP.
uri Especifica la URI que identificará a esta librería de forma unívoca.
display-name Especifica un nombre que puede ser obtenido por las utilidades de autoría.
small-icon Especifica la ruta de un icono pequeño que puede ser usado por utilidades.
large-icon Especifica la ruta de un icono grande que puede ser usado por utilidades.
description Contendrá información adicional sobre la utilidad de la librería.
validator Especifica una clase validadora de tipo TagLibraryValidator, la cual podemos usar para
validar que las etiquetas son usadas correctamente.
listener Especifica una clase oyente de tipo ServletContextListenerque será registrada en el
contenedor Web y que podemos usar para realizar inicializaciones.
tag Describe una etiqueta de la librería.
La segunda parte de la librería describe cada etiqueta mediante el atributo tag, usando las siguientes sub-
etiquetas:
Etiqueta Significado
name Especifica el nombre de la etiqueta que será usado en las páginas JSP.
tag-class Especifica la clase Java manejadora en la que se encuentra la funcionalidad de la etiqueta.
tei-class Especifica una subclase opcional de javax.servlet.jsp.tagext.TagExtraInfo. Se usa si la
etiqueta necesita definir alguna variable o una clase de ayuda para realizar validaciones en
tiempo de ejecución.
body-content Indica el tipo de contenido del cuerpo de la etiqueta (si lo tiene). Y puede tomar tres
valores: empty (cuando no hay nada), JSP (si contiene código JSP o incluso otra etiqueta)
o tagdependent (indica que el cuerpo debe ser interpretado por la propia etiqueta). El
valor por defecto es JSP.
display-name Establece un nombre que puede ser obtenido por las utilidades de autoría.
description Establece la descripción de la etiqueta.
small-icon Establece la ruta de un icono pequeño que puede ser usado por utilidades.
large-icon Establece la ruta de un icono grande que puede ser usado por utilidades.
variable Especifica la información de una variable de script.
attribute Describe un atributo que la etiqueta puede aceptar.
example Especifica información opcional sobre un ejemplo de uso de la etiqueta.
La descripción de cada atributo contiene la siguiente información:
Etiqueta Significado

164
name Establece el nombre del atributo.
required Puede ser true, false, yes o no para indicar si el atributo es obligatorio u opcional.
rtexprvalue Establece un valor para especificar cuándo el atributo puede aceptar expresiones JSP y
EL que serán evaluadas en tiempo de respuesta. Puede ser true, false, yes o no.
type Establece el tipo de dato del atributo (cuando rtexprvalue es true). El valor por defecto
java.lang.String.
description Establece una descripción del atributo.
Especial atención merece el atributo rtexprvalue. Si este atributo está asignado a true, cualquier expresión JSP
o EL que contenga el atributo será evaluada previamente antes de que el atributo quede asignado a un valor
durante la ejecución de la etiqueta. Si este atributo está asignado a false, cualquier expresión JSP o El será
interpretada como un literal de texto.
1.4. Reconocimiento de librerías de etiquetas por parte del contenedor.
Para poder usar las etiquetas de una librería de etiquetas en una página JSP, el contenedor JSP del servidor
Web debe ser capaz de encontrar la librería a partir de su URI. El motor JSP busca librerías y analiza su
contenido en ubicaciones concretas del sitio web. Estas ubicaciones vienen dadas de la siguiente manera:
1) El archivo TLD está ubicado dentro de una subcarpeta de la carpeta WEB-INF. Por ejemplo, si tenemos la
siguiente estructura en nuestro sitio Web:
Figura 1

Tanto Libreria2.tld como Libreria3.tld son reconocidos como librerías de etiquetas; sin embargo, Libreria1.tld
no es reconocido como librería de etiquetas.
2) El archivo TLD está dentro de un archivo de tipo JAR dentro de la carpeta META-INF. (Ésta es la técnica
que utilizan las librerías de etiquetas de JSTL.) Por ejemplo, si tenemos la siguiente estructura dentro de un
archivo comprimido MisEtiquetas.jar:
Figura 2

Libreria1.tldes reconocido como una librería de etiquetas, pero Libreria2.tld no lo es.


3) Si el archivo TLD no está ubicado en los lugares indicados en las maneras previas, debe estar registrada
en el archivo descriptor. Cada URI de una librería de etiquetas debe ser declarada en el archivo web.xml de
la siguiente forma:
<web-app>
<jsp-config>
<taglib>
<taglib-uri>simpleLib</taglib-uri>
<taglib-location>/META-INF/simpleLib.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
En el archivo web.xml se identifica una librería por su URI y se indica su ubicación física respecto a la
carpeta raíz de la aplicación web.

165
4) Si trabajamos directamente con tag files sin usar un TLD, la carpeta donde están contenidos los tag files
es reconocida como una librería de etiquetas si está ubicada dentro de la carpeta WEB-INF del sitio Web. Por
ejemplo, si tenemos la siguiente estructura en nuestro sitio Web:
Figura 3

La carpeta tags es reconocida como una librería que contiene una etiqueta identificada por etiqueta1, y la
carpeta test es reconocida como una librería que contiene una etiqueta identificada por etiqueta2.
1.5. Primer ejemplo de una librería de etiquetas.
Usaremos el entorno de desarrollo NetBeans para crear un ejemplo de librería de etiquetas. Empezaremos
creando un proyecto web vacío y abriendo el asistente para añadir un nuevo fichero:
Figura 4

En la categoría «Web» debemos seleccionar la plantilla «Tag Library Descriptor». Tras pulsar el botón
«Siguiente» debemos configurar los datos de la nueva librería.

166
Figura 5

Como nombre de la librería pondremos LibreriaTest. Por defecto las nuevas librerías se ubican en la carpeta
/WEB-INF/tlds; esta carpeta es una de las rutas donde el motor JSP busca librerías, así que la mantendremos.
Cada librería debe tener asociada una URI única, para este ejemplo hemos puesto /com.personal.test, y un
prefijo, que hemos dejado con test.
Tras pulsar el botón «Finalizar» se crea la nueva librería.
Figura 6

En el fichero LibreriaTest.tld podemos rectificar cualquier dato que nos interese. Y ahora la librería está
preparada para que podamos agregarle etiquetas personalizadas.

167
2. Etiquetas basadas en clases manejadoras
El primer mecanismo para crear una etiqueta personalizada que esté incluida dentro de una librería es
asociarla a una clase de Java que proporcione la funcionalidad de la misma. Estas clases deben implementar
las interfaces SimpleTag, Tag y BodyTag o bien extender las clases adaptadoras SimpleTagSupport, TagSupport y
BodyTagSupport, que a su vez implementan respectivamente estas interfaces. Todas estas clases e interfaces se
encuentran en el paquete javax.servletJsp.tagtext.
Figura 7

Cuando una página JSP procesa una etiqueta, el motor JSP realiza las siguientes acciones en la clase
manejadora asociada, si implementa Tag o BodyTag:
1. Llama al método setPageContext() para asignar el contexto de la página.
2. Llama al método setParent() para asignar una etiqueta anidada de nivel superior, si la hubiera.
3. Establece los valores de los atributos, si los hay, mediante métodos setAtributo().
4. Llama al método doStartTag(), y procesa la funcionalidad de la etiqueta. El valor de retorno indica si debe
evaluarse el posible cuerpo de la etiqueta. (Si debe evaluarse el cuerpo llama a doInitBody() y doAfterBody() a
continuación.)
5. Llama al método doEndTag().
6. Llama al método release().
En estas interfaces, los métodos doTag(), doStartTag() y doEndTag() pueden lanzar una JspException si ocurre
un error.
2.1. Etiquetas que implementan «SimpleTag».
Si queremos definir etiquetas sencillas, donde no se distingue la ejecución del inicio de la etiqueta de su final,
podemos usar la interfaz SimpleTag. Esta interfaz proporciona los siguientes métodos:
• public void doTag(), es llamado una sola vez por cada invocación de la etiqueta. Este único método realiza
las tareas que realizarían los métodos doStartTag() y doEndTag() juntos.
• public void setParent(JspTag parent) para guardar el objeto instanciado para la etiqueta de nivel superior
• public JspTag getParent() para retorna la instancia de la clase de una etiqueta superior.
• public void setJspContext(JspContext pc) para guardar el contexto de la página actual. (JspContext es la
superclase de PageContext.)
Figura 8

168
• public void setJspBody(JspFragment jspBody) proporciona soporte para el contenido del cuerpo. El
contenedor lo invoca con un objeto JspFragment que encapsula el cuerpo de la etiqueta. Se puede utilizar el
método invoke() del fragmento para ejecutarlo y dirigir las salidas a un objeto Writer que se pasa como
argumento (que será el retornado por JspContext.getOut() si el argumento es null).
2.1.1. Características de las etiquetas «SimpleTag».
Cuando una página JSP procesa una etiqueta que implementa la interfaz SimpleTag, el motor JSP realiza las
siguientes acciones en la clase manejadora asociada:
1. Llama a los métodos setJspContext() y setJspBody() para asignar el contexto de la página y el cuerpo de la
página respectivamente.
2. Llama al método setParent() para asignar una etiqueta anidada de nivel superior, si la hubiera.
3. Establece los valores de los atributos, si los hay, mediante métodos setAtributo().
4. Llama al método doTag(), y procesa la funcionalidad de la etiqueta.
Las características de este tipo de etiquetas son:
• Poseen un único método de proceso doTag(), que es el que determina la funcionalidad de la etiqueta.
• Deben evaluar su cuerpo por código.
2.1.2. La clase «SimpleTagSupport».
La clase SimpleTagSupport es usada como base para crear nuevas clases manejadoras de etiquetas sencillas.
Implementa la interfaz SimpleTag y añade métodos adicionales.
Define las siguientes propiedades privadas:
• JspTag parentTag, referencia la etiqueta contenedora.
• JspContext jspContext, referencia el contexto de la página JSP que invoca la etiqueta.
• JspFragment jspBody, referencia el contenido del cuerpo. Posee el método invoke(Writer), el cual permite
evaluar el cuerpo y enviarlo al canal de salida pasado por argumento; si el argumento es null se toma como
canal de salida JspContext.getOut().
Los métodos que añade son:
• JspContext getJspContext(), retorna la propiedad jspContext.
• JspFragment getJspBody(), retorna la propiedad jspBody.
• static final JspTag findAncestorWithClass(JspTag from, Class<?> class), busca la instancia de una de las
etiquetas anidadas contenedoras.
2.1.3. Una etiqueta simple que muestra un saludo.
Añadiremos a la librería LibreriaTest una etiqueta que muestre un mensaje típico de saludo. El uso de esta
etiqueta será el siguiente en una página JSP:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:saludo />
</body>
</html>
Para crear la etiqueta comenzaremos añadiendo un fichero Tag Handler al proyecto. En el nodo «Source
Packages» añadiremos un nuevo paquete llamado "test" y mostraremos el menú contextual «Nuevo|Tag
Handler».

169
Figura 9

En el cuadro de diálogo «Nuevo Tag Handler» debemos establecer el nombre de la clase manejadora de la
etiqueta a SaludoTagHanler. Este asistente ofrece la posibilidad de extender SimpleTagSupport (que implementa
SimpleTag) y BodyTagSupport (que implementa Tag). Para este primer ejemplo hay que elegir la opción
«SimpleTagSupport (J2EE 1.4)».

170
Figura 10

Tras pulsar el botón «Siguiente» debemos agregar la nueva etiqueta a la librería creada previamente. También
asignaremos el nombre de etiqueta a saludo, y el contenido del cuerpo a empty. En la parte baja del asistente
podemos agregar atributos a la etiqueta. De momento no agregaremos ninguno.
Figura 11

171
Tras pulsar el botón «Finalizar» se creará la clase test.SaludoTagHandler se mostrará su contenido en el editor
de NetBeans:
package test;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.JspFragment;
import javax.servlet.jsp.tagext.SimpleTagSupport;
public class SaludoTagHandler extends SimpleTagSupport {
@Override
public void doTag() throws JspException {
JspWriter out = getJspContext().getOut();
try {
// TODO: insert code to write html before writing the body content.
// e.g.:
//
// out.println("<strong>" + attribute_1 + "</strong>");
// out.println(" <blockquote>");
JspFragment f = getJspBody();
if (f != null) {
f.invoke(out);
}
// TODO: insert code to write html after writing the body content.
// e.g.:
//
// out.println(" </blockquote>");
} catch (java.io.IOException ex) {
throw new JspException("Error in SaludoTagHandler tag", ex);
}
}
}
La plantilla generada por NetBeans es bastante explícita y nos ofrece un código de ejemplo para poder
escribir el contenido del cuerpo de la etiquete y algo antes y después del mismo. Basta con modificar el
método doTag() para escribir el típico saludo de "¡Hola Mundo!" con formato de título:
public void doTag() throws JspException {
JspWriter out = getJspContext().getOut();
try {
out.println("<h1>¡Hola, Mundo!</h1>");
} catch (java.io.IOException ex) {
throw new JspException("Error en la etiqueta SaludoTagHandler", ex);
}
}
La etiqueta ha quedado definida de la siguiente manera en la librería:
<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.1" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=
"http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd">
<tlib-version>1.0</tlib-version>
<short-name>test</short-name>
<uri>/com.personal.test</uri>
<tag>
<name>saludo</name>
<tag-class>test.SaludoTagHandler</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>
En este primer ejemplo no se ha considerada el cuerpo de la etiqueta ni ningún atributo.
2.1.4. Una etiqueta simple con atributos.
Crearemos ahora una etiqueta llamada <test:correo /> que podamos usar de la siguiente manera:
<body>
<test:correo destinatario="Juan Pérez">

172
Éste es el mensaje del correo.
</test:correo>
</body>
La etiqueta correo tendrá un atributo obligatorio llamado destinatario y un cuerpo. El resultado de la etiqueta
debe ser el escribir el nombre del destinatario en la página y en un párrafo aparte el cuerpo del correo.
Seguiremos los mismos pasos que para la etiqueta saludo:
Figura 12

Pero ahora añadiremos un atributo llamado destinatario. Para que sea obligatorio debemos marcar la casilla
«Atributo requerido», y dejaremos el tipo «java.lang.String».

173
Figura 13

Un atributo puede evaluarse en el momento de la solicitud o en el momento en que la página JSP es


convertida en un servlet dinámico. La primera opción especifica que el valor del atributo puede calcularse
dinámicamente en el momento de la solicitud, mientras que la segunda opción especifica que el valor del
atributo es estático y se determina al generar el servlet.
Las opciones elegidas para esta etiqueta quedarán así:

174
Figura 14

Como la etiqueta debe procesar su cuerpo se ha seleccionado scriptless para el contenido del cuerpo. Las
opciones posibles indican lo siguiente:
• empty, indica que la etiqueta no acepta un cuerpo.
• scriptless (por defecto), indica que el cuerpo puede contener etiquetas estándar y personalizados junto con
texto HTML. Cualquier expresión EL o JSP será evaluada y se mostrará su resultado en la renderización del
cuerpo
• tagdependet, indica otro tipo de contenido del cuerpo, como sentencias SQL pasadas a la etiqueta.
Tras pulsar el botón «Finalizar», se creará la clase CorreoTagHandler. Para ilustrar el uso de la interfaz
SimpleTag, modificaremos el código generado para implementar directamente SimpleTag en vez de extender
SimpleTagSupport.
package test;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class CorreoTagHandler implements SimpleTag {
// Definición de los atributos
private String destinatario;
public void setDestinatario(String destinatario) {
this.destinatario = destinatario;
}
// Datos pasados desde el motor JSP
private JspTag parent;
@Override
public void setParent(JspTag parent) {
this.parent = parent;
}
@Override
public JspTag getParent() {
return this.parent;
}
private JspContext pageContext;
@Override
public void setJspContext(JspContext pc) {

175
this.pageContext = pc;
}
private JspFragment jspBody;
@Override
public void setJspBody(JspFragment jspBody) {
this.jspBody = jspBody;
}
// Método de proceso de la etiqueta
@Override
public void doTag() throws JspException {
JspWriter out = pageContext.getOut();
try {
out.println("<p><strong>" + destinatario + "</strong></p>");
out.print("<p>");
jspBody.invoke(out);
out.print("</p>");
} catch (java.io.IOException ex) {
throw new JspException("Error en etiqueta CorreoTagHandler", ex);
}
}
}
Lo primero a destacar es el método setDestinatario(). Cuando en una etiqueta se define un atributo, el motor
JSP buscará un método setter con el nombre del atributo para pasarle el valor establecido en el fichero JSP.
A continuación, otros métodos setter son invocados por el motor JSP para pasar objetos que representan la
etiqueta contenedora, el contexto de la página, y el cuerpo de la etiqueta.
Por último, el método doTag() procesa la funcionalidad de la etiqueta. En este caso se obtienen del contexto
de la página el canal de salida, y se escribe un párrafo con el destinatario, y otro párrafo con el cuerpo de la
etiqueta.
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>correo</name>
<tag-class>test.CorreoTagHandler</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>destinatario</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
2.2. Etiquetas que implementan «Tag».
Se utiliza la interfaz Tag para crear etiquetas con funcionalidades más avanzadas. Esta etiquetas no requieren
renderizar su cuerpo desde el código de la clase manejadora, y permiten diferenciar entre la ejecución de la
etiqueta de inicio de la etiqueta de cierre.
Por tanto permiten los siguientes procesos en una etiqueta personalizada:
<test:etiqueta> <%-- SE PUEDE EJECUTAR UN CÓDIGO AQUÍ --%>
Cuerpo de la etiqueta <%-- EL CUERPO ES RENDERIZADO POR EL MOTO JSP --%>
</test:etiqueta> <%-- SE PUEDE EJECUTAR UN CÓDIGO AQUÍ --%>
2.2.1. Características de las etiquetas «Tag».
Cuando una página JSP procesa una etiqueta que implementa la interfaz Tag, el motor JSP realiza las
siguientes acciones en la clase manejadora asociada:
1. Llama al método setPageContext() para asignar el contexto de la página.
2. Llama al método setParent() para asignar una etiqueta anidada de nivel superior, si la hubiera.
3. Establece los valores de los atributos, si los hay, mediante métodos setAtributo().

176
4. Llama al método doStartTag(), y procesa la funcionalidad inicial de la etiqueta.
5) Si el método doStartTag() devuelve el valor Tag.EVAL_BODY_INCLUDE, el motor JSP renderiza el cuerpo
de la etiqueta, si lo tiene.
Si el método doStartTag() devuelve el valor Tag.SKIP_BODY, el motor JSP no renderiza el cuerpo.
6) Llama al método doEndTag(), y procesa la funcionalidad de cierre de la etiqueta.
7) Llama al método release(), donde se puede liberar cualquier recurso.
Las características de este tipo de etiquetas son:
• Permite delegar en el motor JSP el renderizado del cuerpo de la etiqueta.
• Permite ejecutar algún código antes y después del cuerpo de la etiqueta.
• Permite establecer desde código si se debe renderizar el cuerpo de la etiqueta.
2.2.2. Una etiqueta con parámetro opcional y cuerpo.
Añadiremos a la librería LibreriaTest una etiqueta que muestre un mensaje de saludo personalizado. El uso de
esta etiqueta será el siguiente en una página JSP:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:hola cliente='Juan Pérez'>
<p>Bienvenido a nuestra empresa<p>
</test:hola>
</body>
</html>
Al igual que con las etiquetas SimpleTag, comenzaremos añadiendo un fichero Tag Handler al proyecto. En
el nodo «Source Package» añadiremos al paquete "test" un «Nuevo|Tag Handler».
En el cuadro de diálogo «Nuevo Tag Handler» debemos establecer el nombre de la clase manejadora de la
etiqueta a HolaTagHanler. Este asistente ofrece la posibilidad de extender SimpleTagSupport (que implementa
SimpleTag) y BodyTagSupport (que implementa Tag). Para este caso hay que elegir la opción
«BodyTagSupport».

177
Figura 15

Tras pulsar el botón «Siguiente» debemos agregar la nueva etiqueta a la librería creada previamente. También
asignaremos el nombre de etiqueta a hola, y el contenido del cuerpo a JSP. En la parte baja del asistente
podemos agregar atributos a la etiqueta. Añadiremos un atributo llamado cliente. Como será opcional
debemos dejar desmarcada la casilla «Atributo requerido», y dejaremos el tipo «java.lang.String».
Figura 16

178
Las opciones elegidas para esta etiqueta quedarán así:
Figura 17

Como vemos, alguna de las opciones para el contenido del cuerpo es diferente. Las opciones posibles indican
lo siguiente:
• empty, indica que la etiqueta no acepta un cuerpo.
• JSP (por defecto), indica que el cuerpo puede contener etiquetas estándar y personalizadas junto con texto
HTML. Cualquier expresión EL o JSP será evaluada y se mostrará su resultado en la renderización del
cuerpo
• tagdependet, indica otro tipo de contenido del cuerpo, como sentencias SQL pasadas a la etiqueta.
Tras pulsar el botón «Finalizar», se creará la clase HolaTagHandler. NetBeans genera una plantilla de código un
poco compleja, así que la vamos a simplificar para ver todos los métodos que incluye la interfaz Tag:
package test;
import java.io.IOException;
import javax.servlet.jsp.*;
public class HolaTagHandler implements Tag {
// Datos pasados desde el motor JSP
private PageContext pageContext;
@Override
public void setPageContext(PageContext pc) {
this.pageContext = pc;
}
private Tag parent;
@Override
public void setParent(Tag parent) {
this.parent = parent;
}
@Override
public Tag getParent() {
return this.parent;
}
// Definición de los atributos
private String cliente;

179
public void setCliente(String cliente) {
this.cliente = cliente;
}
// Métodos de proceso de la etiqueta
@Override
public int doStartTag() throws JspException {
JspWriter out = pageContext.getOut();
try {
out.println(cliente == null ? "" : "<h2>Hola, sr./sra. " + cliente + "</h2>");
} catch (IOException ex) {
throw new JspException("Excepción en doStarttag.");
}
return Tag.EVAL_BODY_INCLUDE;
}
@Override
public int doEndTag() throws JspException {
return Tag.EVAL_PAGE;
}
@Override
public void release() {
}
}
Se han dispuesto los métodos en el mismo orden en que son invocados por el motor JSP, excepto
getParent(), el cual no es invocado automáticamente.
Primero el motor JSP pasa a la etiqueta el contenido de la página y la instancia de la etiqueta contenedora si
existe. Después busca métodos setter correspondientes a cada atributo. Como el atributo cliente es opcional
puede ocurrir que el método setCliente() no sea invocado; en este caso debemos establecer un valor por
defecto para esta atributo, que en este caso es su valor inicial a null.
A continuación se ejecuta el método doStartTag(). En este método podemos usar el objeto
PageContext.getOut() para obtener el canal de escritura en la página. Es este ejemplo, si la variable cliente es
nula no se escribe nada, sino se escribe una párrafo de saludo. Por último, este método debe decidir si el
motor JSP debe evaluar y renderizar el cuerpo de la etiqueta. Este método puede retornar dos valores:
•Tag.EVAL_BODY_INCLUDE, que indica que debe evaluarse el cuerpo. Este valor de retorno sólo tiene
sentido si hemos establecido el contenido del cuerpo a JSP o tagdependent.
• Tag.SKIP_BODY, que indica que no debe evaluarse el cuerpo.
Después se ejecuta el método doEndTag(). En este método también podemos generar una salida hacia la
página, que se ejecutaría después del renderizado del cuerpo. Este método puede retornar dos valores:
• Tag.EVAL_PAGE, para que el motor JSP sigua evaluando el resto de la página.
•Tag.SKIP_PAGE, para que el motor JSP finalice la interpretación de la página.
Por último es invocado el método release().
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>hola</name>
<tag-class>test.HolaTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>cliente</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
2.2.3. La clase «TagSupport».
La clase TagSupport implementa la interfaz IterationTag, que a su vez extiende a Tag, proporcionando a su
método funcionalidades por defecto y añade métodos adicionales.

180
Define la siguiente propiedad:
• protected PageContext pageContext, que encapsula el contexto de la página JSP que invoca la etiqueta.
Los nuevos métodos que aporta son:
• static final Tag findAncestorWithClass(Tag desde, Class clase), busca la instancia de una clase manejadora
cuya etiqueta encierre a la etiqueta actual. Este método se aplica para coordinar etiquetas anidadas en varios
niveles. Téngase en cuenta que el método getParent() debe retornar la instancia de la etiqueta anidada
inmediatamente superior. Este método busca desde la instancia de la etiqueta indicada en el primer
parámetro hacia arriba hasta que encuentre una etiqueta que coincida con la clase especificada en el segundo
parámetro.
• void setValue(String k, Object o), asocia un valor con un atributo creado en el ámbito de la etiqueta. Dicho
atributo se conversa durante el ciclo de vida de la etiqueta.
• Object getValue(String k), retorna el valor asociado con un atributo creado mediante el método setValue(),
o null si no existe el atributo.
• void removeValue(String k), eliminar un atributo creado con el método setValue().
• Enumeration<String> getValues(), retorna un enumeración de los atributos creados en el ámbito de la
etiqueta.
Además, reescribe el métododoStartTag() para que retorne el valor SKIP_BODY y el métododoEndTag() para
que retorne el valor EVAL_PAGE
La mayoría de manejadores de etiquetas que necesiten implementar Tag, deben extender TagSupport si sólo
necesitan redefinir pocos métodos.
El código de registro de la etiqueta en la librería LibreriaTest.tld quedaría así:
<?xml version="1.0" encoding="UTF-8"?>
<taglib>
………………………..
<tag>
<name>hola</name>
<tag-class>test.HolaTagHandler</tag-class>
<body-content>JSP</body-content>
<attribute>
<name>cliente</name>
<rtexprvalue>true</rtexprvalue>
<type>java.lang.String</type>
</attribute>
</tag>
</taglib>
2.2.4. Una etiqueta selectiva que extiende a «TagSupport».
Veremos cómo usar crear una etiqueta condicional que reciba un atributo condicion que debe cumplirse para
evaluar el cuerpo de la etiqueta.
La etiqueta se usará de la siguiente manera:
<%@taglib prefix="test" uri="/com.personal.test" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<test:if condicion="<%=request.getRemoteUser()!=null %>" >
El usuario actual es: <%= request.getRemoteUser()%>
</test:if>