手寫一個簡易版的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.pswp.cn/bicheng/73427.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/73427.shtml
英文地址,請注明出處:http://en.pswp.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…