纯手写Tomcat,看不懂你来揍我【附源码、图文详解】

源码放在了文章末尾

理论知识

何为Tomcat

        Tomcat是一个开源的Servlet容器,它实现了Java Servlet、JavaServer Pages (JSP)、WebSocket等Java EE规范,用于在Web服务器上运行Java Web应用程序。

        说的简单点,Tomcat能处理网络传输来的请求。

输入输出流

        也就是说,Tomcat要帮我们完成客户端和服务器之间的连接、传输。传输的时候是用输入输出流来传输的。

客户端和服务器的通信,说到底就是两个数据的传输,客户端发送inputStream给服务器,服务器回复outputStream给客户端。

HTTP请求

 

http请求也就是 web浏览器发送给web服务器(Tomcat)之间的传输数据协议。也就是商量好一个格式去传输,这样服务器收到了之后,就能对其进行解析了,就知道了浏览器想表达的意思,再对其进行反馈。

http请求协议部分数据

GET /user HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

第一部分:请求行:请求类型,资源路径以及http版本(上述第一行)

第二部分:请求头:紧接在请求行之后,用于说明服务器需要使用的附加信息(第二到第八行)

第三部分:空行(请求头和主体之间必须有换行)

第四部分:主体数据,可以添加任意数据

HTTP响应

        HTTP响应是Web服务器向客户端(通常是浏览器)返回的数据。当客户端发送HTTP请求后,服务器会根据请求的内容和要求生成一个HTTP响应,将其发送回客户端。在仿写Tomcat时,了解HTTP响应的结构和内容是很重要的。

        HTTP 响应是服务器向客户端发送的数据,用于回应客户端的请求。HTTP 响应需要满足一定的格式和要求,以下是一个标准的 HTTP 响应的格式

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 37<html>
<head><title>简单的HTTP响应示例</title>
</head>
<body><h1>Hello, World!</h1>
</body>
</html>
  • HTTP/1.1 200 OK:状态行,表示HTTP协议版本为1.1,状态码为200,状态短语为OK。这表示请求成功。

  • Content-Type: text/html:响应头部,指定响应内容的类型为HTML。

  • Content-Length: 37:响应头部,指定响应内容的长度,以字节为单位。

  • 空行:用于分隔响应头部和响应体。

  • 响应体:实际的响应内容。在本例中,它是一个简单的HTML页面,显示了"Hello, World!"。

静态请求和动态请求

        在Web服务中,静态请求和动态请求是两种不同类型的HTTP请求,用于获取和呈现网页内容。它们有不同的特点和用途:

  1. 静态请求:静态请求是指浏览器请求服务器上的静态资源,如HTML、CSS、JavaScript、图像文件等,这些资源在服务器上存储为不可更改的文件。
  2. 动态请求:动态请求是指浏览器请求服务器上的动态生成内容,通常是通过服务器端的程序逻辑来生成的,如PHP、Python、Ruby等脚本语言。服务器会根据请求的参数和逻辑生成内容,然后将内容返回给浏览器。

        总之,静态请求和动态请求在Web服务中都起着重要作用,静态请求用于提供固定不变的资源,而动态请求用于生成个性化、实时更新的内容。

项目演示

启动Tomcat

 访问 首页

url输入错误,404页面测试

 

静态请求测试:访问html页面 

静态请求测试:css

 动态请求测试:登录

 

 

项目流程

项目目录

 

简单流程图

客户端(浏览器)发送一个请求,Tomcat一直在监听,当受到请求后,开始解析这个请求信息,解析完毕开始处理请求,处理完毕就封装响应信息,再返回给前端。 

详细流程图

 详细的流程请看下面讲解

1启动类:MyTomcat

  1. serverSocket: 是一个 ServerSocket 对象,它通过 ServerSocket 类创建,并在特定端口上监听连接请求。
  2. accept()方法是 ServerSocket 类的一个方法,它会阻塞程序执行,等待客户端连接。当有客户端连接到服务器时,accept() 方法将返回一个新的 Socket 对象,表示与客户端之间的连接。

2.线程任务处理类:ThreadTask

具体就是做下面四个步骤

  1. 对客户端收到的流数据进行解析与封装,得到request对象
  2. 根据流数据与request对象得到response对象
  3. 对静态请求与动态请求分开处理,完善响应对象
  4. 关闭连接

这四步骤也就是tomcat的全部了,但是具体的每个步骤的细分还有很多

 

 

3.请求类:HttpServletRequest

请求信息类,对客户端收到的输入流数据进行解析与封装,得到request对象

将输入流转换成String,开始解析

/*** 将输入流转换成String,开始解析*/
public HttpServletRequest(InputStream iis) {//一次性读完所有请求信息StringBuilder sb = new StringBuilder();int length = -1;byte[] bs = new byte[100*1024];try {length = iis.read(bs);//读取socket输入流数据,将其放到byte数组里面} catch (IOException e) {e.printStackTrace();System.out.println("读取客户请求异常");}//将bs中的字节数据转为charfor(int i = 0;i<length;i++){sb.append((char)bs[i]);}content = sb.toString();//将sb转换成String,存到content里面parseProtocol();      //开始解析
}

具体解析操作

解析协议

/*** 解析协议*/
private void parseProtocol() {String[] ss = content.split(" ");//解析 请求方法类型,存到methodthis.method = ss[0];//解析 请求地址,存到requestURIthis.requestURI = ss[1];//解析 请求参数,存到parameter的map中parseParameter();//解析 请求头,存到headers中parseHeader();//解析 请求cookie:从headers中取cookieparseCookie();//解析 sessionId:从cookie中取出jsessionidjsessionid = parseJSessionId();
}

各种解析方法

private String parseJSessionId() {if(cookies!=null&&cookies.size()>0) {for(Cookie c:cookies) {if("JSESSIONID".equals(c.getName())) {return c.getValue();}}} return null;
}/*** headers中取出cookie,然后在解析出cookie对象存在cookies中* 取出协议中的 Cookie:xxxx  ,如果有则说明已经生成过Cookie  没有则表明是第一次请求,要生成Cookie编号*/
private void parseCookie() {if(headers==null&&headers.size()<=0){return;}//从headers中取出键为cookie的 String cookieValue = headers.get("Cookie");if(cookieValue == null || cookieValue.length()<=0) {return;}String[] cvs = cookieValue.split(": ");if(cvs.length > 0) {for(String cv:cvs) {String[] str = cv.split("=");if(str.length > 0) {String key = str[0];String value = str[1];Cookie c = new Cookie(key,value);cookies.add(c);}}}
}
private void parseHeader() {//请求头String[] parts = this.content.split("\r\n\r\n");//GET /请求地址 HTTP/1.1String[] headerss = parts[0].split("\r\n");for(int i = 1;i<headerss.length;i++){String[] headPair = headerss[i].split(": ");//Host: localhost:8888     Connection: keep-alive ...headers.put(headPair[0], headPair[1]);}
}/*** 取参数*/
private void parseParameter() {//requestURI: user.action?name=z&password=aint index = this.requestURI.indexOf("?");//有?的话if(index>=1){String[] pairs = this.requestURI.substring(index+1).split("&");for(String p:pairs){String[] po = p.split("=");parameter.put(po[0], po[1]);}}if(this.method.equals("POST")){String[] parts = this.content.split("\r\n\r\n");String entity = parts[1];String[] pairs = entity.split("&");for(String p:pairs){String[] po = p.split("=");parameter.put(po[0], po[1]);}}
}

4.响应类:HttpServletResponse

        响应类:根据传过来的请求对象拿到 URL,找到请求的资源文件,设置对应的响应类型,将文件写入到响应流中返回

        如果是静态请求,就会调用到相应类,因为静态请求就是要获取某个HTML、CSS、JavaScript、图像等文件,所以我们只需要从请求url中解析出文件的名称,再找到这个文件,再按照http响应的格式的写入到输出流中,返回给前端就行了。

按照不同类型调用send方法

//3、发送文件响应,不同的文件返回不同类型if(file.getName().endsWith(".jpg")){send(file,"application/x-jpg",code);}else if(file.getName().endsWith(".jpe")||file.getName().endsWith(".jpeg")){send(file,"image/jpeg",code);}else if(file.getName().endsWith(".gif")){send(file,"image/gif",code);}else if(file.getName().endsWith(".css")){send(file,"text/css",code);}else if(file.getName().endsWith(".js")){send(file,"application/x-javascript",code);}else if(file.getName().endsWith(".swf")){send(file,"application/x-shockwave-flash",code);}else{send(file,"text/html",code);}

send方法先调用genProtocol方法,先拼接好响应格式

   /*** 拼接响应协议*/
private String genProtocol(long length, String contentType, int code) {String result = "HTTP/1.1 "+code+" OK\r\n";result+="Server: myTomcat\r\n";result+="Content-Type: "+contentType+";charset=utf-8\r\n"; result+="Content-Length: "+length+"\r\n";result+="Date: "+new Date()+"\r\n"; result+="\r\n";return result;
}

send方法再调用readFile方法把文件读出来,返回字节数组

   /*** 读取文件*/
private byte[] readFile(File file) {ByteArrayOutputStream baos = new ByteArrayOutputStream();FileInputStream fis = null;try {fis = new FileInputStream(file);byte[] bs = new byte[1024];int length;while((length = fis.read(bs,0,bs.length))!=-1){baos.write(bs, 0, length);baos.flush();}} catch (Exception e) {e.printStackTrace();}finally{try {fis.close();} catch (IOException e) {e.printStackTrace();}}return baos.toByteArray();
}

最后send方法就返回给前端流数据了

   /*** 返回给前端响应流*/
private void send(File file, String contentType, int code) {try {String responseHeader = genProtocol(file.length(),contentType,code);byte[] bs = readFile(file);this.oos.write(responseHeader.getBytes());this.oos.flush();//往前端传过去this.oos.write(bs);this.oos.flush();} catch (IOException e) {e.printStackTrace();}finally{try {this.oos.close();} catch (IOException e) {e.printStackTrace();}}
}

5.处理接口、动态处理类、静态处理类

        静态处理就是前端需要一些静态的资源,例如HTML、CSS、图片等,后端直接找到文件,按照格式返回给前端就行了

        动态处理 就有对应得到业务了,要调用对应的servlet

        区分动态还是静态,我这里是这样区分的:URL中有.action的就是动态,其他就是静态,下面代码就是在线程任务处理类里面的代码。

处理接口主要就是一个处理类的接口,动态处理类和静态处理类实现了它

/*** 处理器* 处理请求的接口** @author 康有为* @date 2023/08/17*/
public interface Processor {/** 处理请求,给出响应* @param request 请求* @param response 响应*/void process(HttpServletRequest request, HttpServletResponse response);
}

 静态处理类就很简单,直接调用相应类的sendRedirect方法,返回给前端就行了。

/*** 静态处理器*     实现处理接口** @author 康有为* @date 2023/08/17*/
public class StaticProcessor implements Processor {@Overridepublic void process(HttpServletRequest request, HttpServletResponse response) {//调用响应类的 sendRedirect方法response.sendRedirect();}}

动态处理类

因为动态处理的URL中肯定是有“.action”结尾的(我们自定义的),所以我们要解析URL,拿到.action 前面的那些东西。

例如:localhost:8888/User.action 这个请求,我们拿到User之后,再后面拼接一个“Servlet”,再利用反射,在对应的存放servlet实例文件目录中找到UserServlet,再调用其对应的servlet的方法就行了。

 

6.servlet实例类

这里就是对前端的动态请求进行处理和反馈了,具体的servlet实例就根据不同的业务来处理就行。

注意:servlet实例必须要放到指定的目录下“servlet”,因为我们处理动态请求的时候是用反射来扫描了“servlet”目录,放在其他目录下就找不到了

例如下图,是登录的servlet,那就返回给前端登录成功,并附上session即可。

 

参考文章:【Tomcat】——纯手写实现一个简单的Tomcat_手写tomcat实现部署功能_土豆是我的最爱的博客-CSDN博客

具体的详细代码,请看源码 康有为/手写Tomcat - 码云 - 开源中国 (gitee.com)

喜欢的话,点赞支持一波

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

广告牌安全传感器,实时监测事故隐患尽在掌握

在现代城市中&#xff0c;广告牌作为商业宣传的重要媒介&#xff0c;已然成为城市中一道独特的风景线。然而&#xff0c;随着城市迅速发展&#xff0c;广告牌的安全问题也引起了大众关注。广告招牌一般悬挂于建筑物高处&#xff0c;量大面大。由于设计、材料、施工方法的缺陷&a…

PCIE超高速实时运动控制卡在六面外观视觉检测上的应用

市场应用背景 XPCIE1028超高速实时运动控制卡在六面外观检测高速视觉筛选中的应用&#xff0c;结合正运动技术提供的专用筛选机调试软件&#xff0c;可实现15000pcs/分钟的IO触发检测速度&#xff0c;只需简单参数设置&#xff0c;搭配图像采集硬件和视觉处理软件&#xff0c;…

【算法】活用双指针完成复写零操作

Problem: 1089. 复写零 文章目录 题目解析算法原理分析找到最后一个复写的位置从后往前进行复写操作 代码展示 题目解析 首先我们来分析一下本题的题目意思 可以看到题目中给到了一个数组&#xff0c;意思是让我们将数组中的零元素都复写一遍&#xff0c;然后将其余的元素向后平…

python知识:什么是字符编码?

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 我们的MySQL使用latin1的默认字符集&#xff0c; 也就是说&#xff0c;对汉字字段直接使用GBK内码的编码进行存储&#xff0c; 当需要对一些有汉字的字段进行拼音排序时&#xff08;特别涉及到类似于名字这样的字段时…

图数据库_Neo4j学习cypher语言_使用CQL_构建明星关系图谱_导入明星数据_导入明星关系数据_创建明星关系---Neo4j图数据库工作笔记0009

首先找到明星数据 可以看到有一个sheet1,是,记录了所有的关系的数据 然后比如我们搜索一个撒贝宁,可以看到撒贝宁的数据 然后这个是构建的CQL语句 首先我们先去启动服务 neo4j console 然后我们再来看一下以前导入的,可以看到导入很简单, 就是上面有CQL 看一下节点的属性

Php“牵手”淘宝商品快递费用数据采集方法,淘宝API接口申请指南

淘宝天猫商品快递费用接口 API 是开放平台提供的一种 API 接口&#xff0c;它可以帮助开发者获取商品的详细信息&#xff0c;包括商品的标题、描述、图片&#xff0c;发货地址&#xff0c;快递费用&#xff0c;区域ID&#xff0c;等信息。在电商平台的开发中&#xff0c;快递费…

计算机视觉:比SAM快50倍的分割一切视觉模型FastSAM

目录 引言 1 FastSAM介绍 1.1 FastSAM诞生 1.2 模型算法 1.3 实验结果 2 FastSAM运行环境构建 2.1 conda环境构建 2.2 运行环境安装 2.3 模型下载 3 FastSAM运行 3.1 命令行运行 3.1.1 Everything mode 3.1.2 Text prompt 3.1.3 Box prompt (xywh) 3.1.4 Points p…

springMVC Unix 文件参数变更漏洞修复

错误信息如下&#xff1a; 解决方案&#xff1a; 原因&#xff1a;未对用户输入正确执行危险字符清理 未检查用户输入中是否包含“…”&#xff08;两个点&#xff09;字符串&#xff0c;比如 url 为 /login?action…/webapps/RTJEKSWTN26635&typerandomCode cookie为Coo…

opencv 进阶16-基于FAST特征和BRIEF描述符的ORB(图像匹配)

在计算机视觉领域&#xff0c;从图像中提取和匹配特征的能力对于对象识别、图像拼接和相机定位等任务至关重要。实现这一目标的一种流行方法是 ORB&#xff08;Oriented FAST and Rotated Brief&#xff09;特征检测器和描述符。ORB 由 Ethan Rublee 等人开发&#xff0c;结合了…

校企合作 | 大势智慧受邀参与北斗共同体建设

8月16日&#xff0c;长江工业职业学院&#xff08;后简称“长江工院”&#xff09;副校长刘文胜&#xff0c;质管处处长黄世涛&#xff0c;测绘信息工程系党总支书记刘飞、系副主任陈志兰、系教师陈文玲一行莅临武汉大势智慧科技有限公司&#xff08;后简称“大势智慧”&#x…

【Git Bash】简明从零教学

目录 Git 的作用官网介绍简明概要 Git 下载链接Git 的初始配置配置用户初始化本地库 Git 状态查询Git 工作机制本地工作机制远端工作机制 Git 的本地管理操作add 将修改添加至暂存区commit 将暂存区提交至本地仓库日志查询版本穿梭 Git 分支查看分支创建与切换分支跨分支修改与…

iPhone开启“轻点唤醒”功能但点击屏幕无反应怎么解决?

iPhone的“轻点唤醒”功能启用时&#xff0c;用户只需手指轻触或点击手机屏幕即可快速唤醒设备&#xff0c;无需按压任何按钮。然而&#xff0c;有些用户在使用“轻点唤醒”功能唤醒屏幕时&#xff0c;遇到该功能失灵&#xff0c;无法正常唤醒屏幕的情况&#xff0c;这是怎么回…

Linux系统安全——NAT(SNAT、DNAT)

目录 NAT SNAT SNAT实际操作 DNAT DNAT实际操作 NAT NAT: network address translation&#xff0c;支持PREROUTING&#xff0c;INPUT&#xff0c;OUTPUT&#xff0c;POSTROUTING四个链 请求报文&#xff1a;修改源/目标IP&#xff0c; 响应报文&#xff1a;修改源/目标…

Yalmip入门教程(5)-约束条件操作的相关函数

博客中所有内容均来源于自己学习过程中积累的经验以及对yalmip官方文档的翻译&#xff1a;https://yalmip.github.io/tutorials/ 这篇博客将详细介绍yalmip工具箱中约束条件操作相关函数的用法。 1.约束条件操作的相关函数 1.1 boundingbox函数 boundingbox函数用于求出一组约…

以软件定义存储实现存力与算力的协同,应对 AI 时代数据挑战

本文根据 XSKY 星辰天合高级副总裁张旭明在“算力与前沿技术创新发展论坛”上的演讲内容整理&#xff0c;略有删节。 算力与前沿技术创新发展论坛以“算力创新跃迁 赋能数字经济”为主题&#xff0c;8 月 17 日在汕头召开&#xff0c;该论坛由工业和信息化部、广东省人民政府主…

SpringBoot 2.7 集成 Netty 4 模拟服务端与客户端通讯入门教程

文章目录 1 摘要2 核心 Maven 依赖3 核心代码3.1 服务端事务处理器 (DemoNettyServerHandler)3.2 服务端连接类(InitNettyServer)3.3 客户端事务处理器(DemoNettyClientHandler)3.4 客户端连接类(DemoNettyClient) 4 测试4.1 测试流程4.2 测试结果4.3 测试结论 5 推荐参考资料6…

通过安全日志读取WFP防火墙放行日志

前言 之前的文档中&#xff0c;描写了如何对WFP防火墙进行操作以及如何在防火墙日志中读取被防火墙拦截网络通讯的日志。这边文档&#xff0c;着重描述如何读取操作系统中所有被放行的网络通信行为。 读取系统中放行的网络通信行为日志&#xff0c;在win10之后的操作系统上&am…

继承(C++)

继承 一、初识继承概念“登场”语法格式 继承方式九种继承方式组合小结&#xff08;对九种组合解释&#xff09; 二、继承的特性赋值转换 一一 切片 / 切割作用域 一一 隐藏 / 重定义 三、派生类的默认成员函数派生类的默认成员函数1. 构造函数2. 拷贝构造3. 赋值运算符重载4. …

【编织时空三:探究顺序表与链表的数据之旅】

本章重点 链表OJ题 1. 删除链表中等于给定值 val 的所有结点。 OJ链接 思路一&#xff1a;删除头结点时另做考虑&#xff08;由于头结点没有前一个结点&#xff09; struct ListNode* removeElements(struct ListNode* head, int val) {assert(head);struct ListNode* cur h…

Go:测试框架GoConvey 简介

快速开始 GoConvey是一个完全兼容官方Go Test的测试框架&#xff0c;一般来说这种第三方库都比官方的功能要强大、更加易于使用、开发效率更高&#xff0c;闲话少说&#xff0c;先看一个example&#xff1a; package utils import (. "github.com/smartystreets/goconvey…