上一章内容:第二章:解析 HTTP 请求,支持静态文件-MiniTomcat
在本章节中,我们将引入连接器(Connector)组件,用于管理 HTTP 连接和请求数据的解析。连接器的作用是负责客户端的网络连接,同时解耦网络传输和请求处理的逻辑。
3.1 功能目标
3.2 代码结构
以下是 MiniTomcat 项目的基本代码结构,我们在 com.daicy.minitomcat 包中添加 HttpConnector
, HttpProcessor,Request
,Response
,StaticResourceProcessor
几个类,同时修改SimpleHttpServer
为HttpServer
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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.xml
|
3.3 代码实现
3.3.1 创建 HttpConnector
类
HttpConnector
作为服务器的连接器类,负责监听指定端口,接受客户端连接,并将请求交给 HttpProcessor
进行处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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 processor = new HttpProcessor(clientSocket); processor.process(); } } catch (IOException e) { e.printStackTrace(); } } }
|
3.3.2 创建 HttpProcessor
类
HttpProcessor
类负责处理传入的 HTTP 请求,将请求解析为 Request
对象,并构建响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| 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
类负责查找读取静态文件,并构建响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| 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); } } }
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
用于生成和发送服务器响应。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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
,并接受客户端请求。
1 2 3 4 5 6 7 8
| package com.daicy.minitomcat;
public class HttpServer { public static void main(String[] args) { HttpConnector connector = new HttpConnector(); connector.start(); } }
|
3.4 代码解析
连接器的创建:
请求解析:
响应生成:
3.5 学习收获
通过实现连接器,我们实现了网络传输与请求处理的解耦:
项目源代码地址:
https://github.com/daichangya/MiniTomcat/tree/chapter3/mini-tomcat