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

news2024/10/7 6:43:24

源码放在了文章末尾

理论知识

何为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中的字节数据转为char
   for(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(" ");
   //解析 请求方法类型,存到method
   this.method = ss[0];

   //解析 请求地址,存到requestURI
   this.requestURI = ss[1];

   //解析 请求参数,存到parameter的map中
   parseParameter();

   //解析 请求头,存到headers中
   parseHeader();

   //解析 请求cookie:从headers中取cookie
   parseCookie();

   //解析 sessionId:从cookie中取出jsessionid
   jsessionid = 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.1
   String[] 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=a
   int 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 {

   @Override
   public 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.coloradmin.cn/o/916185.html

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

相关文章

Linux内核提权漏洞

Linux内核提权漏洞 漏洞名称&#xff1a;脏牛&#xff08;Dirty COW&#xff09;CVE-2016-5195 漏洞危害&#xff1a;低权限用户利用该漏洞技术可以在全版本 Linux 系统上实现本地提权 影响范围&#xff1a;Linux 内核2.6.22 < 3.9 (x86/x64) 攻击机&#xff1a;Kali Linu…

Vmware 虚拟机挂起恢复后发现无法 Ping 通,无法连接到主机

解决办法 进入对应主机中&#xff0c;切换到 root 账户&#xff0c;重启网络服务。 systemctl stop NetworkManager systemctl restart network在网上还找到了另一种解决方法&#xff1a; 在网卡配置文件中增加参数 NM_CONTROLLED"no"。 在 Centos 7 中修改如下所…

Elasticsearch配置优化

以下的优化基础是安装的 Elasticsearch 版本为 7.17.7&#xff0c;同时jdk版本为 1.8.321 1、jvm参数优化 这里说的jvm参数调优&#xff0c;是指elasticsearch安装目录下的jvm.options配置&#xff0c;如下图所示&#xff1a; 这里调整的内容主要是调整垃圾回收的收集器&#…

打造引人注目的直播体验:直播美颜SDK的集成与优化

随着移动互联网的迅速发展&#xff0c;视频直播已经成为人们交流、娱乐和信息传递的重要方式。在这个多元化的直播市场中&#xff0c;吸引观众的注意力变得尤为重要。其中&#xff0c;美颜技术在增强直播体验方面发挥着关键作用。直播美颜SDK的集成和优化使得主播能够以最佳状态…

Docker安装并配置cAdvisor

Linux下安装Docker请参考&#xff1a;Linux安装Docker 简介 cAdvisor 是 Google 开源的一款用于展示和分析容器运行状态的可视化工具。通过在主机上运行 CAdvisor 用户可以轻松的获取到当前主机上容器的运行统计信息&#xff0c;并以图表的形式向用户展示。 cAdvisor 可以对…

问道管理:煤炭板块发力拉升,陕西黑猫涨停,郑州煤电等走高

煤炭板块23日盘中发力拉升&#xff0c;截至发稿&#xff0c;陕西黑猫涨停&#xff0c;郑州煤电涨近6%&#xff0c;平煤股份、兰花科创、兖矿能源涨近3%&#xff0c;山西焦煤、潞安环能、我国神华等均走高。 关于该板块&#xff0c;国信证券表明&#xff0c;展望下半年&#xff…

IC芯片 trustzone学习

搭建Airplay TA环境需要在IC的TrustZone中进行。TrustZone是一种安全技术&#xff0c;用于隔离安全和非安全环境&#xff0c;并保护敏感文件。在TrustZone中&#xff0c;我们需要编写一个叫做TA&#xff08;Trusted Application&#xff09;的应用程序来控制这些私密文档。 &am…

echart 图表添加数据分析功能,(右上控制选择)

echart 图表添加数据分析功能,可区域选择数据,右上按钮,控制echart行为 chart.on(globalcursortaken, onGlobalcursortaken); //绑定事件chart.off("brushSelected");//解绑事件处理函数chart.on(brushSelected, renderBrushed);getBarDev2(eIndex, eTimeArr, eSerie…

海外网红营销中的创新技术与趋势:AI、AR和VR的应用探索

随着全球数字化时代的不断发展&#xff0c;互联网已经成为连接人们的桥梁&#xff0c;而社交媒体则在其中扮演着举足轻重的角色。在这个全球性的社交媒体网络中&#xff0c;海外网红以其独特的个人魅力和内容创作能力迅速崭露头角。而为了在竞争激烈的市场中脱颖而出&#xff0…

前端console.log打印内容与后端请求返回数据不一致

后端传值num0 前端打印num1 ,如图&#xff0c;console.log后台显示的数据与展开后不一致 造成该问题原因是深拷贝与浅拷贝的问题。 var obj JSON.parse(JSON.stringify(res)) 修改后打印 正常

冠达管理:股票分红的钱会计算到收益吗?为什么分红之后出现亏损?

我们常说炒股的主要收益来源便是除了高抛低吸赚取差价收益之外&#xff0c;还有参加股票分红取得。那么股票分红的钱管帐算到收益吗&#xff1f;为什么分红之后呈现亏本&#xff1f;下面就由冠达管理为大家剖析&#xff1a; 股票分红的钱管帐算到收益吗&#xff1f; 不会。 股…

胖小酱之恰恰是什么

意思是&#xff1a;指所指的事物截然不同&#xff0c;正好相反。 恰恰相反的近义词&#xff1a;事与愿违、适得其反 一、事与愿违 [ sh yǔ yun wi ] 【解释】&#xff1a;事实与愿望相反。指原来打算做的事没能做到。 【出自】&#xff1a;茅盾《子夜》十六&#xff1a;不…

Git拉取分支、基于主分支创建新的开发分支、合并开发分支到主分支、回退上一次的merge操作

系列文章目录 第1章 Git拉取分支、基于主分支创建新的开发分支、合并开发分支到主分支、回退上一次的merge操作 文章目录 系列文章目录一、拉取分支二、如何从master分支创建一个dev分支三、如何将dev分支合并到master分支四、如何回退上一次的merge 一、拉取分支 项目文件夹…

苹果叶病害识别(Python代码,pyTorch框架,深度卷积网络模型,很容易替换为其它模型,带有GUI识别界面)

代码运行要求&#xff1a;Torch>1.13.1即可 1.数据集介绍&#xff1a; Apple Scab类文件夹图片 Black Rot类文件夹图片 Cedar Apple Rust文件夹 healthy文件夹 2.整个项目 data文件夹存放的是未被划分训练集和测试集的原始照片 picture文件夹存放的是经hf.py对data文件夹…

第8章 海量数据搜索实现

mini商城第8章 海量数据搜索实现 一、课题 海量数据搜索 二、回顾 1、理解OpenResty 百万并发站点架构 2、能明白多级缓存架构思路 3、实现Nginx代理缓存 4、能实现缓存一致性 三、目标 1、了解ElasticSearch并会使用核心API 2、基于Canal实现ES和数据库数据同步 3、…

ssm人事管理信息系统源码和文档

ssm人事管理信息系统源码和文档046 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm &#xff08;一&#xff09;课题目的及意义&#xff08;含国内外的研究现状分析&#xff09; &#xff08;1&#xff09;人事…

SAP 如何创建Company-公司

目录 什么是公司 一、创建/定义公司的意义&#xff1f; 二、创建公司的案例分析 1.定义Company-公司 2.详细处理介绍 总结 什么是公司 SAP FICO解决方案中使用了某些术语来定义分类账的输入。其中一个术语是公司&#xff0c;一种SAP FI企业结构&#xff0c;任何SAP会计人…

linux下系统问题排查基本套路

文章目录 总结常用命令原文GC相关网络TIME_WAITCLOSE_WAIT 总结常用命令 top 查找cpu占用高的进程ps 找到对应进程的pidtop -H -p pid 查找cpu利用率较高的线程printf ‘%x\n’ pid 将线程pid转换为16进制得到 nidjstack pid |grep ‘nid’ -C5 –color 在jstack中找到对应堆栈…

无线上网连接及配置

目录 1. 无线上网连接及配置 1.1 无线路由器连接方式 ​编辑 1.2 无线路由器的基本配置 1.配置用户计算机上的IP地址 2.访问无线路由Web管理界面 1.3 WAN 口设置 1.动态 IP 2.静态 IP 1. 无线上网连接及配置 一小型公司共有20名员工。由于公司业务需要访问Internet&…

Jacoco XML 解析

1 XML解析器对比 1. DOM解析器&#xff1a; ○ 优点&#xff1a;易于使用&#xff0c;提供完整的文档树&#xff0c;可以方便地修改和遍历XML文档。 ○ 缺点&#xff1a;对大型文档消耗内存较多&#xff0c;加载整个文档可能会变慢。 ○ 适用场景&#xff1a;适合小型XML文档…