从零手写实现 tomcat-04-请求和响应的抽象

创作缘由

平时使用 tomcat 等 web 服务器不可谓不多,但是一直一知半解。

于是想着自己实现一个简单版本,学习一下 tomcat 的精髓。

系列教程

从零手写实现 apache Tomcat-01-入门介绍

从零手写实现 apache Tomcat-02-web.xml 入门详细介绍

从零手写实现 tomcat-03-基本的 socket 实现

从零手写实现 tomcat-04-请求和响应的抽象

从零手写实现 tomcat-05-servlet 处理支持

从零手写实现 tomcat-06-servlet bio/thread/nio/netty 池化处理

从零手写实现 tomcat-07-war 如何解析处理三方的 war 包?

从零手写实现 tomcat-08-tomcat 如何与 springboot 集成?

从零手写实现 tomcat-09-servlet 处理类

从零手写实现 tomcat-10-static resource 静态资源文件

从零手写实现 tomcat-11-filter 过滤器

从零手写实现 tomcat-12-listener 监听器

整体思路

我们针对入参 request 和 出参 response 做一个简单的封装。

v1-出入参的抽象

request

java 复制代码
    /**
     * 请求方式 例如:GET/POST
     */
    private String method;


    /**
     * / , /index.html
     */

    private String url;


    /**
     * 其他的属性都是通过inputStream解析出来的。
     */

    private InputStream inputStream;

    public MiniCatRequest(InputStream inputStream) {
        this.inputStream = inputStream;

        this.readFromStream();
    }

    private void readFromStream() {
        try {
            //从输入流中获取请求信息
            int count = inputStream.available();
            byte[] bytes = new byte[count];
            int readResult = inputStream.read(bytes);
            String inputsStr = new String(bytes);
            logger.info("[MiniCat] readCount={}, input stream {}", readResult, inputsStr);

            //获取第一行数据
            String firstLineStr = inputsStr.split("\\n")[0];  //GET / HTTP/1.1
            String[] strings = firstLineStr.split(" ");
            this.method = strings[0];
            this.url = strings[1];
        } catch (IOException e) {
            logger.error("[MiniCat] readFromStream meet ex", e);
            throw new RuntimeException(e);
        }
    }

这里是针对 http 请求的解析处理。

response

java 复制代码
    private final OutputStream outputStream;

    public MiniCatResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }


    public void write(byte[] bytes) {
        try {
            outputStream.write(bytes);
        } catch (IOException e) {
            throw new MiniCatException(e);
        }
    }

start 方法调整

直接改动为对应的出入参对象。

java 复制代码
while(runningFlag && !serverSocket.isClosed()){
    Socket socket = serverSocket.accept();
    // 输入流
    InputStream inputStream = socket.getInputStream();
    MiniCatRequest request = new MiniCatRequest(inputStream);
    // 输出流
    MiniCatResponse response = new MiniCatResponse(socket.getOutputStream());
    response.write(InnerHttpUtil.httpResp("Hello miniCat!").getBytes());
    socket.close();
}

测试

less 复制代码
[INFO] [2024-04-02 16:27:32.455] [Thread-0] [c.g.h.m.b.MiniCatBootstrap.startSync] - [MiniCat] start listen on port 8080
[INFO] [2024-04-02 16:27:32.455] [Thread-0] [c.g.h.m.b.MiniCatBootstrap.startSync] - [MiniCat] visit url http://127.0.0.1:8080

我们浏览器访问 http://127.0.0.1:8080

读取到的流内容为:

less 复制代码
[INFO] [2024-04-02 16:28:17.825] [Thread-0] [c.g.h.m.d.MiniCatRequest.readFromStream] - [MiniCat] readCount=664, input stream GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
sec-ch-ua: "Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9

v2-返回静态资源文件

思路

我们根据 url 解析本地的 html 等静态资源信息。

resources 下面的文件,mvn clean install 之后,默认在 ~\target\classes 路径下

核心代码

java 复制代码
// 输出流
MiniCatResponse response = new MiniCatResponse(socket.getOutputStream());
// 判断文件是否存在
String staticHtmlPath = request.getUrl();
if (staticHtmlPath.endsWith(".html")) {
    String absolutePath = ResourceUtil.buildFullPath(ResourceUtil.getClassRootResource(MiniCatBootstrap.class), staticHtmlPath);
    String content = FileUtil.getFileContent(absolutePath);
    logger.info("[MiniCat] static html path: {}, content={}", absolutePath, content);
    String html = InnerHttpUtil.http200Resp(content);
    response.write(html);
} else {
    String html = InnerHttpUtil.http404Resp();
    response.write(html);
}

主要是两个步骤:

1)获取当前 class 文件对应的资源文件根路径。

2)然后拼接完整文件路径,读取文件内容。

测试

比如我们在 resource 下面放一个 index.html

内容如下:

html 复制代码
mini cat index html!

启动后,访问:

访问 http://127.0.0.1:8080/index.html

页面返回:

bash 复制代码
mini cat index html!

开源地址

scss 复制代码
 /\_/\  
( o.o ) 
 > ^ <

mini-cat 是简易版本的 tomcat 实现。别称【嗅虎】(心有猛虎,轻嗅蔷薇。)

开源地址:github.com/houbb/minic...

相关推荐
唐青枫7 小时前
Java Gradle 实战指南:从 Wrapper、Kotlin DSL 到 Spring Boot 多模块构建
java·kotlin·gradle
带刺的坐椅7 小时前
Solon TeamAgent 协作协议:从 SEQUENTIAL 流水线到 HIERARCHICAL 主管团队
java·ai·llm·agent·solon
wuqingshun3141598 小时前
SpringBoot是如何实现自动配置的
java·spring boot·后端
用户053704031438 小时前
我的学习记录泛型
java
zhouhui0018 小时前
AI帮我写了个Spring Boot校验,线上漏掉了这组边界条件
java·spring boot·redis·ai编程
JRedisX8 小时前
Java从零手写企业级内存数据库
java
萧瑟余晖8 小时前
JDK 20 新特性详解
java·开发语言
hdsoft_huge8 小时前
SpringBoot系列17:日志体系Logback配置,日志分割、脱敏、异常堆栈收集规范(生产级落地)
java·spring boot·logback
吃饱了得干活8 小时前
扫码登录如何实现?从原理到实战
java·spring boot·redis
JRedisX8 小时前
JRedisX 项目骨架搭建指南 从零开始 — 手把手教你搭
java