题目
使用Java实现一个简单的多线程HTTP服务器,处理GET请求并返回静态文件
信息
- 类型:问答
- 难度:⭐⭐
考点
Socket编程, HTTP协议解析, 多线程处理, 异常处理, 资源管理
快速回答
实现要点:
- 使用ServerSocket监听端口
- 为每个连接创建独立线程处理
- 解析HTTP请求行获取请求方法和路径
- 读取本地文件并生成HTTP响应
- 处理404等错误状态
- 正确关闭资源
核心原理
HTTP服务器基于TCP协议,通过ServerSocket监听端口,对每个连接:
- 解析HTTP请求头(重点关注GET方法和请求路径)
- 映射路径到本地文件系统
- 读取文件内容并构造HTTP响应(状态行+头部+正文)
- 处理文件不存在等异常情况
代码示例
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)