MiniCat

基本介绍

我们要手写实现一个 Mini 版的 Tomcat,我们要实现的是,作为一个服务器软件,可以通过我们的浏览器客户端发送HTTP请求,Minicat处理之后,处理结果可以返回给浏览器的客户端。

● 提供服务,接受请求(Socket通信) ● 请求信息封装成 Request 对象(Response对象) ● 客户端请求资源,资源分为静态资源(HTML)和动态资源(Servlet) ● 资源返回客户端浏览器

Tomcat 的核心作用

Tomcat 是一个 Servlet 容器,核心职责包括:

  • 接收浏览器的 HTTP 请求
  • 分发请求给对应的 Servlet
  • 管理 Servlet 的生命周期
  • 返回响应给浏览器

最小功能模块

为了实现最基本的功能,MiniCat 至少需要实现以下模块:

Socket 服务端监听器

  • 使用 ServerSocket 接收浏览器请求
  • 多线程处理并发请求

Request 解析器

  • 解析 HTTP 请求(请求行、请求头、请求体)
  • 构建 MiniRequest 对象,封装请求信息

Response 构建器

  • 构建 HTTP 响应
  • 包含响应状态、响应头、响应体

Servlet 接口与管理

  • 自定义 MiniServlet 接口(模拟 javax.servlet.Servlet)
  • 加载与初始化 Servlet
  • 调用 service() 方法处理请求

URL 映射 Dispatcher

  • 维护 URL 与 Servlet 的映射关系
  • 根据请求路径匹配对应的 Servlet 实例

WebApp 配置加载器

  • 读取 web.xml(简化版)或自定义配置文件
  • 动态加载类(使用 Class.forName)

准备依赖

<dependencies>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
</dependencies>

Bootstrap 代码

public class Bootstrap {
    private int port = 8080;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public static void main(String[] args) {
        Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void start() throws Exception {
        loadServlet();

        int corePoolSize = 10;
        int maximumPoolSize = 20;
        long keepAliveTime = 100L;
        TimeUnit unit = TimeUnit.SECONDS;

        BlockingQueue<Runnable> deque = new ArrayBlockingQueue<>(50);
        ThreadFactory threadFactory = Executors.defaultThreadFactory();
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                deque,
                threadFactory,
                handler
        );

        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("ServerSocket: " + serverSocket);
        while (true) {
            Socket socket = serverSocket.accept();
            RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
            threadPoolExecutor.execute(requestProcessor);
        }
    }

    private Map<String, HttpServlet> servletMap = new HashMap<>();

    private void loadServlet() {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (int i = 0; i < selectNodes.size(); i ++) {
                Element element = selectNodes.get(i);

                Element servletElement = (Element) element.selectSingleNode("servlet-name");
                String servletName = servletElement.getStringValue();

                Element servletClassElement = (Element) element.selectSingleNode("servlet-class");
                String servletClass = servletClassElement.getStringValue();

                Element servletMapping = (Element) rootElement
                        .selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
                servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

代码解析

主入口

创建 Bootstrap 实例,调用 start() 启动服务器,捕获异常输出。

启动逻辑

调用 loadServlet() 方法,加载自定义的 Servlet 配置(从 web.xml 解析映射关系)。

创建线程池

  • 核心线程数:10,最大线程数:20
  • 空闲线程存活时间:100秒
  • 任务队列:50个
  • 拒绝策略:默认抛出异常(AbortPolicy)

创建 ServerSocket 并监听请求

ServerSocket serverSocket = new ServerSocket(port);
System.out.println("ServerSocket: " + serverSocket);

主循环

  • accept() 阻塞直到有浏览器请求
  • 创建一个新的 RequestProcessor 实例处理请求
  • 使用线程池异步执行

加载配置

从 classpath 中读取 web.xml 文件,并用 SAXReader 解析 XML 文档。


暂时小结

Bootstrap 作为 MiniCat 的核心启动类,完成了:

  • 加载配置文件 web.xml 中配置的 Servlet 类
  • 初始化 Servlet 并建立 URL 映射
  • 启动 Socket 服务监听 HTTP 请求
  • 为每一个请求分发一个线程去执行
  • 线程中实际使用了 RequestProcessor 进行业务处理