En este tema vamos a aprender a centralizar la gestión de todas las rutas de nuestra web con un Front Controller, de manera que podamos servir tanto páginas HTML dinámicas como recursos estáticos (CSS, JS, imágenes).
Nuestro objetivo será:
miweb/ miweb/ ├── src/ │ └── main/ │ ├── java/ <-- paquetes con clases Servlet │ │ └── es/cesguiro/servlets/web/ │ │ └── FrontController.java │ └── webapp/ │ ├── index.jsp <-- archivo JSP inicial (lo borramos) │ ├── WEB-INF/ │ │ ├── web.xml <-- descriptor web (opcional en Servlet 3+) │ └── META-INF/ <-- opcional │ ├── index.html <-- HTML principal │ ├── books.html <-- página de libros │ ├── css/ <-- css básico │ │ └── style.css │ └── js/ <-- js básico │ └── script.js
<link rel="stylesheet" href="css/style.css"> <script src="js/script.js"></script>
Un Front Controller es un patrón muy habitual en aplicaciones web: consiste en un único servlet que actúa como punto de entrada de toda la aplicación. Todas las peticiones HTTP pasan primero por él, y desde allí se decide qué lógica ejecutar o qué página devolver.
En nuestro proyecto, el FrontController:
package es.cesguiro.servlets.web; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; @WebServlet(name = "frontController", urlPatterns = "/*") public class FrontController extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String path = request.getPathInfo(); if (path == null || path.equals("/") || path.isEmpty()) { path = "/index.html"; } // Intentar servir un recurso estático (CSS, JS, imágenes) if (handleStaticResource(request, response, path)) { return; } // Rutas dinámicas switch (path) { case "/books": request.getRequestDispatcher("/books.html").forward(request, response); break; default: response.sendError(HttpServletResponse.SC_NOT_FOUND, "Page not found: " + path); break; } } private boolean handleStaticResource(HttpServletRequest request, HttpServletResponse response, String path) throws IOException { try (InputStream inputStream = getServletContext().getResourceAsStream(path)) { if (inputStream != null) { String mimeType = getServletContext().getMimeType(path); if (mimeType == null) { mimeType = "application/octet-stream"; } if (mimeType.startsWith("text/")) { response.setCharacterEncoding("UTF-8"); } response.setContentType(mimeType); response.setStatus(HttpServletResponse.SC_OK); inputStream.transferTo(response.getOutputStream()); return true; } } return false; } }
Tomcat tiene un servlet por defecto llamado DefaultServlet que sirve archivos estáticos desde webapp/.
Si usamos DefaultServlet, podemos acceder a CSS, JS o imágenes simplemente con rutas relativas:
<link rel=“stylesheet” href=“/css/style.css”>
Si creamos un Front Controller que captura todas las rutas con /*, este servlet intercepta todas las peticiones, incluidas las de recursos estáticos (CSS, JS, imágenes). Esto provoca que:
Posibles soluciones
Para que las rutas relativas de CSS y JS funcionen sin prefijos extra (/web/), debemos desplegar el WAR como ROOT.war:
En Maven, dentro de pom.xml:
<finalName>ROOT</finalName>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Hello World</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <h1>Hello World</h1> </body> </html>