手写一个简易版的tomcat

        Tomcat 是一个广泛使用的开源 Servlet 容器,用于运行 Java Web 应用程序。深入理解 Tomcat 的工作原理对于 Java 开发者来说是非常有价值的。本文将带领大家手动实现一个简易版的 Tomcat,通过这个过程,我们可以更清晰地了解 Tomcat 是如何处理 HTTP 请求和响应的。

        tomcat涉及到的知识点较多,主要有注解、抽象类、反射、IO流等,对基础掌握度要求较高,掌握这些基础后,我们开始手写tomcat

一、创建包

一个基本的 Tomcat 主要完成以下几个核心功能:

  1. 监听端口:等待客户端的 HTTP 请求。
  2. 解析请求:从客户端请求中提取关键信息,如请求方法、请求路径等。
  3. 处理请求:根据请求信息调用相应的处理逻辑。
  4. 返回响应:将处理结果封装成 HTTP 响应返回给客户端。

根据以上功能,我们创建如下图所示的包

--tomcat类的作用是启动整个tomcat容器

--webapp包下存放你自己创建的servlet动态资源

--httpServletRe包下有两个类HttpServletRequest和HttpservletResponse,这两个类可以说是与前端请求直接关联。

        在前端的请求信息中,请求方式和访问路径都要放在HttpServletRequest类中,可以说是相当的重要,我们可以通过socket将前端信息装到该类中,具体写法之后细讲,也就是说我们知道该类有请求方式和访问路径,通过和项目本身的资源对比从而定位到某个实际的静态资源或动态资源

而HttpServletResponse则是向前端发送我们自己写的消息,依赖outputStream来完成,该消息需要遵循响应信息的特定格式,之后的工具类中会给出

--servlet包下则是仿照Java类库中的servlet继承关系,创建了servlet接口,Gservlet抽象类和Httpservlet抽象类

--util包下存放两个工具类,一个类的作用是找到webapp下所有servlet类的类路径,用来进行反射;

一个类的作用是创建响应消息的返回格式,包展开如下:

二、写tomcat逻辑

要想得到前端发送的请求,我们首先需要创建一个ServerSocket对象实例来接收:

ServerSocket serverSocket = new ServerSocket(9090);
while(true){Socket socket = serverSocket.accept();
}

写一个while语句循环接收前端请求,一旦接收到请求,我们就可以创建输入流对象,将前端发送的消息存下来,在这里我们先只获取请求行的信息也就是第一行的信息

BufferedReader bufferedReader = new BufferedReader(new                             InputStreamReader(socket.getInputStream()));
String s = bufferedReader.readLine();

可以看到,浏览器中第一行有请求方式和访问路径,我们是以字符串形式接收的第一行数据,所以可以通过split()方法将请求方式和访问路径分开

String[] s1 = s.split(" ");

tomcat代码展示:(端口的话可以自己选,我写的是9090,tomcat默认端口是8080)

三、写HttpServletRequest的逻辑

得到请求方式和访问路径之后,我们需要将请求方式和访问路径封装到HttpServletRequest类中,在HttpServletRequest类中定义字符串类型的变量url和method,创建getter和setter方法:

public class HttpServletRequest {private String url;private String method;public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}
}

四、补充tomcat逻辑

我们已经定义好HttpServletRequest类,接下来我们将tomcat类中拿到的请求方式和访问路径赋给该类中的变量method和url,首先在tomcat创建HttpServletRequest类的实例对象request,调用request的set方法,入参是inputStream获取到的请求方式和访问路径

代码如下:

写到该阶段我们可以先测试一下,在HttpServletRequest中重写toString方法,在tomcat中调用

到浏览器上输入http://localhost:9090,发现控制台输出:

请求方法和访问路径成功封装到了request中,我们继续下面的逻辑。

五、仿写servlet的继承关系

servlet包下是仿照Java类库中的servlet继承关系,创建了servlet接口,Gservlet抽象类和Httpservlet抽象类,我们现在在这三个类中添加逻辑

Servlet接口

该接口中定义以下方法:init方法、service方法和destroy方法,service方法用来判断是get请求还是post请求,在本接口中只定义,在HttpServlet抽象类中实现。其中有两个入参,HttpServletRequest类型的request和HttpservletResponse的response,之前request对象封装过请求方式,该方法会通过拿到入参的请求方式判断是get请求还是post请求

package com.qcby.servlet;import com.qcby.httpServletRe.HttpServletRequest;
import com.qcby.httpServletRe.HttpServletResponse;public interface Servlet {void init();void service(HttpServletRequest request, HttpServletResponse response) throws Exception;void destroy();
}

Gservlet抽象类

该抽象类继承Servlet接口并实现init方法和destroy方法,这两种方法我们不写实际功能,了解java类库中这两种方法的作用即可

package com.qcby.servlet;public abstract class Gservlet implements Servlet{@Overridepublic void init() {System.out.println("初始化");}@Overridepublic void destroy() {System.out.println("销毁");}
}

HttpServlet抽象类

该类继承Gservlet抽象类,实现了Servlet接口中的service方法,此外HttpServlet类中还增加了两种方法,doGet方法和doPost方法,相信学过servlet的小伙伴们并不陌生,doGet方法和doPost方法作为抽象方法不具体实现,子类也就是我们自己创建的servlet类继承该类时就必须实现该方法,至于service方法是用来区分get方法和post方法的,所以之前的request入参就是为了提供method做if判断

package com.qcby.servlet;import com.qcby.httpServletRe.HttpServletRequest;
import com.qcby.httpServletRe.HttpServletResponse;import java.io.IOException;public abstract class HttpServlet extends Gservlet{@Overridepublic void service(HttpServletRequest request, HttpServletResponse response) throws Exception{if(request.getMethod().equals("GET")){doGet(request,response);}else{doPost(request,response);}}public abstract void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException;public abstract void doPost(HttpServletRequest request, HttpServletResponse response);
}

六、创建注解类WebSocket

在工具包下创建WebSocket注解类,该注解类的作用是定义servlet类的url路径

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface WebSocket {public String url() default "";
}

七、创建servlet容器

tomcat启动时,Servlet 容器需要将客户端请求的 URL 路径映射到具体的 Servlet 上,以便正确处理请求。为了实现这一功能,容器使用 Map 来存储 URL 路径和 Servlet 名称或实例之间的映射关系。

要想获取URL路径和Servlet实例对象,我们首先要通过反射获取到类的类信息,而获取类信息需要获取类的全路径,所以写一个工具类获取webapp包下所有类的全路径,然后初始化map容器

import com.qcby.servlet.HttpServlet;import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;public class FindServletAllPath {public static HashMap<String, HttpServlet> map = new HashMap<>();public static List<String> getClassPaths(String packageName) throws IOException, ClassNotFoundException {List<String> classPaths = new ArrayList<>();// 将包名转换为文件系统路径String path = packageName.replace('.', '/');// 获取类加载器ClassLoader classLoader = Thread.currentThread().getContextClassLoader();// 获取指定路径下的所有资源Enumeration<URL> resources = classLoader.getResources(path);while (resources.hasMoreElements()) {URL resource = resources.nextElement();// 获取资源的文件路径String filePath = resource.getFile();// 递归扫描目录scanDirectory(new File(filePath), packageName, classPaths);}return classPaths;}/*** 递归扫描目录,查找所有的 .class 文件* @param directory 要扫描的目录* @param packageName 当前包名* @param classPaths 存储类路径的列表*/private static void scanDirectory(File directory, String packageName, List<String> classPaths) {if (!directory.exists()) {return;}// 获取目录下的所有文件和文件夹File[] files = directory.listFiles();if (files != null) {for (File file : files) {if (file.isDirectory()) {// 递归扫描子目录scanDirectory(file, packageName + "." + file.getName(), classPaths);} else if (file.getName().endsWith(".class")) {// 获取类名String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6);classPaths.add(className);}}}}static{try {// 指定要扫描的包名String packageName = "com.qcby.webapp";// 获取类路径列表List<String> classPaths = getClassPaths(packageName);// 打印类路径for (String classPath : classPaths) {Class<?> aClass = Class.forName(classPath);WebSocket annotation = aClass.getAnnotation(WebSocket.class);HttpServlet o = (HttpServlet) aClass.newInstance();map.put(annotation.url(),o);}} catch (Exception e) {e.printStackTrace();}}
}

我们把这个过程装入static块中,这样在类加载时就能完成容器的映射

八、创建servlet动态资源

在webapp包下创建一个servlet类,继承HttpServlet抽象类,重写doGet和doPost方法,各自添加一个输出语句,添加WebSocket注解,确定该类的url路径

import com.qcby.httpServletRe.HttpServletRequest;
import com.qcby.httpServletRe.HttpServletResponse;
import com.qcby.servlet.HttpServlet;
import com.qcby.util.WebSocket;import java.io.IOException;@WebSocket(url = "/first")
public class FirstServlet extends HttpServlet {@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {System.out.println("first的doGet方法被调用");}@Overridepublic void doPost(HttpServletRequest request, HttpServletResponse response) {System.out.println("first的doPost方法被调用");}
}

九、补充tomcat逻辑

前端发送的请求方式和url路径已经封装到request对象当中,map中k值存放着请求路径,通过map映射可以找到对应的servlet实例对象,此时servlet的引用类型是父类类型,我们可以直接调用父类的方法service,有两个参数request和response,创建一个response对象放入对应参数位置

if(FindServletAllPath.map.containsKey(request.getUrl())){HttpServlet servlet = FindServletAllPath.map.get(request.getUrl());servlet.service(request,response);
}

进入service方法后,会通过获取request的请求方式判断是执行doget方法还是doPost方法,因为方法被子类重写,所以最终会调用到实际servlet的doget方法或者doPost方法

十、写HttpServletResponse逻辑

我们已经完成了请求的接受和处理,接下来需要将信息返回给前端

创建BufferOutputStream包装流将socket.outputStream包装后进行发送,需要注意的是,发送回前端的数据需要遵循特定的响应格式,我们再util包下创建一个工具类将数据封装到固定格式中

package com.qcby;public class ResponseUtil {public  static  final String responseHeader200 = "HTTP/1.1 200 \r\n"+"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n";public static String getResponseHeader404(){return "HTTP/1.1 404 \r\n"+"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + "404";}public static String getResponseHeader200(String context){return "HTTP/1.1 200 \r\n"+"Content-Type:text/html; charset=utf-8 \r\n"+"\r\n" + context;}
}

之后通过bufferoutputStream的write方法将数据返回给前端,注意发送后需要进行刷新

import com.qcby.ResponseUtil;
import java.io.BufferedOutputStream;
import java.io.IOException;import java.net.Socket;
import java.nio.charset.StandardCharsets;public class HttpServletResponse {Socket socket;public HttpServletResponse(Socket socket){this.socket = socket;}public void write(String s) throws IOException {BufferedOutputStream outputStream = new BufferedOutputStream(socket.getOutputStream());s = ResponseUtil.getResponseHeader200(s);outputStream.write(s.getBytes(StandardCharsets.UTF_8));outputStream.flush();outputStream.close();}
}

tomcat的整体流程如上,现在测试一下接收前端数据以及向前端返回数据信息:

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

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

相关文章

VSTO(C#)Excel开发8:打包发布安装卸载

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

如何逐步迭代衍生出一个网络安全产品

逐步迭代衍生出一个网络安全产品需要结合市场需求、技术趋势和用户反馈&#xff0c;通过系统化的开发和优化过程来实现。以下是逐步迭代的详细步骤&#xff1a; 1. 确定市场需求和产品定位 市场调研&#xff1a;分析当前网络安全市场的痛点和趋势&#xff0c;如云安全、零信任、…

uni-app打包h5并部署到nginx,路由模式history

uni-app打包有些坑&#xff0c;当时运行的基础路径填写了./&#xff0c;导致在二级页面刷新之后&#xff0c;页面直接空白。就只能换一个路径了&#xff0c;nginx也要跟着改&#xff0c;下面是具体步骤。 manifest.json配置web 运行路径写/h5/&#xff0c;或者写你们网站的目…

Ceph(1):分布式存储技术简介

1 分布式存储技术简介 1.1 分布式存储系统的特性 &#xff08;1&#xff09;可扩展 分布式存储系统可以扩展到几百台甚至几千台的集群规模&#xff0c;而且随着集群规模的增长&#xff0c;系统整体性能表现为线性增长。分布式存储的水平扩展有以下几个特性&#xff1a; 节点…

Linux驱动开发实战(五):Qt应用程序点RGB灯(保姆级快速入门!)

Linux驱动开发实战&#xff08;五&#xff09;&#xff1a;Qt应用程序点RGB灯&#xff08;保姆级快速入门&#xff01;&#xff09; 文章目录 Linux驱动开发实战&#xff08;五&#xff09;&#xff1a;Qt应用程序点RGB灯&#xff08;保姆级快速入门&#xff01;&#xff09;前…

Docker安装Kafka(内含zookeeper)

因为kafka是基于zookeeper做的&#xff0c;所以必须要有zookeeper 一、Zookeeper 1.拉取镜像 docker pull zookeeper:3.7.02.运行 docker run --restartalways \--log-driver json-file \--log-opt max-size100m \--log-opt max-file2 \--name zookeeper -p 2181:2181 \-v…

芯谷D8563TS实时时钟/日历芯片详解可替代PCF8563

概述 芯谷D8563TS是一款低功耗CMOS实时时钟/日历芯片&#xff0c;广泛应用于移动电话、便携式仪器、传真机和电池供电产品等领域。该芯片通过两线双向IC总线进行数据传输&#xff0c;最大总线速度为400 kbits/s。D8563TS内置了自动递增的字地址寄存器&#xff0c;支持多种功能…

【一次成功】Win10本地化单机部署k8s v1.31.2版本及可视化看板

【一次成功】Win10本地化单机部署k8s v1.31.2版本及可视化看板 零、安装清单一、安装Docker Desktop软件1.1 安装前<启用或关闭Windows功能> 中的描红的三项1.2 查看软件版本1.3 配置Docker镜像 二、更新装Docker Desktop三、安装 k8s3.1 点击启动安装3.2 查看状态3.3 查…

MoonSharp 文档五

目录 13.Coroutines&#xff08;协程&#xff09; Lua中的协程 从CLR代码中的协程 从CLR代码中的协程作为CLR迭代器 注意事项 抢占式协程 14.Hardwire descriptors&#xff08;硬编码描述符&#xff09; 为什么需要“硬编码” 什么是“硬编码” 如何进行硬编码 硬编…

【初级篇】如何使用DeepSeek和Dify构建高效的企业级智能客服系统

在当今数字化时代,企业面临着日益增长的客户服务需求。使用Dify创建智能客服不仅能够提升客户体验,还能显著提高企业的运营效率。关于DIfy的安装部署,大家可以参考之前的文章: 【入门级篇】Dify安装+DeepSeek模型配置保姆级教程_mindie dify deepseek-CSDN博客 AI智能客服…

【网络编程】HTTP网络编程

13.1 HTTP 简介 HTTP(Hyper Text Transfer Protocol,超文本传输协议)是用于从万维网(WWW:World Wide Web) 服务器(简称Web 服务器)传输超文本到本地浏览器的传送协议&#xff0c;基于TCP/IP 通信协 议来传递数据 (HTML 文件、图片文件、查询结果等)。 13.2 HTTP 的工作原理 …

用Scrum敏捷的视角看《哪吒2》的创作

去年我们公司邀请Scrum中文网的老师培训了敏捷开发课程&#xff0c;让我对敏捷有了更深入的理解。前阵子我参加了scrum中文网的一个直播&#xff0c;老师分享了敏捷在个人领域或生活其他领域的应用&#xff0c;很有意思。因为我学习敏捷&#xff0c;除了应用到本身软件研发的工…

Docker+Flask 实战:打造高并发微服务架构

DockerFlask 实战&#xff1a;打造高并发微服务架构 今天我们要深入探讨一个非常热门且实用的主题&#xff1a;基于 Docker 部署 Python Flask 应用。Docker 作为当下最流行的容器化技术&#xff0c;已经广泛应用于各种开发和部署场景&#xff0c;尤其是在微服务架构中。而 Fl…

Linux find 命令完全指南

find 是 Linux 系统最强大的文件搜索工具&#xff0c;支持 嵌套遍历、条件筛选、执行动作。以下通过场景分类解析核心用法&#xff0c;涵盖高效搜索、文件管理及高级技巧&#xff1a; 一、基础搜索模式 1. 按文件名搜索&#xff08;精确/模糊匹配&#xff09; <BASH> f…

【量化策略】趋势跟踪策略

【量化策略】趋势跟踪策略 &#x1f680;量化软件开通 &#x1f680;量化实战教程 技术背景与应用场景 在金融市场中&#xff0c;趋势跟踪策略是一种基于市场趋势进行交易的量化投资方法。该策略的核心思想是“顺势而为”&#xff0c;即认为市场价格会沿着一定的方向持续移…

AI自动化、资本短视、三输与破局

当前AI应用中的一个深层矛盾&#xff1a;工程师使用AI将很专业的任务变成小白可以操作的工作&#xff0c;然后资本方给小白很少的钱把工程师裁掉了&#xff0c;然而小白不懂底层&#xff0c;出问题几乎无法修复。由此&#xff0c;技术普及与专业能力之间的断层引发了"三输…

Python数据分析之数据可视化

Python 数据分析重点知识点 本系列不同其他的知识点讲解&#xff0c;力求通过例子让新同学学习用法&#xff0c;帮助老同学快速回忆知识点 可视化系列&#xff1a; Python基础数据分析工具数据处理与分析数据可视化机器学习基础 四、数据可视化 图表类型与选择 根据数据特…

简述计算机网络中的七层模型和四层模型

在计算机网络中&#xff0c;网络协议栈的设计通常采用分层结构来处理不同的通信任务。常见的分层结构有OSI七层模型和TCP/IP四层模型。虽然它们的层次数量不同&#xff0c;但本质上都在解决如何有效地进行计算机间通信。本文将分别介绍这两种结构的功能和各层的协议。 一、OSI七…

2025高频面试算法总结篇【持续更新中】

文章目录 递归&回溯131. 分割回文串面试题 08.12. 八皇后 动态规划72编辑距离5. 最长回文子串279. 完全平方数300. 最长递增子序列 递归&回溯 131. 分割回文串 回溯思路&#xff1a; 临界条件&#xff1a; if (start s.length) > 保存 循环遍历这个字串 for (int…

【大模型学习】第二十二章 什么是对抗生成网络

目录 一、背景介绍 二、生活化例子说明什么是对抗生成网络 三、技术细节详解 &#xff08;一&#xff09;基本概念 &#xff08;二&#xff09;训练机制 &#xff08;三&#xff09;损失函数 一、背景介绍 对抗生成网络&#xff08;Generative Adversarial Networks, GANs…