首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

從零手寫一個極簡 Java Web Server 核心原理解析

     編輯:Java 綜合教學

從零手寫一個極簡 Java Web Server 核心原理解析

Tomcat、Jetty 等成熟容器底層均基於 ServerSocket + Socket 的 BIO 模型(早期版本)。本文通過 100 行核心代碼,實現一個支持靜態資源訪問、404 處理、優雅關閉的微型 HTTP Server,幫助理解 Servlet 容器核心原理。

架構設計

  • HttpServer:主循環,監聽端口,分派 Request/Response。
  • Request:解析 HTTP 請求行,提取 URI。
  • Response:構建 HTTP 響應頭,輸出靜態文件或 404 頁面。

核心代碼實現

1. HttpServer.java - 主入口與分派循環

package ex01.pyrmont;

import java.io.*;
import java.net.*;
import java.nio.file.*;

public class HttpServer {
    public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
    private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";

    public static void main(String[] args) {
        new HttpServer().await();
    }

    public void await() {
        try (ServerSocket serverSocket = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"))) {
            System.out.println("Server started on 127.0.0.1:8080");
            
            while (true) {
                try (Socket socket = serverSocket.accept();
                     InputStream input = socket.getInputStream();
                     OutputStream output = socket.getOutputStream()) {
                    
                    Request request = new Request(input);
                    request.parse();
                    
                    if (SHUTDOWN_COMMAND.equals(request.getUri())) {
                        System.out.println("Shutdown command received.");
                        return; // 退出主循環
                    }
                    
                    Response response = new Response(output);
                    response.setRequest(request);
                    response.sendStaticResource();
                    
                } catch (IOException e) {
                    System.err.println("Request processing error: " + e.getMessage());
                }
            }
        } catch (IOException e) {
            System.err.println("Server socket error: " + e.getMessage());
            System.exit(1);
        }
    }
}

2. Request.java - HTTP 請求解析

package ex01.pyrmont;

import java.io.*;

public class Request {
    private InputStream input;
    private String uri;

    public Request(InputStream input) { this.input = input; }

    public void parse() throws IOException {
        byte[] buffer = new byte[2048];
        int bytesRead = input.read(buffer);
        if (bytesRead <= 0) return;
        
        String requestText = new String(buffer, 0, bytesRead);
        System.out.println("=== Request ===\n" + requestText);
        
        // 解析請求行: GET /index.html HTTP/1.1
        int firstSpace = requestText.indexOf(' ');
        int secondSpace = requestText.indexOf(' ', firstSpace + 1);
        if (firstSpace != -1 && secondSpace > firstSpace) {
            this.uri = requestText.substring(firstSpace + 1, secondSpace);
        }
    }

    public String getUri() { return uri; }
}

3. Response.java - HTTP 響應構建與靜態資源輸出

package ex01.pyrmont;

import java.io.*;
import java.nio.file.*;

public class Response {
    private static final int BUFFER_SIZE = 1024;
    private OutputStream output;
    private Request request;

    public Response(OutputStream output) { this.output = output; }
    public void setRequest(Request request) { this.request = request; }

    public void sendStaticResource() throws IOException {
        String uri = request.getUri();
        Path filePath = Paths.get(HttpServer.WEB_ROOT, uri).normalize();
        
        // 安全檢查:防止目錄遍歷攻擊
        if (!filePath.startsWith(Paths.get(HttpServer.WEB_ROOT).normalize())) {
            sendError(403, "Forbidden");
            return;
        }

        if (Files.exists(filePath) && Files.isRegularFile(filePath)) {
            // 發送 200 OK + 文件內容
            String header = "HTTP/1.1 200 OK\r\n" +
                            "Content-Type: " + Files.probeContentType(filePath) + "\r\n" +
                            "Content-Length: " + Files.size(filePath) + "\r\n" +
                            "\r\n";
            output.write(header.getBytes());
            Files.copy(filePath, output);
        } else {
            sendError(404, "File Not Found");
        }
    }

    private void sendError(int code, String msg) throws IOException {
        String body = "

" + code + " " + msg + "

"; String header = "HTTP/1.1 " + code + " " + msg + "\r\n" + "Content-Type: text/html\r\n" + "Content-Length: " + body.length() + "\r\n" + "\r\n"; output.write((header + body).getBytes()); } }

運行測試

  1. 項目根目錄創建 webroot 文件夾,放入 index.html、style.css、圖片等。
  2. 運行 HttpServer.main()。
  3. 瀏覽器訪問 https://127.0.0.1:8080/index.html。
  4. 訪問不存在路徑觸發 404。
  5. 訪問 https://127.0.0.1:8080/SHUTDOWN 關閉服務。

核心知識點總結

模塊 關鍵技術點
ServerSocket bind/listen/accept 三步驟;backlog 參數控制等待隊列長度
Socket 雙向流;getInputStream()/getOutputStream()
HTTP 協議 請求行/頭/體;響應行/頭/體;\r\n\r\n 分隔頭體
靜態資源 Files.probeContentType() 自動 MIME;Files.copy() 零拷貝輸出
安全 路徑規範化 normalize() + 前綴檢查防目錄遍歷

擴展方向

  • 支持 POST 參數解析(Content-Length + body 讀取)。
  • 引入線程池處理併發(ExecutorService)。
  • 實現 Servlet 規範:ServletRequest/ServletResponse 接口、生命周期管理。
  • 升級 NIO (ServerSocketChannel + Selector) 支持高併發。

參考資料:《深入剖析 Tomcat》—— 從源碼級理解容器本質。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved