04 - Servidor HTTP - 2
Servidor HTTP multicliente con ficheros estáticos
En el tema anterior vimos cómo crear un mini servidor HTTP muy básico.
Ahora vamos a mejorarlo para que:
- Soporte múltiples clientes simultáneamente usando Threads.
- Devuelva ficheros HTML estáticos desde la carpeta `src/main/resources/www`.
Funcionamiento
- El servidor escucha en el puerto 8080.
- Cada vez que un cliente se conecta, se crea un hilo independiente para manejar su solicitud.
- Si el cliente solicita `/`, se envía el fichero `index.html`.
- Si solicita otro recurso (por ejemplo `/about.html`), se busca en la carpeta `www`.
- Si el archivo no existe, se devuelve un 404 Not Found.
Estructura del proyecto
proyecto/ ├─ src/main/java/es/cesguiro/socket/HttpServerMulti.java ├─ src/main/java/es/cesguiro/socket/ClientHandler.java ├─ src/main/java/es/cesguiro/socket/Main.java └─ src/main/resources/www/ ├─ index.html └─ about.html
Código
package es.cesguiro.socket; import java.net.ServerSocket; import java.net.Socket; public class HttpServerMulti { private final int port = 8080; public void start() { System.out.println("Starting HTTP server on port " + port + "..."); try { // Iniciar el servidor y aceptar múltiples conexiones ServerSocket serverSocket = new ServerSocket(port); System.out.println("HTTP server started. Waiting for requests..."); while (true) { Socket clientSocket = serverSocket.accept(); System.out.println("Client connected: " + clientSocket.getInetAddress()); // Manejar la conexión en un nuevo hilo new Thread(new ClientHandler(clientSocket)).start(); } } catch (Exception e) { System.err.println("Error starting HTTP server: " + e.getMessage()); throw new RuntimeException("Failed to start HTTP server", e); } } }
package es.cesguiro.socket; import java.io.*; import java.net.Socket; import java.nio.charset.StandardCharsets; public class ClientHandler implements Runnable { private final Socket clientSocket; public ClientHandler(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { try ( BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true) ) { // Leer la primera línea de la solicitud HTTP String requestLine = in.readLine(); System.out.println("Received request: " + requestLine); if (requestLine == null || !requestLine.startsWith("GET")) { sendError(out, 400, "Bad Request"); return; } // Extraer recurso solicitado String[] parts = requestLine.split(" "); String resource = parts.length > 1 ? parts[1] : "/"; if (resource.equals("/")) { resource = "/index.html"; } // Buscar archivo en resources/www InputStream fileStream = getClass().getResourceAsStream("/www" + resource); if (fileStream != null) { String body = new String(fileStream.readAllBytes(), StandardCharsets.UTF_8); sendResponse(out, 200, "OK", body); } else { sendResponse(out, 404, "Not Found", "<h1>404 Not Found</h1>"); } } catch (IOException e) { System.err.println("Error handling client connection: " + e.getMessage()); } finally { try { clientSocket.close(); } catch (Exception e) { System.err.println("Error closing client socket: " + e.getMessage()); } } } private void sendResponse(PrintWriter out, int statusCode, String statusText, String body) { out.print("HTTP/1.0 " + statusCode + " " + statusText + "\r\n"); out.print("Content-Type: text/html\r\n"); out.print("Content-Length: " + body.getBytes(StandardCharsets.UTF_8).length + "\r\n"); out.print("\r\n"); out.print(body); out.flush(); } private void sendError(PrintWriter out, int statusCode, String statusText) { sendResponse(out, statusCode, statusText, "<h1>" + statusCode + " " + statusText + "</h1>"); } }
package es.cesguiro.socket; public class Main { public static void main(String[] args) { HttpServerMulti server = new HttpServerMulti(); server.start(); } }
Archivos HTML
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Bienvenido</title> </head> <body> <h1>Hola desde el Servidor HTTP multicliente</h1> <p>Este es el archivo <strong>index.html</strong> servido desde <code>src/main/resources/www</code>.</p> <p><a href="/about.html">Ir a la página About</a></p> </body> </html>
about.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Acerca de</title> </head> <body> <h1>Página About</h1> <p>Este servidor es un ejemplo básico de manejo de múltiples conexiones y ficheros estáticos en Java.</p> <p><a href="/">Volver al inicio</a></p> </body> </html>
Ejecución
- Arranca el servidor (`Main.java`).
- Abre un navegador y accede a:
- http://localhost:8080/ → index.html
- Prueba con varios navegadores o pestañas a la vez para comprobar que atiende múltiples clientes.