第三章:实现连接器(Connector)组件-MiniTomcat
上一章内容:第二章:解析 HTTP 请求,支持静态文件-MiniTomcat
在本章节中,我们将引入连接器(Connector)组件,用于统一管理 HTTP 连接及请求数据的解析。连接器的核心作用是负责客户端的网络连接建立,同时将网络传输逻辑与请求处理逻辑解耦,为后续扩展奠定基础。
3.1 功能目标
本章主要实现以下两个核心目标:
- 管理 HTTP 连接:连接器组件负责监听指定端口,与客户端建立连接,并读取传入的数据包。
- 解耦网络传输和请求解析:将底层的网络传输逻辑与上层的请求处理逻辑分离,提升代码的清晰度、可维护性及容错性。
3.2 代码结构
以下是 MiniTomcat 项目的基本代码结构。我们在 com.daicy.minitomcat 包中新增了 HttpConnector、HttpProcessor、Request、Response 及 StaticResourceProcessor 类,并将原有的 SimpleHttpServer 重构为 HttpServer。
MiniTomcat
├─ src
│ ├─ main
│ │ ├─ java
│ │ │ ├─ com.daicy.minitomcat
│ │ │ │ ├─ HttpConnector.java // 连接器类
│ │ │ │ ├─ HttpProcessor.java // 请求处理器
│ │ │ │ ├─ HttpServer.java // 主服务器类
│ │ │ │ ├─ Request.java // 请求封装类
│ │ │ │ ├─ Response.java // 响应封装类
│ │ │ │ ├─ StaticResourceProcessor.java // 静态资源处理器
│ │ ├─ resources
│ ├─ test
│ ├─ webroot
│ │ ├─ index.html
├─ pom.xml3.3 代码实现
3.3.1 创建 HttpConnector 类
HttpConnector 作为服务器的连接器组件,负责监听指定端口,接受客户端连接,并将具体的请求交给 HttpProcessor 进行处理。
package com.daicy.minitomcat;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class HttpConnector implements Runnable {
private static final int PORT = 8080;
public void start() {
Thread thread = new Thread(this);
thread.start();
}
@Override
public void run() {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("HTTP Connector is running on port " + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("Accepted connection from " + clientSocket.getInetAddress());
// 将连接交给 HttpProcessor 处理
HttpProcessor processor = new HttpProcessor(clientSocket);
processor.process();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}3.3.2 创建 HttpProcessor 类
HttpProcessor 类负责处理传入的 HTTP 请求,将原始数据解析为 Request 对象,并构建相应的响应。
package com.daicy.minitomcat;
import java.io.*;
import java.net.Socket;
public class HttpProcessor {
private Socket socket;
private StaticResourceProcessor staticProcessor = new StaticResourceProcessor();
public HttpProcessor(Socket socket) {
this.socket = socket;
}
public void process() {
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 解析请求
Request request = parseRequest(inputStream);
// 构建响应
Response response = new Response(outputStream);
if (null == request) {
return;
}
String uri = request.getUri();
// 本章示例中,所有请求均交由静态资源处理器处理
if (uri.endsWith(".html") || uri.endsWith(".css") || uri.endsWith(".js")) {
staticProcessor.process(request, response);
} else {
staticProcessor.process(request, response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private Request parseRequest(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String requestLine = reader.readLine();
if (requestLine == null || requestLine.isEmpty()) {
return null;
}
System.out.println("Request Line: " + requestLine);
String[] parts = requestLine.split(" ");
String method = parts[0];
String path = parts[1];
return new Request(method, path);
}
}3.3.3 创建 StaticResourceProcessor 类
StaticResourceProcessor 类负责根据请求路径查找并读取静态文件,构建 HTTP 响应返回给客户端。
package com.daicy.minitomcat;
import java.io.*;
import java.net.URL;
import static com.daicy.minitomcat.HttpServer.WEB_ROOT;
public class StaticResourceProcessor {
public void process(Request request, Response response) {
try {
OutputStream outputStream = response.getOutputStream();
// 查找请求的静态文件
String path = request.getUri();
URL url = HttpServer.class.getClassLoader().getResource(WEB_ROOT + path);
if (null == url) {
sendResponse(outputStream, 404, "Not Found", "The requested resource was not found.");
return;
}
File file = new File(url.getPath());
if (file.exists() && !file.isDirectory()) {
sendFileResponse(outputStream, file);
} else {
sendResponse(outputStream, 404, "Not Found", "The requested resource was not found.");
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 发送普通文本响应
private static void sendResponse(OutputStream outputStream, int statusCode, String statusText, String message) throws IOException {
PrintWriter writer = new PrintWriter(outputStream, true);
writer.println("HTTP/1.1 " + statusCode + " " + statusText);
writer.println("Content-Type: text/html; charset=UTF-8");
writer.println();
writer.println("<html><body><h1>" + statusCode + " " + statusText + "</h1><p>" + message + "</p></body></html>");
}
// 发送文件响应
private static void sendFileResponse(OutputStream outputStream, File file) throws IOException {
PrintWriter writer = new PrintWriter(outputStream, true);
writer.println("HTTP/1.1 200 OK");
writer.println("Content-Type: " + getContentType(file));
writer.println("Content-Length: " + file.length());
writer.println();
// 发送文件内容
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
}
// 根据文件后缀返回 Content-Type
private static String getContentType(File file) {
String name = file.getName().toLowerCase();
if (name.endsWith(".html") || name.endsWith(".htm")) {
return "text/html";
} else if (name.endsWith(".css")) {
return "text/css";
} else if (name.endsWith(".js")) {
return "application/javascript";
} else if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
return "image/jpeg";
} else if (name.endsWith(".png")) {
return "image/png";
} else {
return "application/octet-stream";
}
}
}3.3.4 Request 和 Response 类
Request 类用于封装客户端请求数据,Response 类用于生成和发送服务器响应。
package com.daicy.minitomcat;
public class Request {
private String method;
private String path;
public Request(String method, String path) {
this.method = method;
this.path = path;
}
public String getMethod() {
return method;
}
public String getPath() {
return path;
}
}package com.daicy.minitomcat;
import java.io.*;
import java.net.URL;
public class Response {
private OutputStream outputStream;
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
public OutputStream getOutputStream() {
return outputStream;
}
}3.3.5 启动 HttpConnector
HttpServer 类作为服务器的入口,负责启动 HttpConnector 并开始接受客户端请求。
package com.daicy.minitomcat;
public class HttpServer {
// 注意:需在 HttpServer 中定义 public static final String WEB_ROOT = "/webroot"; 供静态处理器使用
public static void main(String[] args) {
HttpConnector connector = new HttpConnector();
connector.start();
}
}3.4 代码解析
连接器的创建
HttpConnector监听指定端口,当客户端连接建立后,创建新的HttpProcessor实例处理该连接。- 连接器与请求处理器解耦,确保每个连接的处理逻辑独立清晰。
请求解析
HttpProcessor读取并解析 HTTP 请求行,构建Request对象,提取方法(Method)和路径(URI)信息。- 简化了请求解析的逻辑,确保连接器和处理器职责分离。
响应生成
StaticResourceProcessor类根据Request的路径生成响应内容,支持静态资源返回。- 响应类设计灵活,便于后续扩展支持更多类型的请求和响应格式。
3.5 学习收获
通过实现连接器组件,我们成功实现了网络传输与请求处理的解耦:
- 职责分离:将网络连接管理和请求解析分别交由连接器和处理器管理,显著提升了代码的可读性和维护性。
- 面向组件设计:连接器作为服务器的核心组件之一,符合后续扩展不同类型连接(如 HTTPS、NIO 等)的需求。
项目源代码地址:
https://github.com/daichangya/MiniTomcat/tree/chapter3/mini-tomcat
说明:本文示例基于 Java BIO(Blocking IO)模型实现,适用于学习 HTTP 协议原理及服务器基础架构。生产环境建议使用 NIO 模型或成熟的 Web 容器。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/di-san-zhang--shi-xian-lian-jie-qi-connector-zu-jian-minitomcat.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。