【学习笔记】手写Tomcat 二

news2024/9/25 11:12:27

目录

响应静态资源

HTTP协议请求格式

1. 解析请求信息

创建解析请求类 HttpRequest

2. 创建静态资源目录 webs

3. 封装响应信息

创建静态资源处理器 StaticResourceHandler

创建响应类 HttpResponse

作业

1. 绘制 请求解析类 HttpRequest 和响应类 HttpResponse 的封装流程图

2. 优化客户端的连接


响应静态资源

在昨天的基础上,再进一步优化,能够响应HTML文件,图片等静态资源

在响应静态资源之前,我们要先确定客户端请求的是哪个静态资源,是HTML页面还是图片呢?

我们先打印一下客户端的请求信息

HTTP协议请求格式

可以看到,第一行也就是 请求行 ,它有请求方法,请求URL和请求协议

请求URL就是需要请求的资源

1. 解析请求信息

那如何拿到 请求的URL呢?然后根据请求URL响应对应的资源

我们看一下 HTTP协议的请求格式,不难发现,都是以回车符换行符结尾

以 回车符和换行符进行分割,然后再根据 空格 分割就可以拿到请求URL了

创建解析请求类 HttpRequest

package com.shao.net;

import java.util.HashMap;

public class HttpRequest {
    /**
     * 请求信息
     */
    private String msg;

    /**
     * 请求行
     */
    private String requestLine;
    /**
     * 请求行的请求方法
     */
    private String requestMethod;

    /**
     * 请求行的请求URI,请求路径
     */
    private String requestURI;
    /**
     * 请求行的请求模块,例如:/index.html?a=1 请求模块为:/index.html
     */
    private String requestModule;
    /**
     * 请求行的请求协议
     */
    private String requestProtocol;

    /**
     * 存储请求头参数
     */
    private HashMap<String, String> requestHeaderParams = new HashMap<>();

    /**
     * 存储请求体参数
     */
    private HashMap<String, String> requestBodyParams = new HashMap<>();

    /**
     * 构造函数
     */
    public HttpRequest(String msg) {
        this.msg = msg;

        // 根据HTTP协议格式 分割请求信息
        String[] requestArr = msg.split("\r\n");
        // 把数组中的第一个元素赋值给请求行
        requestLine = requestArr[0];


        // 1. 解析请求行
        parseRequestLine();

        // 2. 解析请求头
        parseRequestHeader(requestArr);

        // 3. 解析请求体
        parseRequestBody();
    }

    // 1. 解析请求行
    private void parseRequestLine() {

        // 把请求行按空格分割
        String[] requestParts = requestLine.split(" ");
        // 请求方法
        requestMethod = requestParts[0];
        // 请求资源路径
        requestURI = requestParts[1];
        // 请求协议
        requestProtocol = requestParts[2];

        // 如果请求方法是GET,则根据 ? 号分割,获取请求模块,例如:/index.html?a=1 请求模块为:/index.html
        String[] split = requestURI.split("\\?");
        requestModule = split[0];

        System.out.println("请求方法:" + requestMethod);
        System.out.println("请求uri:" + requestURI);
        System.out.println("请求协议:" + requestProtocol);
        System.out.println("请求模块:" + requestModule);

    }

    // 2. 解析请求头
    private void parseRequestHeader(String[] requestArr) {
        // 检查请求数组,判断是否为空,判断有没有请求头
        if (requestArr == null || requestArr.length <= 1) {
            return;
        }
        // 遍历请求数组,从第二个元素开始,因为第一个元素是请求行
        for (int i = 1; i < requestArr.length; i++) {
            // 判断是否为空,如果为空,表示请求头结束,因为 HTTP 协议中,请求头和请求体之间有2个回车符 换行符
            String headerLine = requestArr[i];
            if (headerLine.length() == 0) {
                break;
            }
            // 把请求头按 :  分割,获取请求头参数
            // 注意:这里需要使用 ": ",而不是 ":"
            String[] headerParts = headerLine.split(": ");
            // 判断请求头的格式是否正确
            if (headerParts.length >= 2) {
                requestHeaderParams.put(headerParts[0], headerParts[1]);
            }
        }
        System.out.println("请求头参数:" + requestHeaderParams);

    }

    // 3. 解析请求体
    private void parseRequestBody() {
        // POST 方法的请求参数是在请求体,GET 方法的请求参数在请求行
        if (this.requestMethod.equalsIgnoreCase("POST")) {
            // 分割请求信息
            String[] split = msg.split("\r\n\r\n");
            // 如果分割后的长度 >= 2,表示有请求体
            if (split.length >= 2) {
                // 分割请求体
                splitRequestBody(split[1]);
            }
        } else if (this.requestMethod.equalsIgnoreCase("GET")) {
            // 把请求行按空格分割,获取请求参数
            String[] requestLineParts = this.requestLine.split(" ");
            String[] split = requestLineParts[1].split("\\?");
            if (split.length >= 2) {
                // 分割请求体
                splitRequestBody(split[1]);
            }
        }
        System.out.println("请求体参数:" + requestBodyParams);
    }

    private void splitRequestBody(String requestBody) {
        // 分割请求参数,例如:a=1&b=2&c=3
        String[] requestBodyParts = requestBody.split("&");
        // 遍历请求体
        for (int i = 0; i < requestBodyParts.length; i++) {
            String part = requestBodyParts[i];
            // 把请求参数按 = 分割,获取键值对,最多分割2部分,如果有多个 = ,只保留第一个 = 之前和之后的部分
            String[] keyValue = part.split("=", 2);
            if (keyValue.length == 2) {
                requestBodyParams.put(keyValue[0], keyValue[1]);
            } else {
                System.out.println("警告:非法格式的请求体:" + part);
            }
        }
    }

    /**
     * 获取
     * @return msg
     */
    public String getMsg() {
        return msg;
    }

    /**
     * 设置
     * @param msg
     */
    public void setMsg(String msg) {
        this.msg = msg;
    }

    /**
     * 获取
     * @return requestLine
     */
    public String getRequestLine() {
        return requestLine;
    }

    /**
     * 设置
     * @param requestLine
     */
    public void setRequestLine(String requestLine) {
        this.requestLine = requestLine;
    }

    /**
     * 获取
     * @return requestMethod
     */
    public String getRequestMethod() {
        return requestMethod;
    }

    /**
     * 设置
     * @param requestMethod
     */
    public void setRequestMethod(String requestMethod) {
        this.requestMethod = requestMethod;
    }

    /**
     * 获取
     * @return requestURI
     */
    public String getRequestURI() {
        return requestURI;
    }

    /**
     * 设置
     * @param requestURI
     */
    public void setRequestURI(String requestURI) {
        this.requestURI = requestURI;
    }

    /**
     * 获取
     * @return requestModule
     */
    public String getRequestModule() {
        return requestModule;
    }

    /**
     * 设置
     * @param requestModule
     */
    public void setRequestModule(String requestModule) {
        this.requestModule = requestModule;
    }

    /**
     * 获取
     * @return requestProtocol
     */
    public String getRequestProtocol() {
        return requestProtocol;
    }

    /**
     * 设置
     * @param requestProtocol
     */
    public void setRequestProtocol(String requestProtocol) {
        this.requestProtocol = requestProtocol;
    }

    /**
     * 获取
     * @return requestHeaderParams
     */
    public HashMap<String, String> getRequestHeaderParams() {
        return requestHeaderParams;
    }

    /**
     * 设置
     * @param requestHeaderParams
     */
    public void setRequestHeaderParams(HashMap<String, String> requestHeaderParams) {
        this.requestHeaderParams = requestHeaderParams;
    }

    /**
     * 获取
     * @return requestBodyParams
     */
    public HashMap<String, String> getRequestBodyParams() {
        return requestBodyParams;
    }

    /**
     * 设置
     * @param requestBodyParams
     */
    public void setRequestBodyParams(HashMap<String, String> requestBodyParams) {
        this.requestBodyParams = requestBodyParams;
    }

    public String toString() {
        return "HttpRequest{msg = " + msg + ", requestLine = " + requestLine + ", requestMethod = " + requestMethod + ", requestURI = " + requestURI + ", requestModule = " + requestModule + ", requestProtocol = " + requestProtocol + ", requestHeaderParams = " + requestHeaderParams + ", requestBodyParams = " + requestBodyParams + "}";
    }
}

调用 HttpRequest 解析请求信息

游览器发送请求

打印解析后的请求信息,可以看到请求的静态资源是 /index.html

拿到了请求uri,我们就可以根据 请求uri 响应静态资源了

那这个静态资源放在哪儿呢?

2. 创建静态资源目录 webs

创建一个 HTML 文件,内容先写英文,写中文需要在响应头加上 UTF-8 字符编码

那现在 index.html 有了,怎么响应给客户端呢?

读取 index.html 文件,将读取到的内容发送到游览器,游览器会自动渲染页面

3. 封装响应信息

把响应信息的代码拿出来,单独放到一个类里,这个类就只需要响应数据

这样可以降低代码的耦合度

在创建响应类之前有两个问题

1. 怎么确定请求的是静态资源?动态资源一般需要操作数据库

2. 响应的媒体类型怎么写?

如果响应的是图片,那么就需要修改为 Content-Type:image/jpeg 或 image/png

所以,就需要动态的判断响应的静态资源是什么媒体类型。

创建静态资源处理器 StaticResourceHandler

package com.shao.net;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class StaticResourceHandler {

    // 常见静态资源扩展名
    private static final String[] staticExtensions = new String[]{".html", ".css", ".js", ".jpg", ".png", ".gif", ".ico", ".svg", ".pdf", ".txt"};

    /**
     * 判断是否静态资源
     * 如果是静态资源则返回 true ,否则返回 false
     */
    public static boolean isLikelyStaticResource(String fileName) {

        // 检查文件的扩展名是否存在于静态资源扩展名数组中
        for (String ext : staticExtensions) {
            if (fileName.endsWith(ext)) {
                return true;
            }
        }
        return false;
    }


    /**
     * 根据文件路径获取文件内容
     */
    public static byte[] getFileContents(String filePath) {
        // 1. 获取文件对象
        File file = new File(filePath);
        // 2. 判断文件是否存在
        if (!file.exists()) {
            return null;
        }

        // 3. 获取文件内容

        // 定义一个字节数组,数组大小根据文件大小确定
        byte[] fileContents = new byte[0];
        try {
            FileInputStream fis = new FileInputStream(file);

            // fis.available() 是读取的文件的字节数
            fileContents = new byte[fis.available()];

            // 读取文件存放到 fileContents 数组中
            fis.read(fileContents);

            // 关闭文件输入流
            fis.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return fileContents;
    }

    /**
     * 获取文件的媒体类型
     */
    public static String getFileMimeType(String filePath) {
        // 1 获取文件对象
        File file = new File(filePath);

        // 2 判断文件是否存在
        if (!file.exists()) {
            return "text/html";
        }
        // 3 获取文件的媒体类型
        String fileType = null;
        try {
            fileType = Files.probeContentType(Paths.get(filePath));

        } catch (IOException e) {
            e.printStackTrace();
        }
        return fileType;
    }

}

 

创建响应类 HttpResponse

package com.shao.net;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

public class HttpResponse {

    /**
     * 输出流
     */
    private OutputStream os;

    /**
     * 解析请求信息的对象
     */
    private HttpRequest httpRequest;

    public HttpResponse(OutputStream os, HttpRequest httpRequest) {
        this.os = os;
        this.httpRequest = httpRequest;
    }

    public void response(String filePath) {
        //判断请求的是否为静态文件
        if (StaticResourceHandler.isLikelyStaticResource(httpRequest.getRequestModule())) {
            // 获取静态资源一般是 GET 请求方法
            if (httpRequest.getRequestMethod().equals("GET")) {
                // 响应静态资源
                responseStaticResource(filePath);
            }
        } else {
            // 处理动态请求
            System.out.println("请求动态资源");
        }
    }

    /**
     * 响应静态资源
     */
    private void responseStaticResource(String filePath) {
        // 读取文件
        byte[] fileContents = StaticResourceHandler.getFileContents(filePath);

        // 判断文件是否存在,不存在则返回 404 的页面
        if (fileContents == null) {
            try {
                FileInputStream fis = new FileInputStream("webs/pages/not_Found404.html");

                fileContents = new byte[fis.available()];

                fis.read(fileContents);

                fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // 响应协议
        String protocol = httpRequest.getRequestProtocol();
        // 文件媒体类型
        String fileMimeType = StaticResourceHandler.getFileMimeType(filePath);
        try {
            os.write((protocol + " 200 OK\r\n").getBytes());
            os.write(("Content-Type: " + fileMimeType + "\r\n").getBytes());
            os.write(("Content-Length: " + fileContents.length + "\r\n").getBytes());
            os.write("\r\n".getBytes());
            os.write(fileContents);
            os.flush();
            System.out.println("响应成功");
            os.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

然后就可以调用响应类了

作业

1. 绘制 请求解析类 HttpRequest 和响应类 HttpResponse 的封装流程图

2. 优化客户端的连接

现在只有主线程处理客户端连接,如果同一时刻有多个客户端发起连接,同一时间只能处理一个客户端的连接,这样效率不高,怎么处理?

解决方案是来一个客户端连接就创建一条线程去处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2126196.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

动手学深度学习(pytorch土堆)-03常见的Transforms

Compose transforms.Compose 是 PyTorch 中的一个函数&#xff0c;用于将多个图像变换操作组合在一起&#xff0c;形成一个变换流水线。这样可以将一系列的图像处理操作整合为一个步骤&#xff0c;便于对图像进行批量预处理或增强。 基本用法 transforms.Compose 接受一个列表…

vue + Lodop 制作可视化设计页面 实现打印设计功能(四)

历史&#xff1a; vue2 Lodop 制作可视化设计页面 实现打印设计功能&#xff08;一&#xff09; vue Lodop 制作可视化设计页面 实现打印设计功能&#xff08;二&#xff09; vue Lodop 制作可视化设计页面 实现打印设计功能&#xff08;三&#xff09; 前言&#xff1a…

必看!实网环境下,合宙Cat.1模组低功耗详细数据

一、背景说明 合宙4G Cat.1模组以低功耗为显著特点&#xff0c;提供了三种功耗模式以适应不同需求。 分别是&#xff1a;常规模式&#xff0c;低功耗模式&#xff0c;PSM模式。 在实际应用中&#xff0c;用户可以根据不同的应用场景和需求&#xff0c;灵活选择合宙4G Cat.1模…

数据结构与算法图论 并查集

前言 写一道并查集的题 判断是否为亲戚 原题目&#xff1a;现在有若干家族图谱关系&#xff0c;给出了一些亲戚关系&#xff0c;如Marrv和Tom是亲戚&#xff0c;Tom和Ben是亲戚等等。从这些信息中&#xff0c;你可以推导出Marry和Ben是亲戚。请写一个程序&#xff0c;对于我…

一文读懂在线学习凸优化技术

一文读懂在线学习凸优化技术 在当今的数据驱动时代&#xff0c;机器学习算法已成为解决复杂问题的关键工具。在线学习凸优化作为机器学习中的一项核心技术&#xff0c;不仅在理论研究上具有重要意义&#xff0c;还在实际应用中展现出巨大的潜力。本文将深入浅出地介绍在线学习…

C++(三)----内存管理

1.C/C内存分布 看下面这个问题&#xff08;考考你们之前学的咋样&#xff09;&#xff1a; int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";char* pCh…

【leetcode C++】 动态规划

4. 91 解码方法 题目&#xff1a; 一条包含字母 A-Z 的消息通过以下映射进行了 编码 &#xff1a; "1" -> A "2" -> B ... "25" -> Y "26" -> Z 然而&#xff0c;在 解码 已编码的消息时&#xff0c;你意识到有许多不同的…

数据湖-方案对比

数据湖架构结合了数据湖和数据仓库。虽然它不仅仅是两者之间的简单集成&#xff0c;但其理念是充分发挥两种架构的优势&#xff1a;数据仓库的可靠交易以及数据湖的可扩展性和低成本。 Lakehouse 架构支持管理各种数据类型&#xff0c;例如结构化、半结构化和非结构化数据&…

巧用工具,Vue 集成 medium-zoom 实现图片缩放

文章目录 巧用工具&#xff0c;Vue 集成 medium-zoom 实现图片缩放介绍medium-zoomVue3集成 medium-zoom 示例Vue2集成 medium-zoom 示例进阶 - 可选参数 巧用工具&#xff0c;Vue 集成 medium-zoom 实现图片缩放 在现代网页开发中&#xff0c;为用户提供良好的视觉体验至关重…

爬虫逆向学习(六):补环境过某数四代

声明&#xff1a;本篇文章内容是整理并分享在学习网上各位大佬的优秀知识后的实战与踩坑记录 引用博客&#xff1a; https://blog.csdn.net/shayuchaor/article/details/103629294 https://blog.csdn.net/qq_36291294/article/details/127699273 https://blog.csdn.net/weixin_…

vivo手机已删除的短信还能恢复吗?

虽然现在我们很少使用vivo手机的短信功能&#xff0c;但是我们偶尔还会通过vivo手机短信功能接收一些重要的信息。如果我们在清理垃圾短信的时候误删了vivo手机重要短信&#xff0c;该怎么恢复呢&#xff1f; 方法一&#xff1a;通过vivo云服务恢复 1、确保您已开启vivo云服务…

Go常用正则函数整理

前言 在Go语言中&#xff0c;标准库regexp提供了丰富的API来处理正则表达式&#xff0c;支持编译、匹配、查找、替换等操作。以下是一个Go标准库正则函数的常用大全教程及部分使用示例。 Go标准库常用正则函数 Compile&#xff1a;编译正则表达式&#xff0c;返回一个可用于…

Qt 构建报错 undefined reference to xxx

初次学习使用Qt&#xff0c;在构建时报错。这是个典型报错&#xff0c;熟练vs与c的朋友一眼就可以看出要么是库出问题了&#xff0c;要么是库链接出问题了。那么就可以按照相对明确的方向找问题。 1、确认库与项目配置是否一致。32位还是64位&#xff0c;debug还是release一定…

如何逆转Instagram账号流量减少?4个实用技巧分享

Instagram作为全球十大社媒之一&#xff0c;不仅是个人分享生活的平台&#xff0c;还是跨境卖家进行宣传推广和客户开发的关键工具。在运营Instagram的过程中&#xff0c;稍有不慎就容易出现账号被限流的情况&#xff0c;对于账号状态和运营工作的进行都十分不利。 一、如何判断…

new String(),toString()和Arrays.toString()的区别

下面写了一段代码来展示结果 import javax.sound.midi.Soundbank; import java.util.Arrays; import java.util.Scanner;public class Main {public static void main(String[] args) {String str "abc";System.out.println("str:"str);char[] chars st…

若楠带你初识OpenCV(7) -- 轮廓检测之银行卡号识别

文章目录 银行卡号识别详细流程一、设置参数二、函数准备三、具体步骤1. 得到每个数字的信息2. 银行卡处理3. 找到数字边框4. 模板匹配 总结 银行卡号识别 经过了几篇关于轮廓检测的学习&#xff0c;本篇我们来尝试完成对银行卡号的检测识别&#xff1a; 目标&#xff0c;识别…

获取时间,并将时间按一定的格式输出

一、时间函数 1.获取秒数 CTS 北京时间 2.转换为需要个格式 系统时间的获取: 1.time 获得秒数 time_t time(time_t *t); time_t 类型变量 第一种 准备一个变量 time_t tm; time(&tm) 第二种 tm time(NULL); 功能: 获得1970年到现在的秒数 第一步&#xff0c;先获得秒…

OpenHarmony鸿蒙( Beta5.0)RTSPServer实现播放视频详解

鸿蒙开发往期必看&#xff1a; 一分钟了解”纯血版&#xff01;鸿蒙HarmonyOS Next应用开发&#xff01; “非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门到精通&#xff09; “一杯冰美式的时间” 了解鸿蒙HarmonyOS Next应用开发路…

JavaScript基础语法(超详细!)

为什么学JS&#xff1f; 1.页面动态效果 2.表单验证 可以包含在文档中的任何地方&#xff0c;只要保证这些代码在被使用前已读取并加载到内存即可 <script>… </script>网页中引用JavaScript的方式&#xff1a; 1.使用<script>标签外部JS文件 <scrip…

层归一化(201607)

Layer Normalization 层归一化 https://arxiv.org/abs/1607.06450 Abstract Training state-of-the-art, deep neural networks is computationally expensive. One way to reduce the training time is to normalize the activities of the neurons. A recently introduced…