Tabla de Contenidos

08 - Servlets: Front Controller y Recursos Estáticos

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á:

Estructura de proyecto recomendada

 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

Todos los HTML deben usar rutas relativas al contexto para CSS y JS, por ejemplo:
<link rel="stylesheet" href="css/style.css">
<script src="js/script.js"></script>

FrontController.java

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;
}


}

Este Front Controller permite servir HTML dinámico y recursos estáticos como CSS y JS desde webapp.

Recursos estáticos y DefaultServlet de Tomcat

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

Despliegue como ROOT.war

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>

HTML de ejemplo

<!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>