从零手写实现 tomcat-05-servlet 处理支持

创作缘由

平时使用 tomcat 等 web 服务器不可谓不多,但是一直一知半解。

于是想着自己实现一个简单版本,学习一下 tomcat 的精髓。

系列教程

从零手写实现 apache Tomcat-01-入门介绍

从零手写实现 apache Tomcat-02-web.xml 入门详细介绍

从零手写实现 tomcat-03-基本的 socket 实现

从零手写实现 tomcat-04-请求和响应的抽象

从零手写实现 tomcat-05-servlet 处理支持

从零手写实现 tomcat-06-servlet bio/thread/nio/netty 池化处理

从零手写实现 tomcat-07-war 如何解析处理三方的 war 包?

从零手写实现 tomcat-08-tomcat 如何与 springboot 集成?

从零手写实现 tomcat-09-servlet 处理类

从零手写实现 tomcat-10-static resource 静态资源文件

从零手写实现 tomcat-11-filter 过滤器

从零手写实现 tomcat-12-listener 监听器

整体思路

模拟实现 servlet 的逻辑处理,而不是局限于上一节的静态文件资源。

整体流程

1)定义 servlet 标准的 接口+实现

2)解析 web.xml 获取对应的 servlet 实例与 url 之间的映射关系。

3)调用请求

1. servlet 实现

api 接口

servlet 接口,我们直接引入 servlet-api 的标准。

<dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>${javax.servlet.version}</version>
</dependency>

抽象 servlet 定义

package com.github.houbb.minicat.support.servlet;import com.github.houbb.minicat.constant.HttpMethodType;import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;public abstract class AbstractMiniCatHttpServlet extends HttpServlet {public abstract void doGet(HttpServletRequest request, HttpServletResponse response);public abstract void doPost(HttpServletRequest request, HttpServletResponse response);@Overridepublic void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {HttpServletRequest httpServletRequest = (HttpServletRequest) req;HttpServletResponse httpServletResponse = (HttpServletResponse) res;if(HttpMethodType.GET.getCode().equalsIgnoreCase(httpServletRequest.getMethod())) {this.doGet(httpServletRequest, httpServletResponse);return;}this.doPost(httpServletRequest, httpServletResponse);}}

根据请求方式分别处理

简单的实现例子

下面是一个简单的处理实现:

  • MyMiniCatHttpServlet.java
package com.github.houbb.minicat.support.servlet;import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.minicat.dto.MiniCatResponse;
import com.github.houbb.minicat.util.InnerHttpUtil;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** 仅用于测试** @since 0.3.0*/
public class MyMiniCatHttpServlet extends AbstractMiniCatHttpServlet {private static final Log logger = LogFactory.getLog(MyMiniCatHttpServlet.class);@Overridepublic void doGet(HttpServletRequest request, HttpServletResponse response) {String content = "MyMiniCatServlet-get";MiniCatResponse miniCatResponse = (MiniCatResponse) response;miniCatResponse.write(InnerHttpUtil.http200Resp(content));}@Overridepublic void doPost(HttpServletRequest request, HttpServletResponse response) {String content = "MyMiniCatServlet-post";MiniCatResponse miniCatResponse = (MiniCatResponse) response;miniCatResponse.write(InnerHttpUtil.http200Resp(content));}}

2. web.xml 解析

说明

web.xml 需要解析处理。

比如这样的:

<?xml version="1.0" encoding="UTF-8" ?>
<web-app><servlet><servlet-name>my</servlet-name><servlet-class>com.github.houbb.minicat.support.servlet.MyMiniCatHttpServlet</servlet-class></servlet><servlet-mapping><servlet-name>my</servlet-name><url-pattern>/my</url-pattern></servlet-mapping></web-app>

解析方式

接口定义

package com.github.houbb.minicat.support.servlet;import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;/*** servlet 管理** @since 0.3.0*/
public interface IServletManager {/*** 注册 servlet** @param url     url* @param servlet servlet*/void register(String url, HttpServlet servlet);/*** 获取 servlet** @param url url* @return servlet*/HttpServlet getServlet(String url);}

web.xml

web.xml 的解析方式,核心的处理方式:

    //1. 解析 web.xml//2. 读取对应的 servlet mapping//3. 保存对应的 url + servlet 示例到 servletMapprivate void loadFromWebXml() {InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");SAXReader saxReader = new SAXReader();try {Document document = saxReader.read(resourceAsStream);Element rootElement = document.getRootElement();List<Element> selectNodes = rootElement.selectNodes("//servlet");//1, 找到所有的servlet标签,找到servlet-name和servlet-class//2, 根据servlet-name找到<servlet-mapping>中与其匹配的<url-pattern>for (Element element : selectNodes) {/*** 1, 找到所有的servlet标签,找到servlet-name和servlet-class*/Element servletNameElement = (Element) element.selectSingleNode("servlet-name");String servletName = servletNameElement.getStringValue();Element servletClassElement = (Element) element.selectSingleNode("servlet-class");String servletClass = servletClassElement.getStringValue();/*** 2, 根据servlet-name找到<servlet-mapping>中与其匹配的<url-pattern>*///Xpath表达式:从/web-app/servlet-mapping下查询,查询出servlet-name=servletName的元素Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']'");String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();HttpServlet httpServlet = (HttpServlet) Class.forName(servletClass).newInstance();this.register(urlPattern, httpServlet);}} catch (Exception e) {logger.error("[MiniCat] read web.xml failed", e);throw new MiniCatException(e);}}

解析之后的 HttpServlet 全部放在 servletMap 中。

然后在对应的 url 我们选取处理即可。

3. url 的处理

说明

根据 url 找到对应的 servlet 进行处理。

主要分为 3 大类:

1)url 不存在

2)url 为 html 等静态资源

3) servlet 的处理逻辑

设计

我们把这部分抽象为接口:

public void dispatch(RequestDispatcherContext context) {final MiniCatRequest request = context.getRequest();final MiniCatResponse response = context.getResponse();final IServletManager servletManager = context.getServletManager();// 判断文件是否存在String requestUrl = request.getUrl();if (StringUtil.isEmpty(requestUrl)) {emptyRequestDispatcher.dispatch(context);} else {// 静态资源if (requestUrl.endsWith(".html")) {staticHtmlRequestDispatcher.dispatch(context);} else {// servlet servletRequestDispatcher.dispatch(context);}}
}

servlet 例子

如果是 servlet 的话,核心处理逻辑如下:

// 直接和 servlet 映射
final String requestUrl = request.getUrl();
HttpServlet httpServlet = servletManager.getServlet(requestUrl);
if(httpServlet == null) {logger.warn("[MiniCat] requestUrl={} mapping not found", requestUrl);response.write(InnerHttpUtil.http404Resp());
} else {// 正常的逻辑处理try {httpServlet.service(request, response);} catch (Exception e) {logger.error("[MiniCat] http servlet handle meet ex", e);throw new MiniCatException(e);}
}

4. 读取 request 的问题修复

问题

发现 request 读取输入流的时候,有时候读取为空,但是页面明明是正常请求的。

原始代码

private void readFromStream() {try {//从输入流中获取请求信息int count = inputStream.available();byte[] bytes = new byte[count];int readResult = inputStream.read(bytes);String inputsStr = new String(bytes);logger.info("[MiniCat] readCount={}, input stream {}", readResult, inputsStr);if(readResult <= 0) {logger.info("[MiniCat] readCount is empty, ignore handle.");return;}//获取第一行数据String firstLineStr = inputsStr.split("\\n")[0];  //GET / HTTP/1.1String[] strings = firstLineStr.split(" ");this.method = strings[0];this.url = strings[1];logger.info("[MiniCat] method={}, url={}", method, url);} catch (IOException e) {logger.error("[MiniCat] readFromStream meet ex", e);throw new RuntimeException(e);}
}

问题分析

问题其实出在 inputStream.available() 中,网络流(如 Socket 流)与文件流不同,网络流的 available() 方法可能返回 0,即使实际上有数据可读。这是因为网络通讯是间断性的,数据可能分多个批次到达。

修正

由于 available() 方法在网络流中可能不准确,您可以尝试不使用此方法来预分配字节数组。

相反,您可以使用一个固定大小的缓冲区,或者使用 read() 方法的循环来动态读取数据。

    /*** 直接根据 available 有时候读取不到数据* @since 0.3.0*/private void readFromStreamByBuffer() {byte[] buffer = new byte[1024]; // 使用固定大小的缓冲区int bytesRead = 0;try {while ((bytesRead = inputStream.read(buffer)) != -1) { // 循环读取数据直到EOFString inputStr = new String(buffer, 0, bytesRead);// 检查是否读取到完整的HTTP请求行if (inputStr.contains("\n")) {// 获取第一行数据String firstLineStr = inputStr.split("\\n")[0];String[] strings = firstLineStr.split(" ");this.method = strings[0];this.url = strings[1];logger.info("[MiniCat] method={}, url={}", method, url);break; // 退出循环,因为我们已经读取到请求行}}if ("".equals(method)) {logger.info("[MiniCat] No HTTP request line found, ignoring.");// 可以选择抛出异常或者返回空请求对象}} catch (IOException e) {logger.error("[MiniCat] readFromStream meet ex", e);throw new RuntimeException(e);}}

开源地址

 /\_/\  
( o.o ) > ^ <

mini-cat 是简易版本的 tomcat 实现。别称【嗅虎】(心有猛虎,轻嗅蔷薇。)

开源地址:https://github.com/houbb/minicat

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

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

相关文章

认识 Pixel 8a:这款 Google AI 手机拥有无与伦比的价值。

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

代码随想录训练营Day27:贪心算法05区间问题

1.435无重叠区间 思路&#xff1a;可以延续前面的引爆气球的思路&#xff0c;所以本题的贪心策略为&#xff1a;对于相同起始位置的&#xff0c;删去终点位置靠右的&#xff0c;只留下一个最靠左的。对于具有不同的起始位置的&#xff0c;但是存在重复的情况&#xff08;即当前…

html+css-Day1(盒子模型)

一、常用属性 1、字体设置font "line-height" 是 CSS 中的一个属性&#xff0c;用于设置文本行之间的距离&#xff0c;也就是行间距。它影响着段落、行内元素或者任何包含文本的元素的可读性。"line-height" 可以设置为数字、长度单位&#xff08;如 px、e…

数据结构——希尔排序

基本思想&#xff1a; 希尔排序法又称缩小增量法。希尔排序法的基本思想是&#xff1a;先选定一个整数&#xff0c;把待排序文件中所有记录分成个组&#xff0c;所有距离为的记录分在同一组内&#xff0c;并对每一组内的记录进行排序。然后&#xff0c;取&#xff0c;重复上述…

【代码随想录算法训练Day3】LeetCode 203.移除链表元素、LeetCode 707.设计链表、LeetCode 206.反转链表

Day3 链表 链表也是一种很重要的数据结构&#xff0c;链表的优势是空间不必连续&#xff0c;分配比较自由&#xff0c;缺点是不支持随机访问&#xff0c;想要获取链表中间的某个元素&#xff0c;必须要从头遍历。 LeetCode 203.移除链表元素【虚拟头结点】 移除链表中的某个…

【图文教程】PyCharm安装配置PyQt5+QtDesigner+PyUic+PyRcc

这里写目录标题 PyQt5、Qt Designer、PyUic、PyRcc简介&#xff08;1&#xff09;下载安装PyQt5&#xff08;2&#xff09;打开designer.exe所在位置&#xff08;3&#xff09;在PyCharm中配置QtDesigner&#xff08;4&#xff09;验证QtDesigner是否配置成功&#xff08;5&…

通过编写dockerfile部署python项目

docker命令总览 docker通过dockerfile构建镜像常用命令 # 创建镜像&#xff08;进入dockerfile所在的路径&#xff09; docker build -t my_image:1.0 .# 查看镜像 docker images# 创建容器 docker run -dit --restartalways -p 9700:9700 --name my_container my_image:1.0 #…

阿里发布通义千问2.5:一文带你读懂通义千问!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

就业班 第三阶段(zabbix) 2401--5.9 day1 普通集zabbix 5.0部署 nginx部署+agent部署

文章目录 环境一、zabbix 5.0 部署1、安装yum源2、安装相关软件3、数据库安装和配置mariaDB数据库mysql57数据库 安装mysql万能卸载mysql代码&#xff1a;启动mysql并初始化4、数据表导入5、修改配置&#xff0c;启动服务6、配置 web GUI7、浏览器访问注意数据加密的选项不要勾…

分享四种免费获取SSL的方式

SSL证书目前需要部署安装的网站很多&#xff0c;主要还是基于国内目前对证书的需求度在不断的升高&#xff0c;网站多了、服务器多了之后。网络安全问题就成为了大家不得不面对的一个重要的问题了。SSL证书的作用有很多&#xff0c;这里就不一一详述了&#xff0c;本期作品主要…

一次消谐器适用于各种电压等级的电力系统

你知道什么是一次消谐器吗&#xff1f;它是一种保护装置&#xff0c;用于消除电力系统中的谐振过电压&#xff0c;保护设备的安全可靠运行。那么一次消谐器有什么作用呢&#xff1f;接下来我会为你详细介绍一下。 一次消谐器的设计原理是利用其非线性电阻特性&#xff0c;在谐振…

RSAC 2024现场:谷歌展望大模型在网络安全领域的前景

人类距离将网络安全的控制权交给生成式AI还有多远&#xff1f; 前情回顾RSAC2024动态 伪造内容鉴别厂商Reality Defender斩获2024 RSAC创新沙盒冠军 RSAC 2024上值得关注的10款网络安全产品 RSAC 2024创新沙盒十强出炉&#xff0c;谁能夺冠&#xff1f; 安全内参5月8日消息…

table表格 如何加卡片 实现?

实现非常简单 代码 const columnsinsu [{dataIndex: nowdate,key: nowdate,render: (text, record) ><Cardhoverablestyle{{width: 97%,height: 90,}}// cover{<img alt"example" src"https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png&…

浅析安全用电监控系统在工厂的研究与应用论述

摘 要&#xff1a;随着社会时代的发展&#xff0c;人们的安全意识越来越强烈&#xff0c;在人们生活和工作中离不开各种用电设备&#xff0c;用电设备的安全使用是保障人们生命安全的重要内容。工厂因自身厂内工作环境的特殊性&#xff0c;用电设备的种类多且复杂&#xff0c;如…

指针的奥秘(二):指针与数组的联系+字符指针+二级指针+指针数组+《剑指offer》笔试题

指针 一.指针与数组的联系1.数组名的理解2.使用指针访问数组3.一维数组传参的本质 二.字符指针1.字符指针隐藏秘密2.常量字符串3.《剑指offer》笔试题 三.二级指针四.指针数组1.指针数组模拟二维数组 一.指针与数组的联系 1.数组名的理解 也许大部分人认为数组名就是一个名称&…

分页PageHelper、PageInfo

PageHelper是一个基于MyBatis的分页插件&#xff0c;可以帮助开发者快速、方便地实现分页功能。PageHelper支持多种数据库&#xff08;包括MySQL、Oracle、SQL Server、PostgreSQL等&#xff09;&#xff0c;可以通过简单的配置即可使用。 使用PageHelper&#xff0c;只需要在…

TL-WN826N无线网卡连接电脑蓝屏,提示rtl8188gu.sys

TL-WN826N无线网卡插电脑就蓝屏&#xff0c;提示rtl8188gu.sys 处理方法&#xff1a; 设备管理器中卸载其他的2.0无线网卡程序和功能中卸载网卡驱动TPlink官网下载 TL-WN826N V1.0_1.0.0&#xff08;https://www.tp-link.com.cn/product_572.html?vdownload&#xff09;&…

【Osek网络管理测试】[TG4_TC3]LimpHome状态下的睡眠中断

🙋‍♂️ 【Osek网络管理测试】系列💁‍♂️点击跳转 文章目录 1.环境搭建2.测试目的3.测试步骤4.预期结果5.测试结果1.环境搭建 硬件:VN1630 软件:CANoe 2.测试目的 验证DUT在LimpHome状态下的睡眠中断是否正确 分析:在跛脚运行状态下,满足睡眠条件后,进入到NM…

欧盟MDR法规对医疗器械网络安全都有哪些要求?

MDR&#xff0c;欧盟医疗器械法规&#xff08;Medical Device REGULATION (EU) 2017/745&#xff0c;简称“MDR”&#xff09;&#xff0c;当医疗器械办理欧盟CE认证时&#xff0c;需满足新法规 MDR (EU) 2017/745要求。 M DR符合性评估 医械网络安全咨询与相关文件出具&#x…

【Linux】项目自动化构建工具make/makefile

&#x1f389;博主首页&#xff1a; 有趣的中国人 &#x1f389;专栏首页&#xff1a; Linux &#x1f389;其它专栏&#xff1a; C初阶 | C进阶 | 初阶数据结构 小伙伴们大家好&#xff0c;本片文章将会讲解Linux中项目自动化构建工具make/makefile的相关内容。 如果看到最后…