====== 04 - Servidor HTTP concurrente ======
El servidor del tema anterior atiende una petición a la vez. La solución es combinar ambas ideas: un **pool de hilos** del tema 3 atendiendo las conexiones del servidor de sockets.
===== 1. Un hilo por conexión =====
public class ConcurrentHttpServer {
private static final ExecutorService POOL = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws IOException {
try (ServerSocket serverSocket = new ServerSocket(8080)) {
while (true) {
Socket socket = serverSocket.accept();
POOL.execute(() -> handle(socket));
}
}
}
private static void handle(Socket socket) {
try (socket) {
// leer petición y responder como en el tema 02
} catch (IOException exception) {
// registrar el error
}
}
}
* ''accept()'' devuelve el ''Socket'' y el bucle principal queda libre para aceptar la siguiente conexión.
* El pool ejecuta cada petición en un hilo worker.
* Los hilos del pool **se reutilizan** entre peticiones.
@startuml
participant "Servidor" as S
participant "Hilo 1" as H1
participant "Hilo 2" as H2
actor "Cliente A" as A
actor "Cliente B" as B
A -> S : conexión A
S -> H1 : handle(socketA)
B -> S : conexión B (simultánea)
S -> H2 : handle(socketB)
H1 --> A : respuesta
H2 --> B : respuesta
C -> S : conexión C (posterior)
note right of S : reutiliza un hilo del pool
S -> H1 : handle(socketC)
H1 --> C : respuesta
@enduml
===== 2. Las reglas de los hilos siguen valiendo =====
* Estado ''static'' compartido → condición de carrera entre peticiones.
* ''ThreadLocal'' sin limpiar → fuga de datos a la siguiente petición del mismo hilo.
* Variables locales → seguras: cada hilo tiene su propia pila.
===== 3. HttpServer del JDK =====
Java incluye un servidor HTTP con pool de hilos integrado, útil como alternativa intermedia entre el servidor propio y Tomcat:
public class JdkHttpServer {
public static void main(String[] args) throws IOException {
HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);
server.createContext("/books", exchange -> {
String body = "{\"message\":\"books endpoint\"}";
exchange.getResponseHeaders().set("Content-Type", "application/json");
exchange.sendResponseHeaders(200, body.getBytes().length);
try (OutputStream output = exchange.getResponseBody()) {
output.write(body.getBytes());
}
});
server.setExecutor(Executors.newFixedThreadPool(10));
server.start();
}
}
''HttpServer'' resuelve el parseo del protocolo; el ''Executor'' gestiona el pool. Lo que sigue sin resolver: enrutado por rutas, archivos estáticos, ciclo de vida de componentes... Eso es exactamente lo que aporta un **contenedor de servlets**.
===== 4. Hacia Tomcat =====
^ Capa ^ Problema que resuelve ^
| Sockets | Comunicación TCP |
| Pool de hilos | Concurrencia entre peticiones |
| HttpServer / contenedor | Protocolo HTTP, enrutado, recursos |
| **Tomcat** | Todo lo anterior + ciclo de vida de **servlets** y filtros |
Este servidor es el modelo mental con el que entender Tomcat: un contenedor que acepta conexiones, las reparte entre hilos de un pool y delega cada petición en un componente (el servlet) que el desarrollador escribe.