Programación Distribuida y su Aplicación Bajo Internet Curso: 2004-2005 Autor: Juansa Sendra
Los 'servlets' son módulos de código Java que se ejecutan como una aplicación servidor (normalmente como extensión de un servidor web) para responder a solicitudes de los clientes. Aunque el concepto no está limitado a un único protocolo cliente/servidor, se usan principalmente con http (HttpServlet).
Los servlets utilizan las clases definidas en el paquete javax.servlet (entorno básico) y javax.servlet.http (extensiones del entorno básico para servlets que responde a peticiones http). Gracias a la portabilidad de Java y el uso de un entorno estandard, los servlets son independientes del sistema operativo y del servidor concretos.
Usos típicos de los servlets:
Ventajas sobre los CGI:
El primer paso es instalar JDK 1.2 o posterior (se recomienda JDK 1.3 o JDK 1.4). Posteriormente se instala Tomcat 4.0 o posterior (ambos pasos se describen en la documentación de la práctica inicial)
El contexto ROOT es la aplicación web por defecto. Para habilitarlo hay que editar el fichero server.xml del directorio conf, y eliminar el comentario que invalida la línea
<Context path="" docBase="ROOT" debug="0"/>
El siguiente cambio simplifica el desarrollo de servlets (simplemente almacenamos el servlet X en WEB-INF/classes y utilizamos la URL http://host/servlet/X) en lugar de tener que editar WEB-INF/web.xml cada vez que añadimos un servlet. Hay que editar el fichero web.xml del directorio conf, quitando el comentario al elemento servlet-mapping
<servlet-mapping> <servlet-name>invoker</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
Si no tenemos otros servidores escuchando en el port 80, indicamos a Tomcat que escuche en dicho port (evita tener que indicar el port en cada URL). En ciertos sistemas se necesitan permisos de administración para realizar el cambio (en Windows NT tendríamos que deshabilitar previamente el IIS). Para cambiar el port se edita el fichero server.xml del directorio conf, cambiando el atributo port del elemento Connector de 8080 a 80
<Connector className="org.apache.coyote.tomcat4.CoyoteConnector" port="80" .. />
Para no tener que arrancar de nuevo Tomcat cada vez que recompilamos un servlet que ya estaba cargado en memoria, indicamos que se comprueben las fechas de modificación de los ficheros class solicitados, y se recarguen en memoria los que han sido modificados desde el momento en que se cargaron inicialmente en memoria.
Para ello editamos el fichero server.sml en el directorio conf, y añadimos al elemento Service un subelemento DefaultContext con el atributo 'reloadable' a cierto. En otras palabras, buscamos el comentario
<!-- Define properties for each web application. This is only needed -4-. -->
y añadimos a continuación la siguiente línea:
<DefaultContext reloadable="true"/>
Dicha variable indica a Tomcat el directorio de instalación del JDK, y le permite gestionar las páginas JSP. Para ello editamos el fichero catalina.bat del directorio bin, e insertamos el código
if not "%JAVA_HOME%" == "" gotJavaHome set JAVA_HOME=-4- // esta es la nueva linea. Sustituir -4- por el directorio (ej c:\jdk1.3) :gotJavaHome
Sólo resulta necesario en versiones antiguas de Windows, en las que podemos recibir el mensaje "Out of Environment Space" al arrancar el servidor. Hay que pulsar el botón derecho sobre el fichero startup.bat del directorio bin, seleccionar Propiedades/Memoria, y cambiar 'Entorno inicial' de 'auto' a 2816. Repetir el proceso con el fichero shutdown.bat del directorio bin
Lanzar startup.bat, e iniciar un navegador en http://localhost (o http://localhost:8080 si no hemos cambiado al port 80). Debe aparecer una página de bienvenida
Con ello verificamos en qué directorios descargar pags HTML y la activación del compilador Java.
Debemos situar las pags HTML y JSP en webapps/ROOT o webapps/ROOT/loQueSea, y acceder con la URL http://localhost/fichero o http://localhost/loQueSea/fichero.
Generamos los siguientes ficheros:
hola.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head><title>Primera prueba HTML</title> <body> <h1>Prueba HTML</h1> Un saludo a todos </body> </html>
hola.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head><title>Primera prueba JSP</title> <body> <h1>Prueba JSP</h1> Hora local: <%= new java.util.Date() %> </body> </html>
Si falla el acceso hola.html, hemos usado un directorio o una URL incorrectas. Si sólo falla hola.jsp, revisar el paso 'fijar variable JAVA_HOME'
Al arrancar el servidor se inicializa CLASSPATH para incluir las clases JSP y servlets estandards, y el directorio WEB-INF/classes de cada aplicación web (que contiene servlets compilados). Para crear nuestras propias aplicaciones debemos crear un entorno similar, siguiendo los siguientes pasos:
Creamos un directorio personal donde situar nuestros servlets y páginas JSP (ej.- en la unidad w:), sobre el que iremos creando subdirectorios para reflejar la estructura de nuestros paquetes. Es importante no usar directamente directorios de la instalación Tomcat
Servlets y JSP no forman parte de la edición estandard de la plataforma 2 de Java, de forma que el compilador (javac) puede generar mensajes de error al compilar servlets (clases desconocidas). Hay que fijar CLASSPATH para incluir common/lib/servlet.jar, nuestro directorio de desarrollo, y el directorio actual (.)
set CLASSPATH=.;X;Y\common\lib\servlet.jar
donde X es nuestro dir. de desarrollo e Y el directorio de instalación de Tomcat
Abrimos en un navegador la página
webapps/romcat-docs/servletapi/index.html
y la añadimos a favoritos (contiene la documentación sobre las clases javax.servlet). Con ello facilitamos el acceso a dicha ayuda
Permite verificar que hemos realizado correctamente los pasos anteriores
Situamos este servlet (Hola.java) en nuestro directorio de desarrollo, y compilar (si hay errores, revisar el valor de CLASSPATH)
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Hola extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); String docType = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " + "Transitional//EN\">\n"; out.println(docType + "<HTML>\n" + "<HEAD><TITLE>Hola</TITLE></HEAD>\n" + "<BODY BGCOLOR=\"#FDF5E6\">\n" + "<H1>Hola</H1>\n" + "</BODY></HTML>"); } }
Situar la clase Hola.class en el directorio ROOT/WEB-INF/classes (si el directorio no existe, lo creamos previamente)
Al acceder a http://localhost/servlet/Hola obtendremos una página HTML con el saludo.
Realizar los mismos pasos con el servlet Hola2.java. Al usar el paquete pd, debe incluirse en un subdirectorio PD tanto en el directorio de desarrollo como en el servidor (el fichero Hola2.class debe copiarse en ROOT/WEB-INF/classes/PD)
package PD; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Hola2 extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); String docType = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " + "Transitional//EN\">\n"; out.println(docType + "<HTML>\n" + "<HEAD><TITLE>Hola (2)</TITLE></HEAD>\n" + "<BODY BGCOLOR=\"#FDF5E6\">\n" + "<H1>Hola (2)</H1>\n" + "</BODY></HTML>"); } }
Debemos acceder a la página con la URL http://localhost/servlet/PD.Hola2
Hola3.java es un servlet del paquete PD que utiliza la clase ServletAux.class definida en ServletAux.java
ServletAux.java
package PD; import javax.servlet.*; import javax.servlet.http.*; public class ServletAux { public static final String DOCTYPE = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 " + "Transitional//EN\">"; public static String headWithTitle(String title) { return(DOCTYPE + "\n<HTML>\n<HEAD><TITLE>" + title + "</TITLE></HEAD>\n"); } // Lee el parametro indicado, y lo convierte a entero // si no existe, o formato ilegal, devuelve el valor por defecto public static int getIntParameter(HttpServletRequest request, String nom, int porDefecto) { String s=request.getParameter(nom); int n; try {n=Integer.parseInt(s);} catch(NumberFormatException nfe) {n=porDefecto;} return n; } // Devuelve el cookie dado su nombre y // un vector de cookies (si no existe el nombre, devuelve null) public static Cookie getCookie(Cookie[] cookies, String nom) { if (cookies == null) return null; for(int i=0; i<cookies.length; i++) if (nom.equals(cookies[i].getName())) return(cookies[i]); return(null); } // Devuelve el valor de un cookie dado su nombre y // un vector de cookies (si no existe el nombre, devuelve el valor por defecto public static String getCookieValue(Cookie[] cookies, String nom, String porDefecto) { Cookie c=getCookie(cookies,nom); if (c==null) return porDefecto; else return(c.getValue()); } // Dada una tira, realiza las conversiones que // permiten insertarla en una página web // original '<' '>' '"' '&' // sustituto '<' '>' '"' '&' public static String filter(String input) { StringBuffer filtered = new StringBuffer(input.length()); char c; for(int i=0; i<input.length(); i++) { c = input.charAt(i); if (c == '<') filtered.append("<"); else if (c == '>') filtered.append(">"); else if (c == '"') filtered.append("""); else if (c == '&') filtered.append("&"); else filtered.append(c); } return(filtered.toString()); } }
hola3.java
package PD; import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Hola3 extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); String titulo = "Hola (3)"; out.println(ServletAux.headWithTitle(titulo) + "<BODY BGCOLOR=\"#FDF5E6\">\n" + "<H1>" + titulo + "</H1>\n" + "</BODY></HTML>"); } }
Ambas clases (Hola3 y ServletAux) están en el paquete PD, y por tanto deben situarse en el directorio PD
Hasta ahora desarrollamos y compilamos en un entorno de desarrollo, y luego copiamos manualmente los ficheros .class al directorio correspondiente de Tomcat -> tedioso y propenso a errores. Existen distintas alternativas para automatizar el proceso.
Cada una es preferible a las anteriores, especialmente en desarrollos complejos
Es un método simple y adecuado para empezar, pero no para fase de producción (copia tanto los ficheros .class como los fuentes .java, y depende del servidor especifico)
Situados en X/ROOT/WEB-INF, pulsamos el botón derecho sobre la carpeta 'classes' y seleccionamos Copiar: vamos al directorio de desarrollo y seleccionamos 'pegar acceso directo'. Tras compilar, arrastramos los .class al icono de acceso directo.
La misma idea sirve para facilitar la descarga de otros tipos de fichero (HTML, JSP, etc.), creando en el directorio de desarrollo un icono de acceso directo a X/webapps/ROOT
Por defecto, el compilador sitúa los .class en el mismo directorio donde están los fuentes, pero la opción -d permite indicar otro directorio destino. El único inconveniente es que sólo resuelve el problema de los ficheros java (los HTML, JSP, etc. hay que seguir gestionándolos manualmente)
Ej.- para compilar Hola2.java, escribimos javac -d
X/webapps/WEB-INF/classes Hola2.java
donde X es el directorio de instalación de Tomcat
Se trata de configurar el entorno de desarrollo o compilador para que se encargue de realizar la descarga de los ficheros
ant es una herramienta similar al make de Unix (desarrollada dentro del proyecto jakarta, el mismo que dió origen a Tomcat). Es muy flexible, pero requiere cierto periodo de aprendizaje (ver http://jakarta.apache.org/ant)
Para inicializar un servlet, la aplicación servidor carga la clase Servlet (y probablemente otras clases referenciadas) y crea una instancia invocando un constructor sin argumentos. Entonces invoca el método init del servidor (init(ServletConfig config))
En el método init debemos incluir toda inicialización previa al uso del servlet, y almacenar el objeto ServletConfig que posteriormente puede invocarse mediante el método getServletConfig(). Los servlets que extienden GenericServlets (o su subclase HttpServlet) deben invocar super.init(config) al principio del método init.
El objeto ServletConfig contiene parámetros del Servlet y una referencia al ServletContext del Servlet. Se garantiza que 'init' sólo se invoca una vez durante toda la vida del servlet. No necesita ser thread-safe, porque el método 'service' no se invocará hasta que termine init.
Una vez inicializado, el método 'service(ServletRequest req, ServletResponse resp)' cada vez que el servlet recibe una petición. El método se invoca de forma concurrente (varias tareas pueden invocar simultáneamente dicho método), y por tanto debe ser thread-safe (NOTA.- también existen técnicas para garantizar que se invoque de forma serie cuando resulte necesario).
Cuando necesita descargarse el servlet (ej.- porque debe cargarse en el servidor una nueva versión, o porque vamos a detener el servidor), debe invocarse el método destroy(). Cuando se invoca destroy todavía pueden quedar tareas ejecutando el método 'service', de forma que destroy debe ser thread-safe. Todo recurso reservado en init (ej.- conexiones de red, ficheros, accesos a BD) debe liberarse en destroy. Se garantiza que destroy se invoca una única vez durante toda la vida del servlet.
init (service)* destroy
Es un protocolo orientado a petición/respuesta.
Una petición HTTP consiste en:
Una respuesta contiene:
Un método HTTP llamado X conduce a invocar el método doX del servlet (ej.- GET supone invocar doGet). Todos estos métodos reciben los parámetros (HttpServletRequest req, HttpServletResponse resp):
Los métodos doOptions y doTrace proporcionan implementaciones por defecto, y normalmente no se sobreescriben. El método HEAD (que devuelve las mismas líneas de cabecera que GET, pero sin incluir el cuerpo) se realiza invocando doGet e ignorando la salida escrita por este método.
Nos quedan los métodos doGet, doPut, doPost y doDelete, cuyas implementaciones por defecto devuelven un error HTTP 'Bad Request'. Una subclase de HttpServlet sobreescribe uno o más de estos métodos, para proporcionar una implementación razonable.
Cuando solicitamos una URL en un navegador se usa el método GET. El método GET no incluye datos en el cuerpo. La respuesta debe contener un cuerpo con los datos de respuesta y campos de cabecera que describen el cuerpo (especialmente Content-Type y Content-Encoding). Cuando se envía un formulario HTML puede utilizarse tanto GET como POST. Con la petición GET los parámetros se codifican en la URL, mientras que en la petición POST los parámetros se incluyen en el cuerpo.
Los editores HTML y otras herramientas de desarrollo utilizan peticiones PUT para situar nuevos recursos en el servidor web, y peticiones DELETE para borrar recursos.
Contenido estático (genera el mismo contenido cada vez que se invoca)
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Hola extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<HTML>\n" + "<HEAD><TITLE>Hola</TITLE></HEAD>\n" + "<BODY BGCOLOR=\"#FDF5E6\">\n" + "<H1>Hola</H1>\n" + "</BODY></HTML>"); out.close(); } public String getServletInfo() { return "Hola 1.0 Programación Distribuida y su Aplicación bajo Internet 2003" } }
Aspectos a comentar sobre el código:
En general, podemos considerar el siguiente esqueleto:
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class Hola extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // usa req para leer la cabecera HTTP de la petición (ej.- cookies) // y datos del formulario HTML // usa resp para especificar la cabecera de la respuesta PrintWriter out = resp.getWriter(); // usa out para enviar contenido al navegador } }
Cuando utilizamos servicios de compra por internet, buscadores, gestión de reservas, etc. utilizamos formularios para comunicar al servidor un conjunto de datos. Un formulario presenta un conjunto de etiquetas y campos que permiten al usuario la selección/introducción de datos. Podemos utilizar distintos modelos de formularios en la parte cliente:
Aunque sólo vamos a considerar formularios HTML, los formularios PDF generan el mismo tipo de solicitud hacia el servidor (empaquetan igual los valores introducidos por el usuario), y por lo tanto son indistinguibles para un servlet.
El objetivo de este apartado es los sevlets a la creación de formularios HTML y a la gestión de la respuesta (obtención y procesamiento de los valores editados por el cliente).
Empezamos con un formulario HTML básico:
<HTML> <BODY> <H1>Indica tus datos personales</H1> <FORM action="servlet/form0" method="GET"> Nombre y apellidos: <INPUT type="text" name="nombre"> <INPUT type="text" name="ap1"> <INPUT type="text" name="ap2"> <BR> Departamento: <INPUT type="radio" checked name="depto" value="comercial">Comercial <INPUT type="radio" name="depto" value="desarrollo">Desarrollo <INPUT type="radio" name="depto" value="finanzas">Finanzas <BR> <P> Indica el rasgo que define mejor tu personalidad: <SELECT name="personalidad" <OPTION value="responsable"</OPTION>Responsable <OPTION value="trabajador"</OPTION>Trabajador <OPTION value="ambicioso"</OPTION>Ambicioso <OPTION value="nervioso"</OPTION>Nervioso <OPTION value="educado"</OPTION>Educado <OPTION value="estudioso"</OPTION>Estudioso <OPTION value="valiente"</OPTION>Valiente </SELECT> <BR> <INPUT type="submit"> </FORM> </BODY> </HTML>
La marca 'FORM' del formulario HTML indica en su campo 'action' la aplicación que debe procesar la respuesta, y en su campo 'method' el método utilizado para acceder codificar los parámetros en la posterior petición al servidor (al pulsar el botón 'submit').
Cada campo del formulario posee un nombre único (propiedad 'name' de la marca correspondiente), y una vez cumplimentado por el usuario, un valor (un campo también puede tener un valor por defecto, indicado por la propiedad 'value').
La petición hacia el servidor contiene un conjunto de pares 'nombre=valor', donde 'nombre' corresponde al nombre del campo y 'valor' a su valor actual. El orden de dichos pares no importa, puesto que posteriormente el servidor accede al valor de cada campo por nombre, no por posición.
El conjunto de pares 'nombre=valor' puede hacerse llegar al servidor utilizando dos métodos distintos:
'URL del programa indicado en action'?nombre1=valor1&nombre2=valor2&..'
La ventaja es la simplicidad (ej.- al ser una URL podemos guardar en 'favoritos' formularios con su contenido, o comprobar el funcionamiento del programa sin necesidad de rellenar el formulario). El inconveniente principal es la limitación de tamaño (la URL posee un tamaño máximo limitado, y por tanto limita la longitud del conjunto de pares 'clave=valor')
Al definir un servlet como extensión de 'HttpServlet' podemos sobreescribir los métodos 'doGet' (se activa cuando la petición utiliza el método GET) y 'doPost' (se activa cuando la petición utiliza el método POST).
Si únicamente está previsto un método de invocación, es suficiente sobreescibir el correspondiente método 'doGet' o 'doPost', pero generalmente deseamos diseñar servlets capaces de responder a ambos tipos de petición, por lo que sobreescribimos ambos métodos.
El siguiente servlet sirve para gestionar el formulario 'form0.html' (en este ejemplo nos limitamos a recoger los valores, y confeccionar con ellos una página Html que se devuelve como resultado):
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class form0 extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<HTML>\n" + "<HEAD><TITLE>Respuesta a form0.hml</TITLE></HEAD>\n" + "<BODY BGCOLOR=\"#FDF5E6\">\n" + "<H1>Respuesta al formulario form0.html</H1>\n" + "<UL>\n" + " <LI>nombre: " + req.getParameter("nombre") + "\n" + " <LI>ap1: " + req.getParameter("ap1") + "\n" + " <LI>ap2: " + req.getParameter("ap2") + "\n" + " <LI>depto: " + req.getParameter("depto") + "\n" + " <LI>personalidad: " + req.getParameter("personalidad") + "\n" + "</UL>\n" + "</BODY></HTML>"); out.close(); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } public String getServletInfo() { return "form0 1.0 Programación Distribuida y su Aplicación bajo Internet 2003"; } }
El ejemplo anterior ilustra varios aspectos importantes:
Cuando un determinado campo posee múltiples valores (ej.- una lista con posibilidad de selección múltiple, varios campos de entrada con el mismo nombre, etc) el sufijo que se añade a la URL posee otros tantos pares 'nombre=valor'. Si asumimos un campo con nombre 'x' y valores 'valor1..valorN', tendremos:
'x=valor1&x=valor2&...&x=valorN'
El método 'getParameter' devuelve únicamente el primero de los valores; para acceder a todos ellos debemos utilizar 'getParameterValues', que devuelve un vector de Strings conteniendo los distintos valores.
El siguiente formulario (form1.html) contiene un campo con múltiples valores:
<HTML> <BODY> <H1>Introduce una lista de nombres</H1> <FORM action="/servlet/form1" method="GET"> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="submit"> </FORM> </BODY> </HTML>
Para procesar dicho formulario, utilizamos el siguiente servlet:
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class form1 extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = resp.getWriter(); out.println("<HTML>\n" + "<HEAD><TITLE>Respuesta a form1.hml</TITLE></HEAD>\n" + "<BODY BGCOLOR=\"#FDF5E6\">\n" + "<H1>Respuesta al formulario form1.html</H1>\n"); out.println("<H2>Parametros recibidos</H2>\n"); String noms[]=req.getParameterValues("nombres"); for (int i=0; i<noms.length; i++) out.println(noms[i]); out.println("</BODY></HTML>"); out.close(); } public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } public String getServletInfo() { return "form0 1.0 Programación Distribuida y su Aplicación bajo Internet 2003"; } }
Hasta el momento hemos supuesto que la aplicación conoce todos los nombres de los campos que aparecen en el formulario. Tambien es posible obtener de forma dinámica el conjunto de nombres (el conjunto de campos disponibles), utilizando el método 'getParameterNames'.
El resultado de dicho método es de tipo 'Enumeration' (concretamente java.util.Enumeration). La acción más frecuente sobre un tipo enumerado es visitar todos sus elementos (iterar), para lo cual utilizamos en un bucle los métodos 'hasMoreElements' y 'nextElement'.
El siguiente código ilustra el acceso a los campos de unformulario (localiza los distintos nombres y accede a su(s) valores)
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class leeParam extends HttpServlet { public void service (HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html"); PrintWriter out= resp.getWriter(); out.println("<HTML>\n<BODY>\n Parametros recibidos \n<PRE>"); // obtiene los valores del formulario Enumeration params= req.getParameterNames(); while (params.hasMoreElements()) { String p= (String) params.nextElement(); String v[]=req.getParameterValues(p); if (v.length==1) out.println(p+"="+v[0]); else { out.print(p+"="); for (int i=0; i<v.length; i++) { if (i>0) out.print(","); out.println(v[i]); } out.println(); } } out.println("</PRE>\n</BODY>\n</HTML>"); } }
En este ejemplo se ha recurrido a sobreescribir directamente el método 'service' en lugar de sobreescribir 'doGet' y 'doPost'. El efecto neto es el mismo que en ejemplos anteriores.
Tal y como se ha descrito en el apartado anterior, 'getParameter("X") puede generar resultados diferentes:
La respuesta ante cada una de las situaciones depende de la lógica deseada en la aplicación. Es habitual requerir que determinados campos tengan valor (campos requeridos), y en muchos casos es deseable que los valores de los campos cumplan determinadas propiedades (ej.- que el DNI introducido sea un valor numérico con el número de dígitos correcto).
Cuando el contenido de un formulario es incorrecto, lo deseable es volver a remitir el mismo formulario, pero manteniendo los valores correctos (como valores por defecto) y eliminando los valores incorrectos (además es deseable algún mensaje que indique porqué no eran correctos).
Esta labor debe realizarse en el servlet, utilizando los esquemas que se describen en el siguiente apartado.
Existen distintas posibilidades:
Documento HTML estático (form2.html)
<HTML> <HEAD> <TITLE>Cuestionario inicial</TITLE> </HEAD> <BODY> <H1>Cuestionario inicial</H1> <BR> <P> Te agradeceria que dedicases unos minutos a rellenar este cuestionario <BR> <FORM action="servlet/form2a" method="POST"> 1.- Indica tu lenguaje de programacion preferido <SELECT name="lengupref" <OPTION value="pascal"</OPTION>Pascal <OPTION value="c"</OPTION>C <OPTION value="c++"</OPTION>C++ <OPTION value="delphi"</OPTION>Delphi <OPTION value="ada"</OPTION>Ada <OPTION value="java"</OPTION>Java <OPTION value="visual basic"</OPTION>Visual Basic <OPTION value="python"</OPTION>Python </SELECT> <P> 2.- Indica tu Sistema Operativo preferido <BR> <INPUT type="radio" name="sopref" checked value="linux">Linux <INPUT type="radio" name="sopref" value="windows">Windows <INPUT type="radio" name="sopref" value="mac">MacOS <P> 3.- Indica los lenguajes que has utilizado <BR> <INPUT type="checkbox" name="lengusa" value="pascal">Pascal <INPUT type="checkbox" name="lengusa" value="c">C <INPUT type="checkbox" name="lengusa" checked value="c++">C++ <INPUT type="checkbox" name="lengusa" value="java">Java <INPUT type="checkbox" name="lengusa" value="ada">Ada <INPUT type="checkbox" name="lengusa" value="python">Python <P> 4.- Tecnologias que piensas utilizar en un futuro proximo <BR> <SELECT name="tecno" multiple size=3> <OPTION value="corba"</OPTION>Corba <OPTION value="rmi"</OPTION>RMI <OPTION value="ejb"</OPTION>EJB <OPTION value="com"</OPTION>COM <OPTION value="xml"</OPTION>XML <OPTION value="wap"</OPTION>WAP </SELECT> <P> <BR> <INPUT type="submit" value="envia"> </FORM> </BODY> </HTML>
Servlet que lo procesa (form2a.java)
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class form2a extends HttpServlet { public void service (HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html"); PrintWriter out= resp.getWriter(); // obtiene los valores del formulario String lengupref =req.getParameter("lengupref"); String sopref =req.getParameter("sopref"); String[] lengusa =req.getParameterValues("lengusa"); String[] tecno =req.getParameterValues("tecno"); if (lengusa==null) lengusa= new String[0]; if (tecno==null) tecno = new String[0]; // escribe los valores en un fichero try { PrintWriter fout= new PrintWriter(new FileWriter("datos",true)); fout.println(lengupref); fout.println(sopref); for (int i=0; i<lengusa.length; i++) { if (i>0) fout.print(", "); fout.print(lengusa[i]); } for (int i=0; i<tecno.length; i++) { if (i>0) fout.print(", "); fout.print(tecno[i]); } fout.println(); fout.println("----------"); fout.close(); out.println("<HTML>\n<BODY>\n" + "<H1>Gracias</H1>\n" + "Gracias por participar en la encuesta\n" + "</BODY>\n</HTML>\n"); } catch (IOException e) { out.println("<HTML>\n<BODY>\n" + "<H1>Error</H1>\n" + "No se han podido grabar las respuestas" + "debido a un error en el servidor\n" + "</BODY>\n</HTML>\n"); } } }
Para que el ejemplo tenga interés (y no se limite a generar de forma dinámica un formulario puramente estático), planteamos la generación dinámica de exámenes tipo test (ej.- con preguntas seleccionadas aleatoriamente de un gran conjunto inicial de preguntas).
El servlet form4.java se encarga de procesar los formularios remitidos por los alumnos. El objetivo es construir un histograma con la frecuencia de cada respuesta
NOTA.- En este ejemplo los datos no se añaden al final del fichero, sinoque se sobreescribe el fichero. Si lo que desamos no es sobreescibir, sino actualizar el fichero (ej.- para confeccionar un histograma), usamos la siguiente estrategia:
creación se asume acceso concurrente y se reintenta posteriormente)
modificamos (ej.- buscamos la respuesta para construir un histograma)
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class form4a extends HttpServlet { public void service (HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html"); PrintWriter out= resp.getWriter(); String dir= req.getParameter("dir"); try { File f=null; if (dir!=null) f= new File(dir,"form4.examen"); else f=new File("form4.examen"); BufferedReader in= new BufferedReader(new FileReader(f)); String pregunta=in.readLine(), linea; Vector r= new Vector(); // recoge las respuestas while ((linea=in.readLine()) != null) r.addElement(linea); in.close(); // muestra la pregunta y las posibles respuestas out.println("<HTML>\n<BODY>"); out.println("<FORM action=\"servlet/form4b\" method=\"POST\">"); out.println("<B>pregunta</B> <BR>"); Enumeration e= r.elements(); while (e.hasMoreElements()) { String s= (String) e.nextElement(); out.println("<INPUT type=\"radio\" name=\"respuesta\" value=\""+s+"\"> <"+s+"><BR>"); } out.println("<P>\n<INPUT type=\"submit\" value=\"envia\">" + "</FORM>\n</BODY></HTML>"); } catch (IOException e) { out.println("<HTML>\n<BODY>\n" + "<H1>Error</H1>\n" + "El servidor no está disponible\n" + "</BODY>\n</HTML>\n"); } } }
y para procesarlo:
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class form4b extends HttpServlet { public void service (HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html"); PrintWriter out= resp.getWriter(); String r= req.getParameter("respuesta"); if (r==null) { out.println("<HTML>\n<BODY>\n" + "<H1>No has contestado a la pregunta</H1>" + "</BODY></HTML>"); return; } try { String dir = System.getProperty("Form4b.dir"); File f=null, f2=null; if (dir!=null) { f= new File(dir,"form4b.a"); f2= new File(dir,"form4b.b"); } else { f= new File("form4b.a"); f2= new File("form4b.b"); } boolean fin=false; for (int i=0; i<30; i++) { if (f2.createNewFile()) {fin=true; break;} try{Thread.sleep(1000);} catch (Exception e){} } if (!fin) throw new IOException("no puedo reservar el fichero"); Vector conteo=new Vector(), respuestas= new Vector(); int nresp=0; try { BufferedReader in= new BufferedReader(new FileReader(f)); PrintWriter out= new PrintWriter(new FileWriter(f2)); String linea; boolean hayResp=false; while ((linea=in.readLine()) != null) { // cada linea es de la forma "conteo:respuesta" int pos= linea.indexOf(':'); if (pos<0) continue; // obviamos esa linea String c,r; c=linea.substring(0,pos); r=linea.substring(pos+1); int contador=0; try {contador=Integer.parseInt(c);} catch (Exception e) {} if (r.equals(getParameter("resp")) { hayResp=true; contador++; } nresp+=contador; out.println(contador+":"+r); respuestas.addElement(r); conteo.addElement(new Integer(contador)); } out.close(); in.close(); f.delete(); f2.renameTo(f); // reemplaza fichero original // si la respuesta no aparece en el fichero mostramos el error // inicialmente deberia existir el fichero con todas las respuestas // y contadores a 0 if (!hayResp) { out.println("<HTML><BODY>" + "<H1>Respuesta inesperada<H1>" + "Tu respuesta no coincide con ninguna de las propuestas"); return; } } catch (IOException e) { try { f2.delete(); } catch (Exception e) {} out.println("<HTML>\n<BODY>\n" + "<H1>Error</H1>\n" + "El servidor no está disponible\n" + "</BODY>\n</HTML>\n"); return } out.println("<HTML>\n<BODY>\n" + "<H1>Gracias</H1>\n" + "<H2>Gracias por tu colaboracion</H2> <P>" + " <B>Estos son los resultados:</B> <P>" + "</HTML></BODY>"); } }
Esta estrategia tiene sentido cuando el cliente debe utilizar repetidamenteel mismo formulario, recordando en todo momento los contenidos anteriores(ej.- en una cesta de la compra se visualiza el estado actual, y se permiteañadir/borrar items repetidamente).
Inicialmente (la primera vez que se usa la página) debe generarse el formulario inicial, y posteriormente se presenta siempre el estado actual del formulario. Cuando se solicita un formulario debemos determinar si es o no la primera vez que se utiliza la página, para lo cual podemos usar distintas estrategias:
se puede aplicar cuando queremos rellenar inicialmente campos con valores por defecto y pasamos esos valores como parte de la URL
<INPUT type="hidden" name="yaEnviado" value=yes">
Cuando el usuario accede por vez primera dicha variable no es accesible
(getParameter(yaEnviado) devuelve null)
y el método gestor se declara en la marca <FORM> como POST (ej.- <FORM action="servlet/prog" method="POST">
podemos distinguir entre la primera invocación y las siguientes averiguandoel método usado para transmitir los parámetros Ej.-
if (request.getMethod().equals("POST")).. // no es la primera vez
Como ejemplo vamos a desarrollar un formulario que permita crear tarjetasde visita de forma iterativa (se rellenan los datos, se indica un formatode presentación, y el programa muestra el resultado; si no estamos satisfechos, volvemos a modificar el formulario, etc).El programa detectará también la ausencia de valor en campos requeridos,en cuyo caso mostrará una página con el mensaje correspondiente.
import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.util.*; public class leeParam extends HttpServlet { public void doPost (HttpServletRequest req, HttpServletResponse resp) throws IOException { resp.setContentType("text/html"); PrintWriter out= resp.getWriter(); // obtiene los valores del formulario String nombre= request.getParameter("nombre"); if (nombre==null) nombre=""; String ap1= req.getParameter("ap1"); if (ap1==null) nombre=""; String ap2= req.getParameter("ap2"); if (ap2==null) nombre=""; String direc= req.getParameter("direc"); if (direc==null) nombre=""; String telf= req.getParameter("telf"); if (telf==null) nombre=""; String email= req.getParameter("email"); if (email==null) nombre=""; String formato= req.getParameter("formato"); if (formato==null) nombre=""; // fija estilo por defecto para las etiquetas obligatorias String colorNombre="black", colorAp1="black", colorAp2="black", colorNotificacion="red"; if (req.getMethod().equals("POST")) { boolean estaTodo=true; if (nombre.length==0) {colorNombre=colorNotificacion; estaTodo=false;} if (ap1.length==0) {colorAp1=colorNotificacion; estaTodo=false;} if (ap2.length==0) {colorAp2=colorNotificacion; estaTodo=false;} if (!estaTodo) { out.println("<HTML>\n<BODY>\n" + "<H1>Faltan valores requeridos</H1>\n" + "Los campos a rellenar están marcados en " + "<FONT color=\""+colorNotificacion+"\""+colorNotificacion+"> </FONT>" + "</HTML></BODY>"); } else { // muestra nombre y direccion String nom= ap1+" "+ap2+" "+nombre+"<BR>"+dir+"<BR>"+email+" "+telf; out.println("el nombre actual es: <P>"); if (formato.equals("negrita")) out.println("<B>"+nom+"</B>"); else if (formato.equals("negrita")) out.println("<I>"+nom+"</B>"); else out.println(nom); out.println("<P>"); } } out.println("<FORM action=\"tarjeta.jsp\" method="POST">" + "<TABLE>" + "<TR><TD>nombre: <TD> <INPUT type=\"text\" name=\"nombre\" value="+nombre+">" + "<TD><FONT color=\""+colorNombre+"\">requerido</FONT>" + "<TR><TD>nombre: <TD> <INPUT type=\"text\" name=\"ap1\" value=\""ap1+"\" + "<TD><FONT color=\""colorAp1"\">requerido</FONT>" + "<TR><TD>nombre: <TD> <INPUT type=\"text\" name=\"ap2\" value=\""+ap2+"\>" + "<TD><FONT color="\"+colorAp2+"\">requerido</FONT>" + "<TR><TD>nombre: <TD> <INPUT type=\"text\" name=\"direc\" value=\""+direc+"\>" + "<TR><TD>nombre: <TD> <INPUT type=\"text\" name=\"telf\" value=\""+telf+"\>" + "<TR><TD>nombre: <TD> <INPUT type=\"text\" name=\"email\" value=\""+email+"\>" + "<TR><TD>nombre: <TD> <INPUT type=\"text\" name=\"formato\" value=\""+formato+"\>" + "</TABLE>" + "<P>" + "Opciones de formato:" + "<SELECT name=\"formato\">" + "<OPTION value=\"normal\">Normal</OPTION>" + "<OPTION value=\"negrita\">Negrita</OPTION>" + "<OPTION value=\"italica\">Italica</OPTION>" + "</SELECT>" + "<P>" + "<INPUT type=\"submit\" value=\"Nueva tarjeta\">" + "</FORM>" + "</BODY>" + "</HTML>"); } }
El protocolo HTTP no posee noción de estado (cada solicitud al servidor es independiente del conjunto de peticiones previas), pero en distintas aplicaciones resulta interesante introducir el concepto de estado en el servidor.
Podemos encontrar distintos escenarios en los que resulta importante la noción de estado:
El uso de cookies no presenta riesgos de seguridad (únicamente se accede a un área del disco definida y controlada por el navegador), y facilita en gran medida el desarrollo del software servidor.
Desgraciadamente, su abuso puede comprometer el derecho de la intimidad (el cliente no es consciente del tráfico de información a/desde el servidor en relación con los cookies, de forma que pueden recogerse estadísticas, preferencias, etc. sin autorización del usuario).
De hecho, en la actualidad muchas de las aplicaciones que utilizan cookies 'agreden' en mayor o menor medida los derechos del cliente. Por esta razón, los cookies tienen mala prensa, y se tiende a deshabilitarlos (es una de las opciones de configuración del navegador).
La conclusión es que son una herramienta muy útil en entornos controlados (ej. una intranet), en los que los clientes 'confian' en el software de servidor desarrollado especificamente para dicha intranet.
El concepto de cookie es similar al de un diccionario (conjunto de pares <clave,valor>). La creación de nuevos cookies requiere tres pasos:
Cookie c= new Cookie(nombre,valor);
c.setMaxAge(80);
resp.addCookie(c)
Además la clase Cookie define un conjunto de métodos:
public void setName(String nom); public String getName(); public void setValue(String val); public String getValue(); public void setDomain(String dominio); public String getDomain(); public void setMaxAge(int tiempoVida); public int getMaxAge(); public void setPath(String path); public String getPath(); public void setVersion(int version); public int getVersion(); public void setSecure(boolean); public int getSecure();
Aunque setName y setValue no suelen utilizarse (normalmente especificamos el nombre del cookie y su valor en el constructor), getName y getValue resultan muy útiles para la lectura de cookies.
Normalmente, el navegador únicamente devuelve los cookies al servidor que los envión, pero podemos utilizar setDomain() para indicar al navegador que remita los cookies a otros hosts del mismo dominio. Ej.-
c.setDomain(".upv.es")
para enviar el cookie c a todo host del Politécnico.
Podemos utilizar cookies de sesión (desaparecen al cerrar el navegador) o persistentes (sobreviven el tiempo de vida indicado, o hasta que el usuario los borra de forma explícita). setMaxAge permite especificar la caducidad en segundos. Un valor negativo (o ningún valor) indica que se trata de un cookie de sesión, 0 indica al navegador que borre el cookie, y un valor positivo indica tiempo de vida en segundos.
Por defecto, el navegador únicamente devolverá el cookie a URLs correspondientes al directorio donde está la aplicación que creó el cookie o alguno de sus subdirectorios. El método setPath permite indicar otras rutas a las que también se devolvería el cookie (setPath("/") significa enviar el cookie en todas las páginas).
Por defecto se crean cookies 'versión 0'. Se puede utilizar setVersion para cambiar a la versión 1, pero no lo soportan todos los navegadores (no se aconseja).
Por defecto, lo cookies se envían con indenpendencia de acceder o no a través de SSL. Con setSecure(true) indicamos que sólo se devuelvan los cookies al acceder a través de una conexión cifrada (SSL).
Para leer cookies utilizamos el método getCookies definido en HttpServletRequest (o sea, aplicamos req.getCookies, siendo req uno de los parámetros al método service del servlet).
El método getCookies() devuelve un vector de objetos cookie (todos los cookies enviados por el navegador). Si no hay cookies se obtiene un vector de longitud 0.
Para acceder a cada cookie individual iteramos sobre el vector:
Creamos un servlet simple que registra el número de 'visitas' al mismo. Deseamos mostrar el número de visitas para cada usuario, para lo cual creamos un cookie correspondiente al contador, e incrementamos el cookie en cada visita. El esqueleto de la aplicación queda como sigue:
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class CookieCounter extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { String valor = null; int n=0; // contador, inicializado a su valor por defecto res.setContentType("text/html"); Cookie[] cs = req.getCookies(); if (cs!=null) for (int i=0; i<cs.length; i++) { if ((cs[i].getName()).equals("contador")) { valor = cs[i].getValue(); break; } } if (valor != null) n = Integer.parseInt (valor); n++; Cookie c = new Cookie ("contador", Integer.toString(n)); c.setMaxAge (60*60*24*365); // caduca al cabo de un año res.addCookie (c); PrintWriter out = res.getWriter(); out.println ("<HTML><BODY>"); out.println ("<H1>Numero de visitas: "+n); out.println ("</H1>"); out.println ("</BODY></HTML>"); out.close(); } }
La alternativa/complemento al uso de cookies es la utilización se sesiones en el servidor. Los servlets incluyen un API java denominado Session y diseñado específicamente para este fin.
Con este mecanismo, podemos desarrollar fácilmente aplicaciones que dependen de datos específicos de un usuario (ej.- para servicios personalizados). Los pasos a seguir son:
HttpSession s=req.getSession(); // obtiene objeto sesion de ese usuario HttpSession s=req.getSession(true); //idem, pero crea automat. objeto sesión si no existe s.isNew(); // cierto si se trata de una nueva sesión
Cada usuario recibe de forma automática un identificador de sesión único (si el navegador soporta cookies, el servlet crea automáticamente un cookie con el identificador, y si no el servlet intenta extraer el identificador de la URL)
int n = (int)s.getValue(nombre); //devuelve valor como tipo Object -> requiere cast s.getValueNames(); //devuelve lista de claves (vector de strings)
Otros métodos útiles sobre el objeto sesión son:
String getId(); //devuelve ID sesion boolean isNew(); // cierto si la sesión acaba de crearse long getCreationTime(); long getLastAccessTime();
void putValue(String clave, Object valor); //añade par <clave,valor> void removeValue(String clave); //elimina par <clave,valor> void invalidate(); // invalida la sesión void encodeURL(); // añade ID sesión a la URL
El servidor invalida y borra una sesión cuando ha transcurrido un periodo determinado (dependiente del servidor) sin accesos desde ese usuario. La sesión puede invalidarse manualmente con 'invalidate'.
Si un navegador no soporta cookies se añade de forma automática el identificador de sesión a la URL. Para ello utilizamos el método encodeURL (la implementación concreta depende del servidor)
String url = resp.encodeURL(urlOriginal);
Con este esquema 'toda' URL debe incluir el identificador de sesión (en instalaciones muy complejas resulta dificil ofrecer esa garantía).
El siguiente ejemplo registra el número de visitas desde cada usuario.
import java.io.*; import javax.servlet.*; import javax.servlet.http.*; import java.net.*; import java.util.*; public class ShowSession extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); HttpSession sesion = req.getSession(true); Integer n = (Integer)sesion.getValue("contadorAcceso"); String titular = n!=null?"Hola otra vez":"Hombre, un nuevo visitante!"; int valor = n!=null?n.intValue()+1:0; // actualiza conteo sesion.putValue("contadorAcceso", new Integer(valor)); out.println("<HTML>\n<HEAD>\n<TITLE>"+ "Ejemplo conteo acceso usando sesiones"+ "</TITLE>\n</HEAD>\n" + "<BODY BGCOLOR=\"#FDF5E6\">\n" + "<H1 ALIGN=\"CENTER\">" + titular + "</H1>\n" + "<H2>Información sobre tu sesión:</H2>\n" + "<TABLE BORDER=1 ALIGN=\"CENTER\">\n" + "<TR BGCOLOR=\"#FFAD00\">\n" + " <TH>Tipo información<TH>Valor\n" + "<TR>\n" + " <TD>ID\n" + " <TD>" + sesion.getId() + "\n" + "<TR><TD>Instante creación\n" + "<TD>"+new Date(sesion.getCreationTime()) + "\n" + "<TR><TD>Ultimo acceso\n" + " <TD>"+new Date(sesion.getLastAccessedTime()) + "\n" + "<TR>\n" + " <TD>Número de acceso previos\n" + " <TD>" + valor + "\n" + "</TR>"+ "</TABLE>\n</BODY></HTML>"); } public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {doGet(req, res);} }
JSP (Java Server Pages) es un mecanismo que permite incorporar código java dentro de páginas HTML. El objetivo básico es separar la presentación (HTML) del contenido dinámico (generado al ejecutar el código Java). Sin embargo, también podemos utilizar JSP para sustituir servlets en cualquier otra función (en definitiva un JSP es una abstracción de un servlet, excepto que trabajamos a mayor nivel).
Para ilustrar la utilización de JSP, los utilizaremos para procesar el mismo conjunto de formularios que en apartados previos gestionábamos utilizando servlets.
El siguiente fichero se denomina form0.html, y difiere del presentado al discutir servlets en el valor de la propiedad 'action' (el lugar de servlet/form0 utilizamos form0.jsp). La justificación es:
<HTML> <BODY> <H1>Indica tus datos personales</H1> <FORM action="form0.jsp" method="GET"> Nombre y apellidos: <INPUT type="text" name="nombre"> <INPUT type="text" name="ap1"> <INPUT type="text" name="ap2"> <BR> Departamento: <INPUT type="radio" checked name="depto" value="comercial">Comercial <INPUT type="radio" name="depto" value="desarrollo">Desarrollo <INPUT type="radio" name="depto" value="finanzas">Finanzas <BR> <P> Indica el rasgo que define mejor tu personalidad: <SELECT name="personalidad" <OPTION value="responsable"</OPTION>Responsable <OPTION value="trabajador"</OPTION>Trabajador <OPTION value="ambicioso"</OPTION>Ambicioso <OPTION value="nervioso"</OPTION>Nervioso <OPTION value="educado"</OPTION>Educado <OPTION value="estudioso"</OPTION>Estudioso <OPTION value="valiente"</OPTION>Valiente </SELECT> <BR> <INPUT type="submit"> </FORM> </BODY> </HTML>
El código que procesa el formulario anterior corresponde al fichero form0.jsp
<HTML> <BODY> <% // obtiene los valores del formulario String nom=request.getParameter("nombre"); String ap1=request.getParameter("ap1"); String ap2=request.getParameter("ap2"); String depto=request.getParameter("depto"); String personalidad=request.getParameter("personalidad"); %> <H1>Hola, <%=nom%> <%=ap1%> <%=ap2%>!</H1> Veo que a pesar de trabajar en el departamento <%=depto%> tienes un caracter <%=personalidad%> </BODY> </HTML>
El anterior ejemplo ilustra la utilización de las marcas (<% ... %> y <%= ... %>) para delimitar los fragmentos de código. El resto del texto es HTML puro.
En los bloques de código (entre <% y %>) se puede incluir cualquier conjunto de sentencias java válidas, incluyendo comentarios, declaraciones, y en general cualquier fragmento de código java.
Las marcas <%= ... %> permiten indicar una expresión (literal, variable, o combinación de literales y/o variables utilizando operadores y/o llamadas a métodos). Al evaluar una expresión se genera un resultado; el sistema transforma dicho resultado en una tira de caracteres, y escribe dicha cadena en lugar de <% ... %>. Este mecanismo es el que permite incorporar contenido dinámico (calculado al solicitar el documento).
Cada JSP puede acceder a un conjunto de objetos predefinidos, siendo los más importantes el objeto 'request' (con información sobre la petición desde el navegador) y el objeto 'out' (flujo de salida para devolver datos al navegador).El objeto 'request' se utiliza principalmente para obtener los valores de las variables del formulario, mediante los métodos 'getParameter(String nombre)', 'getParameterValues(nom)' y 'getParameterNames()'. Tambien sirve para acceder a cualquier otra información asociada a la solicitud (ej. getRemoteHost).
El objeto 'out' puede utilizarse para devolver datos de forma directa (desde un bloque de código), aunque normalmente se recurre a devolver datos al navegador utilizando expresiones (<%= ... %>).
Existe un objeto 'response', que modifica la forma en que se devuelve la respuesta (por ejemplo, response.setRedirect(url)' indica al navegador que debe cargar una URL distinta).
El método 'getParameter(nom)' devuelve únicamente el primer valor asociado a dicho nombre. Dado que resulta posible que un mismo nombre tenga varios valores asociados, para acceder a todos ellos debemos utilizar 'getParameterValues', que devuelve un vector de Strings conteniendo los distintos valores
El siguiente formulario (form1.html) contiene un campo con múltiples valores:
<HTML> <BODY> <H1>Introduce una lista de nombres</H1> <FORM action="form1.jsp" method="GET"> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="text" name="nombres"> <BR> <INPUT type="submit"> </FORM> </BODY> </HTML>
Procesamos dicho formulario mediante el fichero form1.jsp:
<HTML> <BODY> Parametros recibidos <PRE> <% // obtiene los valores del formulario String noms[]=request.getParameterValues("nombres"); for (int i=0; i<noms.length; i++) { out.println(noms[i]); %> </PRE> </BODY> </HTML>
Cuando deseamos obtener de forma dinámica el conjunto de nombres (conjunto de campos disponibles) utilizando 'getParameterNames()'. El resultado es de tipo Enumeration, sobre el cual podemos iterar mediante un bucle (utilizando los métodos 'hasMoreElements' y 'nextElement').
<HTML> <BODY> Parametros recibidos <PRE> <% // obtiene los valores del formulario java.util.Enumeration params= request.getParameterNames(); while (params.hasMoreElements()) { String p= (String) params.nextElement(); String v[]=request.getParameterValues(p); if (v.length==1) out.println(p+"="+v[0]); else { out.print(p+"="); for (int i=0; i<v.length; i++) { if (i>0) out.print(","); out.println(v[i]); } out.println(); } } %> </PRE> </BODY> </HTML>
La sintaxis <%@ ... %> permite incluir directivas JSP. Se trata de indicaciones necesarias para interpretar correctamente el fichero, pero no corresponden directamente a bloques de código ni expresiones. Podemos distinguir tres usos principales:
<%@ page import="java.util.*, java.text.*" %>
<%@ include file="otroFichero.jsp" %>
<jsp:forward page="Prueba.jsp"/>
El código java incluido en un JSP corresponde a código dentro de un único método de la clase final (cuando el sistema transforma el JSP en un servlet, el código java corresponde al contenido del método 'service' del servlet). Cualquier declaración de variable corresponde a una variable local dentro de dicho método.
Si deseamos declarar otros métodos (ej. métodos auxiliares) u otros campos de la clase, debemos utilizar un bloque de declaraciones, definido entre las marcas <%! ... %>.
NOTA.- aunque la declaración de métodos auxiliares puede simplificar el código, la declaración de nuevos campos entraña ciertos riesgos; cada petición a un servlet supone una nueva actividad (thread), y el acceso a objetos compartidos puede dar lugar a interferencias. Tenemos dos opciones:
El procesamiento de formularios utilizando JSP es similar al desarrollado utilizando servlets. A continuación se describen distintos ejemplos:
Procesamos el formulario form2.html:
<HTML> <HEAD> <TITLE>Cuestionario inicial</TITLE> </HEAD> <BODY> <H1>Cuestionario inicial</H1> <BR> <P> Te agradeceria que dedicases unos minutos a rellenar este cuestionario <BR> <FORM action="form2.jsp" method="POST"> 1.- Indica tu lenguaje de programacion preferido <SELECT name="lengupref" <OPTION value="pascal"</OPTION>Pascal <OPTION value="c"</OPTION>C <OPTION value="c++"</OPTION>C++ <OPTION value="delphi"</OPTION>Delphi <OPTION value="ada"</OPTION>Ada <OPTION value="java"</OPTION>Java <OPTION value="visual basic"</OPTION>Visual Basic <OPTION value="python"</OPTION>Python </SELECT> <P> 2.- Indica tu Sistema Operativo preferido <BR> <INPUT type="radio" name="sopref" checked value="linux">Linux <INPUT type="radio" name="sopref" value="windows">Windows <INPUT type="radio" name="sopref" value="mac">MacOS <P> 3.- Indica los lenguajes que has utilizado <BR> <INPUT type="checkbox" name="lengusa" value="pascal">Pascal <INPUT type="checkbox" name="lengusa" value="c">C <INPUT type="checkbox" name="lengusa" checked value="c++">C++ <INPUT type="checkbox" name="lengusa" value="java">Java <INPUT type="checkbox" name="lengusa" value="ada">Ada <INPUT type="checkbox" name="lengusa" value="python">Python <P> 4.- Tecnologias que piensas utilizar en un futuro proximo <BR> <SELECT name="tecno" multiple size=3> <OPTION value="corba"</OPTION>Corba <OPTION value="rmi"</OPTION>RMI <OPTION value="ejb"</OPTION>EJB <OPTION value="com"</OPTION>COM <OPTION value="xml"</OPTION>XML <OPTION value="wap"</OPTION>WAP </SELECT> <P> <BR> <INPUT type="submit" value="envia"> </FORM> </BODY> </HTML>
utilizando el JSP definido en el fichero form2.jsp
<%@ page language="java" import="java.io.*;" %> <HTML> <BODY> <% // obtiene los valores del formulario String lengupref =request.getParameter("lengupref"); String sopref =request.getParameter("sopref"); String[] lengusa =request.getParameterValues("lengusa"); String[] tecno =request.getParameterValues("tecno"); if (lengusa==null) lengusa= new String[0]; if (tecno==null) tecno = new String[0]; // escribe los valores en un fichero try { PrintWriter fout= new PrintWriter(new FileWriter("\\tmp\\datos",true)); fout.println(lengupref); fout.println(sopref); for (int i=0; i<lengusa.length; i++) { if (i>0) fout.print(", "); fout.print(lengusa[i]); } for (int i=0; i<tecno.length; i++) { if (i>0) fout.print(", "); fout.print(tecno[i]); } fout.println(); fout.println("----------"); fout.close(); %> <H1>Gracias</H1> Gracias por participar en nuestra encuesta <% } catch (IOException e) { application.log("error al grabar resultados",e); %> <H1>Error</H1> No se han podido grabar las respuestas debido a un error en el servidor <% } %> </BODY> </HTML>
Asumimos la generación dinámica de exámenes tipo test, obteniendo los datos (preguntas y posibles respuestas) desde un fichero de texto.
<%@ page language="java" import="java.io.*;" %> <%@ page language="java" import="java.util.*;" %> <HTML> <BODY> <% try { String dir= System.getProperty("form4.dir"); File f=null; if (dir!=null) f= new File(dir,"form4.in"); else f=new File("form4.in"); BufferedReader in= new BufferedReader(new FileReader(f)); String pregunta=in.readLine(), linea; Vector resp= new Vector(); // recoge las respuestas while ((linea=in.readLine()) != null) resp.addElement(linea); in.close(); // muestra la pregunta y las posibles respuestas %> <FORM action="form4b.jsp" method="POST"> <B> <%= pregunta%> </B> <BR> <% Enumeration e= resp.elements(); while (e.hasMoreElements()) { String s= (String) e.nextElement(); %> <INPUT type="radio" name="respuesta" value="<%=s%>"> <%=s%> <BR> <% } >% <P> <INPUT type="submit" value="envia"> </FORM> <% } catch (Exception e) { %> <H1>Error</H1> El servidor no esta disponible en este momento <% } %> </BODY> </HTML>
y para procesarlo:
<%@ page language="java" import="java.io.*;" %> <%@ page language="java" import="java.util.*;" %> <HTML> <BODY> <% String resp= request.getParameter("respuesta"); if (resp==null) { %> <H1>OJO</H1> No has contestado a la pregunta <% return; } try { String dir= System.getProperty("form4.dir"); File f=nul, f2=null; if (dir != null) { f=new File(dir,"form4b.dat"); f2=new File(dir,"form4b.tmp"); } else { f=new File("form4b.dat"); f2=new File("form4b.tmp"); } boolean fin=false; for (int i=0; i<30; i++) { if (f2.createNewFile()) {fin=true; break;} try{ Thread.sleep(1000); } catch (Exception e) {} } if (!fin) throws new IOException("no puedo reservar el fichero"); Vector conteo=new Vector, respuestas= new Vector(); int nresp=0; try { BufferedReader in= new BufferedReader(new FileReader(f)); PrintWriter out= new PrintWriter(new FileWriter(f2)); String linea; boolean hayResp=false; while ((linea=in.readLine()) != null) { // cada linea es de la forma "conteo:respuesta" int pos= linea.indexOf(':'); if (pos<0) continue; // obviamos esa linea String c,r; c=linea.substring(0,pos); r=linea.substring(pos+1); int contador=0; try { contador=Integer.parseInt(c); } catch (Exception e) {} if (r.equals(getParameter("resp")) { hayResp=true; contador++; } nresp+=contador; out.println(contador+":"+r); respuestas.addElement(r); conteo.addElement(new Integer(contador)); } out.close(); in.close(); f.delete(); f2.renameTo(f); // reemplaza fichero original // si la respuesta no aparece en el fichero mostramos el error // inicialmente deberia existir el fichero con todas las respuestas // y contadores a 0 if (!hayResp) { %> <H1>Respuesta inesperada<H1> Tu respuesta no coincide con ninguna de las propuestas <% return; } } catch (IOException e) { try { f2.delete(); } catch (Exception e) {} %> <H1>Error</H1> El servidor no esta disponible en este momento <% return; } %> <H2>Gracias por tu colaboracion</H2> <P> <B>Estos son los resultados:</B> <P> </BODY> </HTML>
Inicialmente (la primera vez que se usa la página) debe generarse el formulario inicial, y posteriormente se presenta siempre el estado actual del formulario. Cuando se solicita un formulario debemos determinar si es o no la primera vez que se utiliza la página
<HTML> <BODY> <% String nombre= request.getParameter("nombre"); String ap1= request.getParameter("ap1"); String ap2= request.getParameter("ap2"); String direc= request.getParameter("direc"); String telf= request.getParameter("telf"); String email= request.getParameter("email"); String formato= request.getParameter("formato"); if (nombre==null) nombre=""; if (ap1==null) nombre=""; if (ap2==null) ap2=""; if (direc==null) direc=""; if (telf==null) telf=""; if (email==null) email=""; if (formato==null) formato=""; // fija estilo por defecto para las etiquetas obligatorias String colorNombre="black", colorAp1="black", colorAp2="black", colorNotificacion="red"; if (request.getMethod().equals("POST")) { boolean estaTodo=true; if (nombre.length==0) {colorNombre=colorNotificacion; estaTodo=false;} if (ap1.length==0) {colorAp1=colorNotificacion; estaTodo=false;} if (ap2.length==0) {colorAp2=colorNotificacion; estaTodo=false;} if (!estaTodo) { %> Faltan valores en campos requeridos. Los campos a rellenar están marcados en <FONT color=<%= colorNotificacion%>> <%= colorNotificacion%> </FONT> <% } else { // muestra nombre y direccion String nom= ap1+" "+ap2+" "+nombre+"<BR>"+dir+"<BR>"+email+" "+telf; out.println("el nombre actual es: <P>"); if (formato.equals("negrita")) out.println("<B>"+nom+"</B>"); else if (formato.equals("negrita")) out.println("<I>"+nom+"</B>"); else out.println(nom); out.println("<P>"); } } %> <FORM action="tarjeta.jsp" method="POST"> <TABLE> <TR><TD>Nombre: <TD> <INPUT type="text" name="nombre" value=<%= nombre%> <TD><FONT color="<%= colorNombre%>">requerido</FONT> <TR><TD>Primer apellido: <TD> <INPUT type="text" name="ap1" value=<%= ap1%> <TD><FONT color="<%= colorAp1%>">requerido</FONT> <TR><TD>Segundo apellido: <TD> <INPUT type="text" name="ap2" value=<%= ap2%> <TD><FONT color="<%= colorAp2%>">requerido</FONT> <TR><TD>Dirección: <TD> <INPUT type="text" name="direc" value=<%= direc%> <TR><TD>Teléfono: <TD> <INPUT type="text" name="telf" value=<%= telf%> <TR><TD>Correo electrónico: <TD> <INPUT type="text" name="email" value=<%= email%> <TR><TD>Formato: <TD> <INPUT type="text" name="formato" value=<%= formato%> </TABLE> <P> Opciones de formato: <SELECT name="formato"> <OPTION value="normal">Normal</OPTION> <OPTION value="negrita">Negrita</OPTION> <OPTION value="italica">Italica</OPTION> </SELECT> <P> <INPUT type="submit" value="Nueva tarjeta"> </FORM> </BODY> </HTML>
Recordamos los conceptos fundamentales desarrollados en la teoría:
La clase 'DriverManager':
Las clases 'Driver' poseen un inicializador static que crea una instancia e invoca DriverManager.registerDriver()
jdbc.drivers=sun.jdbc.odbc.JdbcOdbcDriver;
jdbc:odbc:mibd jdbc:dcenaming:nominas jdbc:netdb//jsendra:1088/art jdbc:odbc:mibd;UID=js;PWD=patata
Class.forName('sun.jdbc.odbc.JdbcOdbcDriver');
Conection conn = DriverManager.getConnection('jdbc:odbc:mibd','','');
Statement sent = conn.createStatement();
ResultSet rs = sent.executeQuery( "SELECT AP1, NOM, TOTAL" + "FROM estudiantes" + "ORDER BY TOTAL DESC");
while (rs.next()) { String s= rs.getString('NOM') + " " + rs.getString("AP1") + ": " + rs.getString("TOTAL"); System.out.println(s); }
Los siguientes ejemplos asumen una configuración concreta:
En consecuencia, la conexión se consigue con las sentencias
Class.forName("k.jdbc"); Connection c = DriverManager.getConnection("jdbc:kx://dir:2003/mibd");
siendo 'dir' la dirección de red de la máquina servidor (se indicará en el propio laboratorio) y mibd la BD a la que se desea acceder. Para probar estos ejemplos en otros entornos sería necesario modificar dichas líneas.
Class.forName('sun.jdbc.odbc.JdbcOdbcDriver'); Conection c = DriverManager.getConnection('jdbc:odbc:mibd','','');
El primer ejemplo sirve como comprobación del funcionamiento, y no requiere una Base de Datos previa (el propio código crea una tabla y realiza posteriormente una interrogación)
import java.sql.*; public class test{ public static void main(String args[]){ try{ Class.forName("k.jdbc"); //carga el driver Connection c = DriverManager.getConnection("jdbc:kx://dir:2003"); Statement e = c.createStatement(); e.execute("create table t(i int,f float,s varchar,d date,t time,z timestamp,b varbinary)"); PreparedStatement p = c.prepareStatement("insert into t values(?,?,?,?,?,?,?)"); p.setInt(1,2); p.setDouble(2,2.3); p.setString(3,"asd"); p.setDate(4,Date.valueOf("2000-01-01")); p.setTime(5,Time.valueOf("12:34:56")); p.setTimestamp(6,Timestamp.valueOf("2000-01-01 12:34:56")); byte[] b=new byte[2]; b[0]=0x61;b[1]=0x62; p.setBytes(7,b); p.execute(); ResultSet r = e.executeQuery("select * from t"); ResultSetMetaData m= r.getMetaData(); int n=m.getColumnCount(); for(int i=0;i<n;) System.out.println(m.getColumnName(++i)); while(r.next()) for(int i=0;i<n;) System.out.println(r.getObject(++i)); c.close(); } catch(Exception e){System.out.println(e.getMessage());} } }
Para el segundo ejemplo suponemos almacenada en el servidor la BD 'tutor', desarrollada en el correspondiente tema de teoría. Podemos ilustrar la interrogación mediante el siguiente ejemplo:
import java.sql.*; public class EjTutor { public static void main(String[] arg) { try { Class.forName("k.jdbc"); Connection c = DriverManager.getConnection("jdbc:kx://dir:2003/tutor.t"); Statement sent = c.createStatement(); ResultSet rs = sent.executeQuery("$select numMat, nota from notas"); while (rs.next()) { String s= rs.getString("numMat") + " " + rs.getString("nota"); System.out.println(s); } sent.close(); } catch (Exception e) {e.printStackTrace();} } }
Puedes probar otras interrogaciones. A continuación se indican algunas de las posibilidades:
$select nombre from alumno where numMat=3567 $select avg(nota) from notas where asig='Calculo' $select count(numMat) from notas where (asig='Calculo' and nota>=5) or (asig='Diseño' and nota>=5) $select numMat from notas where asig='Algebra' and nota<5 $select numTelf from hab where numHab='120D' $select alumno.nombre from alumno, notas where (alumno.numMat=notas.numMat) and (notas.asig='Algebra' and notas.nota<5) $insert into notas (numMat, asig, conv, nota) values (4356,'Fisica','dic01',5.6) $select alumno.nombre from alumno, notas where alumno.numMat=notas.numMat and notas.asig='Calculo' and (notas.nota > (select avg(nota) from notas where asig='Calculo'))
El servidor también dispone de las bases de datos "piezas" y "cafe", utilizadas como ejemplos en la parte de teoría. Pueden probarse interrogaciones/actualizaciones sobre dichas BD.