Web Server 本質是處理 TCP 連線、解析 HTTP 請求、回應 HTTP 響應 的程式。核心四步驟:
GET /index.html HTTP/1.1) 與 Header。Status-Line + Headers (Content-Type, Content-Length...) + 空行 + Body。以下程式碼示範支援 GET 請求、靜態檔案服務、多執行緒並發處理的最小化 Server:
import java.io.*;
import java.net.*;
public class SimpleWebServer {
private static final int PORT = 8080;
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(PORT);
System.out.println("Server started on port " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept(); // 阻塞等待連線
// 每個連線開啟一條執行緒處理
new Thread(new HttpRequestHandler(clientSocket)).start();
}
}
}
import java.io.*;
import java.net.Socket;
import java.nio.file.*;
class HttpRequestHandler implements Runnable {
private final Socket socket;
HttpRequestHandler(Socket socket) { this.socket = socket; }
@Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
OutputStream out = socket.getOutputStream()) {
// 1. 讀取請求行
String requestLine = in.readLine();
if (requestLine == null || requestLine.isEmpty()) return;
// 解析 GET /path HTTP/1.1
String[] parts = requestLine.split("\\s+");
if (parts.length < 3 || !"GET".equalsIgnoreCase(parts[0])) {
sendResponse(out, 405, "Method Not Allowed", "text/plain", "Only GET supported");
return;
}
String path = parts[1].equals("/") ? "/index.html" : parts[1];
// 2. 定位檔案 (簡易:從 classpath 或工作目錄讀取)
Path filePath = Paths.get(".", "webroot", path).normalize();
// 安全檢查:防止目錄遍歷攻擊
if (!filePath.startsWith(Paths.get(".", "webroot").toAbsolutePath())) {
sendResponse(out, 403, "Forbidden", "text/plain", "Access denied");
return;
}
// 3. 讀取檔案並響應
if (Files.exists(filePath) && Files.isRegularFile(filePath)) {
String mimeType = guessMimeType(filePath.toString());
byte[] content = Files.readAllBytes(filePath);
sendResponse(out, 200, "OK", mimeType, content);
} else {
sendResponse(out, 404, "Not Found", "text/html", "404 Not Found
".getBytes());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try { socket.close(); } catch (IOException ignored) {}
}
}
// 發送完整 HTTP 響應
private void sendResponse(OutputStream out, int statusCode, String statusText, String contentType, byte[] body) throws IOException {
PrintWriter writer = new PrintWriter(out, true);
writer.printf("HTTP/1.1 %d %s\r\n", statusCode, statusText);
writer.printf("Content-Type: %s\r\n", contentType);
writer.printf("Content-Length: %d\r\n", body.length);
writer.printf("Connection: close\r\n");
writer.print("\r\n");
writer.flush();
out.write(body);
out.flush();
}
private void sendResponse(OutputStream out, int statusCode, String statusText, String contentType, String body) throws IOException {
sendResponse(out, statusCode, statusText, contentType, body.getBytes());
}
// 簡易 MIME 類型判斷
private String guessMimeType(String fileName) {
if (fileName.endsWith(".html") || fileName.endsWith(".htm")) return "text/html; charset=UTF-8";
if (fileName.endsWith(".css")) return "text/css";
if (fileName.endsWith(".js")) return "application/javascript";
if (fileName.endsWith(".png")) return "image/png";
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) return "image/jpeg";
return "application/octet-stream";
}
}
ExecutorService 替代 `new Thread()`,避免執行緒耗盡。Selector + SocketChannel 模型 (如 Netty)。手寫 Web Server 是理解網路程式設計、HTTP 協議與 Java I/O 模型的最佳練習。從阻塞式多執行緒起步,逐步引入執行緒池、NIO、協議完整解析,即可演進出生產級的高效能伺服器。