4.1 Tutorial de servlets Introducción (1) Un servlet es una clase Java que puede recibir peticiones (normalmente HTTP) y generar una salida (normalmente HTML, WML o XML) Los servlets que conforman una aplicación web se ejecutan en un servidor de aplicaciones web (contenedor) Cada servlet se puede asociar a una o más URLs Paquetes javax.servlet y javax.servlet.http Introducción (y 2) Hasta la aparición de JSP, el uso principal de los servlets era generar la vista de una aplicación web Recibir petición HTTP asociada a una URL Leer parámetros Invocar operación sobre el modelo Generar salida HTML Motivos para aprender servlets Para usar JSP y Struts es preciso conocer parte del API de los servlets, dado que aunque JSP y Struts están en un nivel superior, no ocultan el API de los servlets Son útiles para implementar el controlador de una aplicación web Apache Struts proporciona un servlet genérico que constituye la parte principal del controlador Visión global del framework de servlets (1) <<interface>> javax.servlet.Servlet + init(config : ServletConfig) : void + destroy() : void + service(request : ServletRequest, response : ServletResponse) : void javax.servlet.GenericServlet + init(config : ServletConfig) : void + destroy() : void + service(request : ServletRequest, response : ServletResponse) : void javax.servlet.http.HttpServlet + # # # service(request : ServletRequest, response : ServletResponse) : void service(request : HttpServletRequest, response : HttpServletResponse) : void doGet(request : HttpServletRequest, response : HttpServletResponse) : void doPost(request : HttpServletRequest, response : HttpServletResponse) : void Visión global del framework de servlets (2) <<interface>> javax.servlet.ServletRequest + getParameter(name : String ) : String + getParameterValues(name : String) : String[] <<interface>> javax.servlet.http.HttpServletRequest <<interface>> javax.servlet.ServletResponse + setContentType(type : String ) : void + getWriter () : java.io.PrintWriter <<interface>> javax.servlet.http.HttpServletResponse Visión global del framework de servlets (3) El diagrama UML anterior sólo ofrece una visión muy reducida del API de servlets, pero suficiente en este momento Cuando el servidor de aplicaciones web decide cargar un servlet en memoria (ej.: al arrancar, la primera vez que se accede a él) llama a la operación init de javax.servlet.Servlet Cuando el servidor de aplicaciones web decide eliminar un servlet de memoria (ej.: lleva cierto tiempo sin usarse), llama a la operación destroy de javax.servlet.Servlet Visión global del framework de servlets (4) La operación pública service de javax.servlet.http.HttpServlet llama a la operación protegida service Esta operación es una operación plantilla (Template Method), que llama a doGet, doPost, doPut, doDelete, etc., según la petición HTTP sea un GET, POST, PUT, DELETE, etc. Normalmente, el programador extiende de javax.servlet.http.HttpServlet y redefine doGet y/o doPost Visión global del framework de servlets (5) En una máquina virtual Java, sólo existe una instancia de cada servlet que se programe, y en consecuencia puede recibir peticiones concurrentes doGet, doPost, etc. deben ser thread-safe Normalmente no será necesario hacer nada especial, dado que la implementación de estas operaciones generalmente sólo hace uso de variables locales (pila) o de variables globales (static) de sólo lectura (típicamente caches) Si modifican alguna estructura global (un atributo propio o alguna variable global), necesitan sincronizar su acceso Sin embargo, en general, eso es mala idea, dado que una aplicación con estas características no será fácil que funcione en un entorno en cluster En estos casos, es mejor usar una base de datos para las estructuras globales que sean de lectura/escritura Visión global del framework de servlets (y 6) javax.servlet.http.HttpServletRequest getParameter Permite obtener el valor de un parámetro univaluado getParameterValues Permite obtener el valor de un parámetro multivaluado También se puede usar con parámetros univaluados IMPORTANTE: cuando el usuario no selecciona ningún valor (ej.: en una lista desplegable múltiple o una lista de checkboxes en HTML), el navegador no envía el parámetro, y en consecuencia, esta operación devuelve null javax.servlet.http.HttpServletResponse setContentType debe llamarse antes de escribir en el PrintWriter que devuelve getWriter Página principal del tutorial Index.jsp <html> ... <ul> <li><a href="Hello1/HelloUser?userName=Fernando+Bellas"> HelloUser by GET (userName = Fernando Bellas)</a></li> <li><a href="Hello1/HelloUserByPost.html">HelloUser by POST</a></li> <li><a href="Portal1/MainPage">Portal-1 main page</a></li> </ul> ... </html> Portal1/HelloUserByPost.html <html> ... <form method="POST" action="HelloUser"> <table width="100%" border="0" align="center" cellspacing="12"> <tr> <th align="right" width="50%">User name</th> <td align="left"> <input type="text" name="userName" size="16“ maxlength="16"> </td> </tr> <tr> <td width="50%“></td> <td align="left" width="50%"> <input type="submit" value="Say me hello"> </td> </tr> </table> </form> ... </html> Demo HelloUser (1) Servlet and JSP Tutorial Main Page Clic en HelloUser by GET (userName = Fernando Bellas) Demo HelloUser (y 2) Servlet and JSP Tutorial Main Page Clic en HelloUser by POST es.udc.fbellas.j2ee.servjsptutorial.hellouser1.HelloUserServlet (1) package es.udc.fbellas.j2ee.servjsptutorial.hellouser1; import java.io.PrintWriter; import java.io.IOException; import import import import javax.servlet.ServletException; javax.servlet.http.HttpServlet; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; public class HelloUserServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { /* Get value of parameter "userName". */ String userName = request.getParameter("userName"); /* Generate response. */ response.setContentType("text/html; charset=ISO-8859-1"); es.udc.fbellas.j2ee.servjsptutorial.hellouser1.HelloUserServlet (y 2) PrintWriter out = response.getWriter(); out.println("<html><head><title>"); out.println("HelloUser response"); out.println("</title></head>"); out.println("<body text=\"#000000\" bgcolor=\"#ffffff\">"); out.println("<h1>Hello " + userName + "! </h1>"); out.println("</body></html>"); out.close(); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); } } Empaquetamiento de una aplicación web (1) Una aplicación web se empaqueta en un fichero .war jar cvf aplicacionWeb.war directorio Opciones similares al comando Unix tar El nombre de una aplicación web no tiene porque coincidir con el de su fichero .war El nombre se decide al instalar el fichero .war Ant incluye la tarea interna war Estructura de un fichero .war Directorio WEB-INF/classes Ficheros .class que conforman la aplicación web, agrupados en directorios según su estructura en paquetes ¡ Sin ficheros fuente ! Directorio WEB-INF/lib Ficheros .jar de librerías que usa la aplicación ¡ Sin ficheros fuente ! Empaquetamiento de una aplicación web (2) Estructura de un fichero .war (cont) WEB-INF/web.xml Configuración estándar de la aplicación web Si la aplicación necesita otros ficheros de configuración (ej.: ficheros de configuración de Struts, ficheros de configuración propios, scripts de creación de tablas, etc.), se recomienda incluirlos debajo de WEB-INF (seguramente en subdirectorios) Directorio raíz y subdirectorios Vista de la aplicación (ej.: ficheros HTML, páginas JSP, imágenes, etc.) Visible a los navegadores Lo que hay debajo de WEB-INF sólo es visible a los servlets y páginas JSP de la aplicación Un fichero .war se puede instalar (deployment) en cualquier servidor de aplicaciones web conforme a J2EE Empaquetamiento de una aplicación web (y 3) Un contenedor de aplicaciones web usa un cargador de clases distinto para cada war instalado => independencia entre aplicaciones web Distintas aplicaciones web pueden usar versiones distintas de una misma clase Dos aplicaciones web nunca podrán compartir una variable global Existe una instancia de un Singleton por cada aplicación que lo use Ej.: Considerar el atributo estático (variable global) parameters de ConfigurationParametersManager Si dos aplicaciones web usan esta clase, el contenedor cargará una clase por cada aplicación, y en consecuencia parameters tendrá un valor distinto en cada aplicación Es lo que deseamos, dado que en general, la configuración será distinta jar tvf ServJSPTutorial.war (1) Portal2/MainPage.jsp Portal2/ShowLogin.jsp Portal2/ProcessLogin.jsp Portal2/ProcessLogout.jsp Hello2/HelloUserByPost.html Hello2/HelloUser.jsp Hello1/HelloUserByPost.html Index.jsp WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/portal1/ ShowLoginServlet.class WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/portal1/ MainPageServlet.class WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/portal1/ LoginManager.class WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/portal1/ ProcessLoginServlet.class WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/portal1/ ProcessLogoutServlet.class jar tvf ServJSPTutorial.war (y 2) WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/portal2/ LoginManager.class WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/portal2/ IncorrectPasswordException.class WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/portal2/ LoginForm.class WEB-INF/classes/es/udc/fbellas/j2ee/servjsptutorial/hellouser1/ HelloUserServlet.class WEB-INF/web.xml WEB-INF/web.xml (1) <?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> <distributable/> <!-- ===================== Servlets ============================= --> <servlet> <servlet-name>HelloUser</servlet-name> <servlet-class>es.udc.fbellas.j2ee.servjsptutorial.hellouser1. HelloUserServlet</servlet-class> </servlet> ... Resto de servlets del tutorial ... WEB-INF/web.xml (y 2) <!-- ==================== Servlet mapping ======================= --> <servlet-mapping> <servlet-name>HelloUser</servlet-name> <url-pattern>/Hello1/HelloUser</url-pattern> </servlet-mapping> ... Mappings para el resto de servlets del tutorial ... <!-- ========================= Session =========================== --> <session-config> <session-timeout>30</session-timeout> </session-config> <!-- ======================= Welcome page ======================== --> <welcome-file-list> <welcome-file>Index.jsp</welcome-file> </welcome-file-list> </web-app> Comentarios (1) Desde la versión 2.4 de la especificación de servlets, los tags que se pueden usar dentro del fichero web.xml están especificados en un esquema (anteriormente lo estaban en un DTD) Espacios de nombres Concepto similar al de paquete en Java Permiten evitar conflictos de nombres cuando en un documento XML se usan tags de distintas aplicaciones XML Cada espacio de nombres está asociado a una URI, que debe ser única Se aconseja usar URLs No tienen porque tener una existencia real (y de hecho, no suelen tenerla) Comentarios (2) En el fichero web.xml se usa <web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"> xmlns="http://java.sun.com/xml/ns/j2ee" permite especificar que el elemento web-app y los que éste contiene pertenecen al espacio de nombres definido por la URI http://java.sun.com/xml/ns/j2ee “Similar a package” xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" permite especificar que el elemento web-app y los que éste contiene pueden usar los atributos y elementos del espacio de nombres asociado con la URI http://www.w3.org/2001/XMLSchema-instance, prefijándolos con xsi: “Similar a import” Es la URI la que define el espacio de nombres y no el prefijo Distintas documentos XML pueden usar distintos prefijos para referirse a un mismo espacio de nombres Comentarios (3) En el fichero web.xml se usa (cont) El atributo xsi:schemaLocation permite especificar las ubicaciones de los esquemas asociados a un conjunto de espacios de nombres Contiene una lista de pares de valores separados por blancos (xsi:schemaLocation="en1 ue1 ... enN ueN") El primer miembro especifica un espacio de nombres (en) El segundo miembro especifica la ubicación del esquema (ue) que define los elementos y atributos de ese esquema El procesador del documento XML no está obligado a hacer uso de las ubicaciones especificadas (e.g. puede tener copias locales de los esquemas) Cualquier procesador tiene una copia del esquema correspondiente al espacio de nombres http://www.w3.org/2001/XMLSchema-instance, y por eso no se especifica en xsi:schemaLocation Cualquier contenedor de aplicaciones web J2EE posiblemente tendrá una copia del esquema correspondiente al espacio de nombres http://java.sun.com/xml/ns/j2ee Comentarios (y 4) El anterior fichero web.xml sólo muestra algunos tags típicos distributable Existe un gran número de tags (la mayoría opcionales) que permiten expresar muchas opciones de configuración Iremos viendo ejemplos de los tags más típicos a medida que nos hagan faltan La aplicación puede funcionar en cluster Nuestras aplicaciones siempre deberían estar diseñadas e implementadas para que puedan funcionar en cluster Los servidores de aplicaciones comerciales suelen generar el fichero web.xml automáticamente El usuario utiliza un cómodo wizard para especificar la información de configuración Tipos de URLs en HTML (1) Las URLs que muestran las imágenes del navegador empiezan por http://localhost:8080/ServJSPTutorial Son URLs absolutas localhost:8080: máquina y puerto en el que corre el servidor de aplicaciones (en este caso Tomcat) /ServJSPTutorial: nombre de la aplicación web En un servidor de aplicaciones pueden instalarse varias aplicaciones web Las URLs que se usan en los ficheros HTML o en las respuestas generadas del ejemplo son de tipo path relativo, y en consecuencia no incluyen http://localhost:8080/ServJSPTutorial Buena idea, permite instalar la aplicación en otro servidor bajo un nombre distinto Tipos URLs en HTML (y 2) Si se desea escribir una URL de tipo path absoluto en un fichero HTML o una respuesta generada, ésta ha de empezar por /nombreAplicación <a href=“/ServJSPTutorial/Hello1/HelloUser? userName=Fernando+Bellas”> HelloUser by GET userName = Fernando Bellas</a> Es posible generar automáticamente la parte del nombre de la aplicación Permite instalar la aplicación con otro nombre, sin que haya que modificar ningún servlet request.getContextPath() devuelve /nombreAplicación Volveremos a hablar sobre tipos de URLs en el siguiente ejemplo (Portal-1) Demo Portal-1 (1) Servlet and JSP Tutorial Main Page Clic en Portal-1 main page Demo Portal-1 (2) Demo Portal-1 (y 3) Clic en Servlet and JSP tutorial main page Servlet and JSP Tutorial Main Page Clic en Portal-1 main page Clic en Logout Servlet and JSP Tutorial Main Page Clic en Portal-1 main page Portal-1 login form es.udc.fbellas.j2ee.servjsptutorial.portal1 HttpS ervlet (fro m h ttp ) M ainP ageS ervlet < < us e> > S howLoginS ervlet P rocess LoginS ervlet < < us e> > P roc es sLogoutS ervlet < < us e >> Log inM anager - Logi nMana ger() < < static >> + login(reques t : Ht tpS ervl etReques t, l oginNam e : S tring) : void < < s tatic> > + logout(request : HttpS ervletRequest) : void < < s tatic> > + getLoginNam e(request : HttpS ervletRequest) : S tring Concepto de sesión (1) El API de servlets permite crear una sesión en el servidor por cada navegador que accede a una aplicación web Cada navegador dispone de su propio objeto javax.servlet.http.HttpSession en el servidor Es posible enganchar objetos a una sesión y recuperarlos void setAttribute(String, Object) Object getAttribute(String) Por motivos de escalabilidad y de que en HTTP no hay nada especial que indique que un navegador ha dejado de usar la aplicación web, cada sesión dispone de un timeout (en minutos) Si transcurre el timeout sin que el navegador acceda a la aplicación, el servidor destruye la sesión Configurable en web.xml Concepto de sesión (2) En el ejemplo Cada vez que un usuario hace un login, crearemos una sesión, le engancharemos su nombre de login, y lo redirigiremos a la página principal del portal Cada vez que el usuario accede a la página principal del portal, si ya ha hecho el login, se le saludará por su nombre (se recupera de la sesión) Cada vez que el usuario accede a la página principal del portal, si todavía no ha hecho el login, o su sesión ha caducado, se le redirigirá a la página de login Cuando un usuario hace un logout, le destruiremos la sesión y lo redirigiremos a la página principal del tutorial Concepto de sesión (y 3) ¿ Qué ocurre cuando el servidor de aplicaciones web se ejecuta en un cluster (distributable) ? El servidor garantiza que todas las peticiones que sean parte de una sesión serán procesadas por una máquina virtual Los objetos de la sesión tienen que ser serializables El servidor de aplicaciones puede decidir moverlos a otra máquina por motivos de escalabilidad y/o tolerancia a fallos En realidad, el concepto de sesión tiene más matices HTTP es un protocolo sin estado ¿ Cómo sabe el servidor de aplicaciones a qué sesión está asociada cada petición HTTP que recibe ? Mecanismos: típicamente cookies o URL rewriting Los estudiaremos en el siguiente apartado es.udc.fbellas.j2ee.servjsptutorial.portal1.MainPageServlet (1) public class MainPageServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String loginName = LoginManager.getLoginName(request); if (loginName != null) { // User has already logged in. generateMainPage(response, loginName); } else { // User has not logged in yet. response.sendRedirect("ShowLogin"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); } es.udc.fbellas.j2ee.servjsptutorial.portal1.MainPageServlet (y 2) private void generateMainPage(HttpServletResponse response, String loginName) throws IOException { response.setContentType("text/html; charset=ISO-8859-1"); PrintWriter out = response.getWriter(); out.println("<html><head><title>"); out.println("Portal-1 main page"); out.println("</title></head>"); out.println("<body text=\"#000000\" bgcolor=\"#ffffff\" " + "link=\"#000ee0\" vlink=\"#551a8b\" alink=\"#000ee0\">"); out.println("<h1>Hello " + loginName + "! </h1>"); out.println("<br><br><br>"); out.println("<a href=\"../Index.jsp\">Servlet and JSP tutorial " + "main page</a><br>"); out.println("<a href=\"ProcessLogout\">Logout</a><br>"); out.println("</body></html>"); out.close(); } } es.udc.fbellas.j2ee.servjsptutorial.portal1.ShowLoginServlet (1) public class ShowLoginServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { /* Get values to show. */ Map errors = (Map) request.getAttribute("errors"); String loginName = ""; String loginNameErrorMessage = ""; if (errors != null) { String errorHeader = "<font color=\"red\"><b>"; String errorFooter = "</b></font>"; if (errors.containsKey("loginName")) { loginName = request.getParameter("loginName"); loginNameErrorMessage = errorHeader + errors.get("loginName") + errorFooter; } } es.udc.fbellas.j2ee.servjsptutorial.portal1.ShowLoginServlet (2) /* Generate response. */ response.setContentType("text/html; charset=ISO-8859-1"); PrintWriter out = response.getWriter(); /* Header and begin of body. */ out.println("<html><head><title>"); out.println("Portal-1 login form"); out.println("</title></head>"); out.println("<body text=\"#000000\" bgcolor=\"#ffffff\">"); /* Begin of form. */ out.println("<form method=\"POST\" action=\"ProcessLogin\">"); /* Begin of table. */ out.println("<table width=\"100%\" border=\"0\" align=\"center\" " + "cellspacing=\"12\">"); es.udc.fbellas.j2ee.servjsptutorial.portal1.ShowLoginServlet (3) /* Login name. */ out.println("<tr>"); out.println("<th align=\"right\" width=\"50%\"> Login name </th>"); out.println("<td align=\"left\">" + "<input type=\"text\" name=\"loginName\" " + " value=\"" + loginName + "\" size=\"16\" maxlength=\"16\">" + loginNameErrorMessage + "</td>"); out.println("</tr>"); /* Print Login button. */ out.println("<tr>"); out.println("<td width=\"50%\"></td>"); out.println("<td align=\"left\" width=\"50%\"> " + "<input type=\"submit\" value=\"Login\"></td>"); out.println("</tr>"); es.udc.fbellas.j2ee.servjsptutorial.portal1.ShowLoginServlet (y 4) /* End of table. */ out.println("</table>"); /* End of body and html. */ out.println("</body></html>"); /* Close "out". */ out.close(); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); } } es.udc.fbellas.j2ee.servjsptutorial.portal1.ProcessLoginServlet (1) public class ProcessLoginServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String loginName = request.getParameter("loginName"); if ( (loginName == null) || (loginName.trim().length() == 0) ) { Map errors = new HashMap(); errors.put("loginName", "Mandatory field"); request.setAttribute("errors", errors); forwardToShowLogin(request, response); } else { LoginManager.login(request, loginName.trim()); response.sendRedirect("MainPage"); } } es.udc.fbellas.j2ee.servjsptutorial.portal1.ProcessLoginServlet (y 2) public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doPost(request, response); } private void forwardToShowLogin(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { RequestDispatcher requestDispatcher = request.getRequestDispatcher("/Portal1/ShowLogin"); requestDispatcher.forward(request, response); } } es.udc.fbellas.j2ee.servjsptutorial.portal1.ProcessLogoutServlet public class ProcessLogoutServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { LoginManager.logout(request); response.sendRedirect("../Index.jsp"); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { doGet(request, response); } } es.udc.fbellas.j2ee.servjsptutorial.portal1.LoginManager (1) public final class LoginManager { private final static String LOGIN_NAME_SESSION_ATTRIBUTE = "loginName"; private LoginManager() {} public final static void login(HttpServletRequest request, String loginName) { HttpSession session = request.getSession(true); session.setAttribute(LOGIN_NAME_SESSION_ATTRIBUTE, loginName); } public final static void logout(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session != null) { session.invalidate(); } } es.udc.fbellas.j2ee.servjsptutorial.portal1.LoginManager (y 2) public final static String getLoginName(HttpServletRequest request) { HttpSession session = request.getSession(false); if (session == null) { return null; } else { return (String) session.getAttribute( LOGIN_NAME_SESSION_ATTRIBUTE); } } } Comentarios (1) El ejemplo, aparte de ilustrar el uso de sesiones, hace énfasis en el uso de sendRedirect y forward sendRedirect Le decimos al navegador que nos haga una nueva petición a otra URL forward Nos movemos a otra URL Ocurre dentro del servidor Se conserva todo lo que había en la request Útil para tratar errores en formularios Los servlets de procesamiento enganchan el atributo errors (un Map) en la request Los servlets que muestran formularios comprueban si la request incluye el atributo errors Comentarios (2) Más sobre tipos de URLs Los tipos de URLs que hemos visto al principio de este apartado se refieren a las URLs en ficheros HTML, páginas JSP o respuestas generadas ¿ Qué tipos de URLs acepta sendRedirect ? Los tres tipos que conocemos hasta ahora Lógico, pues un sendRedirect se usa para informar al navegador que nos haga una petición a otra URL ¿ Qué tipos de URLs acepta forward ? De tipo path relativo (sin salir de la aplicación) De tipo path relativo a contexto Empiezan por “/” y no incluyen el nombre de la aplicación Ej.: /Portal1/MainPage Existe otra manera de hacer forwards distinta a la mostrada en el ejemplo, que sólo acepta URLs de tipo path relativo a contexto Servlet.getServletContext().getRequestDispatcher (String) Comentarios (y 3) ¿ Cuándo usar forward y cuándo sendRedirect ? En principio, un forward siempre es más rápido (ocurre en el servidor) Un forward es preciso cuando queremos enganchar atributos a la request Ej.: Tratamiento de errores en formularios Para el resto de situaciones, es mejor usar un sendRedirect, dado que forward no cambia la URL que muestra la caja de diálogo del navegador (el navegador no se entera de que se hace un forward), lo que será confuso si el usuario decide recargar la página (se invocará a la URL antigua que todavía muestra la caja de diálogo del navegador)