Java-49 深入浅出 Tomcat 手写 Tomcat 实现【02】HttpServlet Request RequestProcessor

点一下关注吧!!!非常感谢!!持续更新!!!

🚀 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 协议类

java 复制代码
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 基础类

java 复制代码
public abstract class HttpServlet implements Servlet {

    public abstract void doGet(Request request,Response response);

    public abstract void doPost(Request request,Response response);


    @Override
    public 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: 表明此方法可能抛出异常,让调用方去处理,便于灵活控制异常响应。

假设我们有一个具体的实现类:

java 复制代码
public class HelloServlet extends HttpServlet {
    @Override
    public void doGet(Request req, Response resp) {
        resp.write("Hello, GET!");
    }

    @Override
    public void doPost(Request req, Response resp) {
        resp.write("Hello, POST!");
    }
}

当用户访问你的 Web 服务 /hello 时:

  • 如果是 GET 请求,会调用 doGet(),返回 "Hello, GET!"。
  • 如果是 POST 请求,会调用 doPost(),返回 "Hello, POST!"。

Request 请求处理类

java 复制代码
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() 循环读取,目的是等到客户端数据准备好再读取。

java 复制代码
while (count == 0) {
    count = inputStream.available();
}

RequestProcessor 响应类

java 复制代码
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;
    }

    @Override
    public 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())。
相关推荐
葫芦和十三5 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp6 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑6 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯7 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan9 小时前
多Agent之间的区别
后端
青石路11 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充11 小时前
1.面向对象设计思想
后端
IT_陈寒11 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro12 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗12 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端