【学习笔记】手写Tomcat 二

news/2024/12/23 6:39:29/

目录

响应静态资源

HTTP协议请求格式

1. 解析请求信息

创建解析请求类 HttpRequest

2. 创建静态资源目录 webs

3. 封装响应信息

创建静态资源处理器 StaticResourceHandler

创建响应类 HttpResponse

作业

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

2. 优化客户端的连接


响应静态资源

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

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

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

HTTP协议请求格式

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

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

1. 解析请求信息

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

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

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

创建解析请求类 HttpRequest

java">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.htmlString[] 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=3String[] 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

java">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

java">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.ppmy.cn/news/1524413.html

相关文章

动态规划(算法)---02.斐波那契数列模型_三步问题

题目链接&#xff1a; 面试题 08.01. 三步问题 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/three-steps-problem-lcci/description/ 一、题目解析 题目&#xff1a; 题目讲解&#xff1a; 我们先举例查看规律&#xff1a; 第一台阶&#xff1a;我…

对比介绍Java Servlet API (javax.servlet)和Apache HttpClient这两个库

1. 基本概念 Java Servlet API (javax.servlet)&#xff1a; 用途&#xff1a;主要用于构建服务器端的 Web 应用程序&#xff0c;处理 HTTP 请求和响应。功能&#xff1a;提供了创建和管理 Servlet 的接口&#xff0c;允许开发者处理来自客户端的请求并生成动态内容。 Apache H…

AcWing算法基础课-788逆序对的数量-Java题解

大家好&#xff0c;我是何未来&#xff0c;本篇文章给大家讲解《AcWing算法基础课》788 题——逆序对的数量。本文详细讲解了如何通过归并排序算法高效计算数组中的逆序对数量。通过递归分治和归并过程&#xff0c;我们不仅实现了数组的排序&#xff0c;还在排序过程中巧妙地计…

浪潮信息:构建高效、安全数据存储底座的领航者

浪潮信息在最新IDC发布的《中国企业级外部存储市场跟踪报告&#xff0c;2024Q1》中表现抢眼&#xff0c;以11.4%的市场销售额占比稳居中国存储市场第二&#xff0c;同比增长率高达13.6%&#xff0c;领跑头部厂商。这标志着浪潮信息在推动中国存储市场持续增长中扮演了关键角色&…

从表级血缘、列级血缘到算子级血缘,数据管理有哪些提升?

现如今&#xff0c;数据已成为企业决策和运营的核心驱动力&#xff0c;找数、用数已经成为企业实现精细化运营、智能化决策的重要环节。然而&#xff0c;数据规模快速增长、数据资产日益增多、加工链路愈发复杂&#xff0c;导致企业数据管理面临诸多挑战&#xff0c;如复杂数据…

单片机毕业设计基于单片机的智能门禁系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍程序代码部分参考 设计清单具体实现截图参考文献设计获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师&#xff0c;一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP…

Linux 基础命令-系统信息查看

Linux 基础命令——系统信息查看详解 在 Linux 系统中&#xff0c;了解和监控系统的状态和性能对系统管理员和开发者来说至关重要。Linux 提供了一组强大的命令&#xff0c;可以帮助我们查看系统信息&#xff0c;包括硬件、操作系统、CPU、内存、存储、网络等。 一、操作系统…

docker mysql 容器导入数据 .sql文件导入容器

docker mysql 容器导入数据 前言准备工作1、按需准备sql文件2、将文件上传服务器&#xff08;宿主机&#xff09;3、将sql文件复制进容器中 操作步骤1、进入容器内部2、进入数据库3、创建数据库4、切换数据库5、导入sql文件 前言 本文所涉及应用场景&#xff1a;远程部署环境…