点一下关注吧!!!非常感谢!!持续更新!!!
🚀 AI篇持续更新中!(长期更新)
目前2025年06月13日更新到:
AI炼丹日志-28 - Audiblez 将你的电子书epub转换为音频mp3 做有声书,持续打造实用AI工具指南!📐🤖
💻 Java篇正式开启!(300篇)
目前2025年06月11日更新到:
Java-44 深入浅出 Nginx - 底层进程机制 Master Worker 机制原理 常用指令
MyBatis 已完结,Spring 已完结,深入浅出助你打牢基础!
📊 大数据板块已完成多项干货更新(300篇):
包括 Hadoop、Hive、Kafka、Flink、ClickHouse、Elasticsearch 等二十余项核心组件,覆盖离线+实时数仓全栈!
目前2025年06月13日更新到:
大数据-278 Spark MLib - 基础介绍 机器学习算法 梯度提升树 GBDT案例 详解
MiniCat
基本介绍
我们要手写实现一个 Mini 版的 Tomcat,我们要实现的是,作为一个服务器软件,可以通过我们的浏览器客户端发送HTTP请求,Minicat处理之后,处理结果可以返回给浏览器的客户端。
● 提供服务,接受请求(Socket通信)
● 请求信息封装成 Request 对象(Response对象)
● 客户端请求资源,资源分为静态资源(HTML)和动态资源(Servlet)
● 资源返回客户端浏览器
HttpProtocoUtil 协议类
public class HttpProtocoUtil {public static String getHttpHeader200(long contentLength) {return "HTTP/1.1 200 OK \n" +"Content-Type: text/html \n" +"Content-Length: " + contentLength + " \n" +"\r\n";}public static String getHttpHeader404() {String str404 = "<h1>404 not found</h1>";return "HTTP/1.1 404 NOT Found \n" +"Content-Type: text/html \n" +"Content-Length: " + str404.getBytes().length + " \n" +"\r\n" + str404;}
}
HttpProtocoUtil,它封装了两个方法,用于手动构造 HTTP 响应报文字符串,分别用于:
- 返回 HTTP 200 成功响应的报文头;
- 返回 HTTP 404 未找到的完整响应报文(包含 header + body)。
关键点分析:
- str404: 定义了一个 HTML 格式的正文
404 not found
- str404.getBytes().length: 计算正文的字节数,填入 Content-Length 字段中
- 最后 + str404: 将正文内容添加在头部之后
HttpServlet 基础类
public abstract class HttpServlet implements Servlet {public abstract void doGet(Request request,Response response);public abstract void doPost(Request request,Response response);@Overridepublic void service(Request request, Response response) throws Exception {if("GET".equalsIgnoreCase(request.getMethod())) {doGet(request,response);}else{doPost(request,response);}}
}
简化版的 HttpServlet 类,是你手写 Web 容器(例如 MiniCat)中模仿 Java EE javax.servlet.http.HttpServlet 的核心组件。
- abstract class: 抽象类,不能直接被实例化,必须由子类实现其抽象方法。
- implements Servlet: 表示这个类实现了一个自定义的 Servlet 接口(你可能已经自己定义了 Servlet 接口),这与 Java Web 中的标准接口 javax.servlet.Servlet 类似。
在抽象方法中:
-
声明了两个抽象方法,分别用于处理 GET 请求 和 POST 请求。
-
子类必须实现它们,否则编译报错。
Request request: 封装了 HTTP 请求的信息(如 URI、参数、方法类型等)。
- Response response: 封装了 HTTP 响应的信息(如设置状态码、响应内容等)。
这两个类也应该是你自己定义的简化版 Request 和 Response 类,用于模拟真实的 Servlet API 行为。
而在service(Request, Response)中:
-
实现了 Servlet 接口中的 service() 方法,作为请求处理的统一入口。
-
根据 request.getMethod() 返回的 HTTP 方法(如 “GET” 或 “POST”),分发给 doGet() 或 doPost() 方法。
-
equalsIgnoreCase: 忽略大小写比较,兼容 “get”, “GET” 等写法。
-
request.getMethod(): 获取请求方法字符串,应该是你自定义 Request 类中的一个方法。
-
throws Exception: 表明此方法可能抛出异常,让调用方去处理,便于灵活控制异常响应。
假设我们有一个具体的实现类:
public class HelloServlet extends HttpServlet {@Overridepublic void doGet(Request req, Response resp) {resp.write("Hello, GET!");}@Overridepublic void doPost(Request req, Response resp) {resp.write("Hello, POST!");}
}
当用户访问你的 Web 服务 /hello 时:
- 如果是 GET 请求,会调用 doGet(),返回 “Hello, GET!”。
- 如果是 POST 请求,会调用 doPost(),返回 “Hello, POST!”。
Request 请求处理类
public class Request {private String method;private String url;private InputStream inputStream;public Request() {}public Request(InputStream inputStream) throws IOException {this.inputStream = inputStream;int count = 0;while (count == 0) {count = inputStream.available();}byte[] bytes = new byte[count];inputStream.read(bytes);String inputStr = new String(bytes);// 请求头String firstLineStr = inputStr.split("\\n")[0];String[] strings = firstLineStr.split(" ");this.method = strings[0];this.url = strings[1];System.out.println("Request method: " + method + ", url: " + url);}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public InputStream getInputStream() {return inputStream;}public void setInputStream(InputStream inputStream) {this.inputStream = inputStream;}
}
在类属性中:
- method:HTTP 请求方法(如 GET, POST)
- url:请求的资源路径(如 /index.html)
- inputStream:从客户端 socket 获取的原始请求数据流
在 inputStream.available() 循环读取,目的是等到客户端数据准备好再读取。
while (count == 0) {count = inputStream.available();
}
RequestProcessor 响应类
public class RequestProcessor extends Thread {private Socket socket;private Map<String, HttpServlet> servletMap;public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {this.socket = socket;this.servletMap = servletMap;}@Overridepublic void run() {try {InputStream inputStream = socket.getInputStream();Request request = new Request(inputStream);Response response = new Response(socket.getOutputStream());// 静态资源if (servletMap.get(request.getUrl()) == null) {response.outputHtml(request.getUrl());} else {HttpServlet servlet = servletMap.get(request.getUrl());servlet.service(request, response);}socket.close();} catch (Exception e) {e.printStackTrace();}}
}
RequestProcessor 这是一个继承了 Thread 的类,意味着每次请求会以独立线程的方式进行处理。它负责解析 HTTP 请求、查找对应的 Servlet 处理器,或返回静态资源。在简易 Web Server 中,每当 ServerSocket.accept() 接收到一个连接,就会创建这个类的一个实例,并调用 start() 进入 run() 方法处理。
此外,在RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap)构造函数中,将 socket 和 servlet 映射传入,以便在 run() 方法中使用。这也是多线程处理请求的基础结构:每个线程独立处理一个连接,不共享 socket。
在核心处理逻辑中 run():从 socket 获取输入流、输出流,构造出 Request 和 Response 对象。Request 负责解析 HTTP 请求头;Response 封装输出功能(例如写 HTML 响应)。
另外,在请求分发逻辑中,“servletMap.get(request.getUrl())”:
- 静态资源路径(如 /index.html):不在 servletMap 中,调用 response.outputHtml(),你很可能实现了这个方法来读取本地文件并写入 socket。
- 动态 Servlet 路径(如 /hello):在 servletMap 中,调用 servlet.service(),执行对应的业务逻辑(最终调用 doGet() 或 doPost())。