MiniCat 基本介绍

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

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

Response 请求响应类

定义了一个名为 Response 的公共类,用于封装向客户端发送数据(如 HTML、文本等)的逻辑。

public class Response {
    private OutputStream outputStream;
    public Response() {
    }
    public Response(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    public void output(String content) throws IOException {
        outputStream.write(content.getBytes());
    }
    public void outputHtml(String path) throws IOException {
        String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);
        File file = new File(absoluteResourcePath);
        if (file.exists() && file.isFile()) {
            StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
        } else {
            output(HttpProtocoUtil.getHttpHeader404());
        }
    }
}

OutputStream 是 Java I/O 的基类,用于向外部(如客户端的 socket)输出字节数据。它的典型使用场景是在 Web 服务中向浏览器写入响应内容。

Response 类的主要职责包括:

  • 封装输出流:把 OutputStream 作为类的核心资源,用于向客户端写数据
  • 文本输出:可以直接输出一个字符串到客户端(如返回 HTTP 响应头和正文)
  • HTML输出:从文件系统中读取静态 HTML 文件并写回客户端
  • 404处理:文件不存在时自动返回标准的 404 响应

Servlet 接口

public interface Servlet {
    void init() throws Exception;
    void destroy() throws Exception;
    void service(Request request, Response response) throws Exception;
}

StaticResourceUtil 静态资源

public class StaticResourceUtil {
    public static String getAbsolutePath(String path) {
        String absolutePath = StaticResourceUtil.class.getResource("/").getPath();
        return absolutePath.replace("\\\\", "/") + path;
    }
    public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {
        int count = 0;
        while (count == 0) {
            count = inputStream.available();
        }
        int resourceSize = count;
        outputStream.write(HttpProtocoUtil.getHttpHeader200(resourceSize).getBytes());
        long written = 0;
        int byteSize = 1024;
        byte[] bytes = new byte[byteSize];
        while (written < resourceSize) {
            if (written + byteSize > resourceSize) {
                byteSize = (int) (resourceSize - written);
                bytes = new byte[byteSize];
            }
            inputStream.read(bytes);
            outputStream.write(bytes);
            outputStream.flush();
            written += byteSize;
        }
    }
}

获取资源的绝对路径:getAbsolutePath 会获取到当前类所在的类路径(classpath 根目录),获取的是该目录的实际路径,最终将 path 拼接在 classpath 后,返回静态资源的绝对路径。

输出静态资源内容:outputStaticResource 中 inputStream.available() 试图获取文件长度,但是用 while (count == 0) 等待它非 0 的方式是不稳定的。available() 并不等价于”文件大小”,它是”当前可读字节数”,不适合用于预估资源总长度。

整体的小总结:

  • 类名:StaticResourceUtil,工具类
  • 功能:获取资源路径、输出文件内容到 HTTP
  • 亮点:模拟 Web Server 发送 HTML 等静态文件
  • 关键问题:误用 available()、不检查读取字节数、频繁分配数组
  • 优化方向:用 readAllBytes() 或 while(read != -1) 模式代替原始读取逻辑

WzkServlet 实现类

这里就是对之前的内容的使用,建立了一自己的Servlet,然后启动服务即可!

public class WzkServlet extends HttpServlet {
    @Override
    public void doGet(Request request, Response response) {
        String content = "<h1>WzkServlet GET Hello World!</h1>";
        try {
            response.output(HttpProtocoUtil.getHttpHeader200(content.getBytes().length) + content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void doPost(Request request, Response response) {
        String content = "<h1>WzkServlet POST Hello World!</h1>";
        try {
            response.output(HttpProtocoUtil.getHttpHeader200(content.getBytes().length) + content);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void init() throws Exception {
    }
    @Override
    public void destroy() throws Exception {
    }
}

资源文件

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>wzk-icu</title>
</head>
<body>
<h1>
    wzk-icu 测试页面!
</h1>
</body>
</html>

web.xml

<?xml version="1.0" encoding="UTF-8" ?>
<web-app>
    <servlet>
        <servlet-name>wzkicu</servlet-name>
        <servlet-class>icu.wzk.WzkServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>wzkicu</servlet-name>
        <url-pattern>/wzkicu</url-pattern>
    </servlet-mapping>
</web-app>

启动测试

启动服务后,访问8080端口,可以看到控制台输出了对应的内容,浏览器成功显示了页面。