侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

使用Java实现一个简单的多线程HTTP服务器,处理GET请求并返回静态文件

2025-12-12 / 0 评论 / 4 阅读

题目

使用Java实现一个简单的多线程HTTP服务器,处理GET请求并返回静态文件

信息

  • 类型:问答
  • 难度:⭐⭐

考点

Socket编程, HTTP协议解析, 多线程处理, 异常处理, 资源管理

快速回答

实现要点:

  • 使用ServerSocket监听端口
  • 为每个连接创建独立线程处理
  • 解析HTTP请求行获取请求方法和路径
  • 读取本地文件并生成HTTP响应
  • 处理404等错误状态
  • 正确关闭资源
## 解析

核心原理

HTTP服务器基于TCP协议,通过ServerSocket监听端口,对每个连接:

  1. 解析HTTP请求头(重点关注GET方法和请求路径)
  2. 映射路径到本地文件系统
  3. 读取文件内容并构造HTTP响应(状态行+头部+正文)
  4. 处理文件不存在等异常情况

代码示例

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

public class SimpleHttpServer {
    private static final int PORT = 8080;
    private static final String WEB_ROOT = "./static";

    public static void main(String[] args) throws IOException {
        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("Server started on port " + PORT);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                new Thread(() -> handleRequest(clientSocket)).start();
            }
        }
    }

    private static void handleRequest(Socket clientSocket) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             OutputStream out = clientSocket.getOutputStream()) {

            // 解析请求行
            String requestLine = in.readLine();
            if (requestLine == null) return;
            String[] parts = requestLine.split(" ");
            if (parts.length < 3 || !"GET".equals(parts[0])) {
                sendError(out, 400, "Bad Request");
                return;
            }

            String path = parts[1].equals("/") ? "/index.html" : parts[1];
            Path filePath = Paths.get(WEB_ROOT, path).normalize();

            // 安全校验:防止路径穿越攻击
            if (!filePath.startsWith(Paths.get(WEB_ROOT))) {
                sendError(out, 403, "Forbidden");
                return;
            }

            // 返回文件或404
            if (Files.exists(filePath) && !Files.isDirectory(filePath)) {
                byte[] fileBytes = Files.readAllBytes(filePath);
                String contentType = Files.probeContentType(filePath);
                sendResponse(out, 200, "OK", contentType, fileBytes);
            } else {
                sendError(out, 404, "Not Found");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try { clientSocket.close(); } catch (IOException ignored) {}
        }
    }

    private static void sendResponse(OutputStream out, int statusCode, String statusText, 
                                     String contentType, byte[] content) throws IOException {
        String header = "HTTP/1.1 " + statusCode + " " + statusText + "\r\n" +
                        "Content-Type: " + contentType + "\r\n" +
                        "Content-Length: " + content.length + "\r\n\r\n";
        out.write(header.getBytes());
        out.write(content);
        out.flush();
    }

    private static void sendError(OutputStream out, int statusCode, String statusText) throws IOException {
        String content = "<h1>" + statusCode + " " + statusText + "</h1>";
        sendResponse(out, statusCode, statusText, "text/html", content.getBytes());
    }
}

最佳实践

  • 线程管理:使用线程池(如ExecutorService)避免频繁创建线程
  • 路径安全:校验文件路径防止目录遍历攻击(如检查filePath.startsWith(WEB_ROOT)
  • 资源释放:使用try-with-resources确保Socket和流正确关闭
  • 性能优化:对静态文件使用NIO(FileChannel)提高读取效率
  • 协议兼容:正确处理HTTP/1.1的Connection头部实现持久连接

常见错误

  • 未处理GET /请求的默认页面
  • 未校验路径导致安全漏洞(如GET /../../etc/passwd HTTP/1.1
  • 未设置Content-Type导致浏览器解析错误
  • 忘记flush输出流导致响应阻塞
  • 线程创建无上限导致资源耗尽

扩展知识

  • HTTP/1.1特性:Keep-Alive、Chunked传输编码
  • 高级IO模型:使用NIO(Selector)实现非阻塞服务器
  • 框架对比:与Tomcat/Jetty等容器的区别(如Servlet规范)
  • 性能调优:零拷贝技术(FileChannel.transferTo)发送文件
  • 安全加固:添加HTTPS支持(SSLContext)