手寫一個Tomcat

Tomcat 是一個廣泛使用的開源 Java Servlet 容器,用于運行 Java Web 應用程序。雖然 Tomcat 本身功能強大且復雜,但通過手寫一個簡易版的 Tomcat,我們可以更好地理解其核心工作原理。本文將帶你一步步實現一個簡易版的 Tomcat,并深入探討其核心組件和運行機制。

一、項目概述

Tomcat是一個簡易的Java Web服務器,它能夠處理HTTP請求并調用相應的Servlet進行處理。項目的核心功能包括:

  • 監聽HTTP請求并解析請求內容。

  • 根據請求路徑調用相應的Servlet。

  • 支持GET和POST請求方法。

  • 使用注解配置Servlet的URL映射。

  • 通過反射機制動態加載Servlet類。

二、項目結構

首先,我們來看一下項目的整體結構:

項目的主要類及其功能如下:

  • ResponseUtil:用于生成HTTP響應頭。

  • SearchClassUtil:掃描指定包下的類文件,獲取類的全限定名。

  • WebServlet:自定義注解,用于標記Servlet并指定URL映射。

  • LoginServlet?和?ShowServlet:具體的Servlet實現類,處理不同的HTTP請求。

  • HttpServletRequest?和?HttpServletResponse:模擬HTTP請求和響應對象。

  • GenericServlet?和?HttpServlet:抽象類,提供Servlet的基本實現。

  • Servlet:Servlet接口,定義了Servlet的生命周期方法。

  • MyTomcat:主類,負責啟動服務器并處理HTTP請求。

  • ServletConfigMapping:維護URL與Servlet的映射關系。

三、核心組件解析

?1、 ResponseUtil 類

ResponseUtil?類用于生成HTTP響應頭。它提供了兩個靜態方法:

  • getResponseHeader200(String context):生成狀態碼為200的HTTP響應頭,并將響應內容附加到響應頭后。

  • getResponseHeader404():生成狀態碼為404的HTTP響應頭。

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;}
}

?2、SearchClassUtil 類

?SearchClassUtil?類用于掃描指定包下的類文件,并獲取這些類的全限定名。它通過遞歸遍歷目錄結構,找到所有以.class結尾的文件,并將其路徑轉換為類的全限定名。

public class SearchClassUtil {public static List<String> classPaths = new ArrayList<String>();public static List<String> searchClass() {String basePack = "com.qcby.webapps.myweb";String classPath = SearchClassUtil.class.getResource("/").getPath();basePack = basePack.replace(".", File.separator);String searchPath = classPath + basePack;doPath(new File(searchPath), classPath);return classPaths;}private static void doPath(File file, String classpath) {if (file.isDirectory()) {File[] files = file.listFiles();for (File f1 : files) {doPath(f1, classpath);}} else {if (file.getName().endsWith(".class")) {String path = file.getPath().replace(classpath.replace("/", "\\").replaceFirst("\\\\", ""), "").replace("\\", ".").replace(".class", "");classPaths.add(path);}}}
}

?3.?WebServlet 注解

WebServlet?是一個自定義注解,用于標記Servlet類并指定URL映射。它包含一個urlMapping屬性,用于指定Servlet處理的URL路徑。

package com.qcby.util;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface WebServlet {String urlMapping() default "";
}

? 4、LoginServlet 和 ShowServlet 類

?LoginServlet?和?ShowServlet?是兩個具體的Servlet實現類,分別處理/login/show路徑的請求。它們繼承自HttpServlet,并重寫了doGet方法以處理GET請求。

@WebServlet(urlMapping = "/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {System.out.println("我是login的doGet方法");response.writeServlet(ResponseUtil.getResponseHeader200("hello"));}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {}
}@WebServlet(urlMapping = "/show")
public class ShowServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {System.out.println("我是show");response.writeServlet(ResponseUtil.getResponseHeader200("show"));}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) {}
}

5、HttpServletRequest 和 HttpServletResponse

HttpServletRequest?和?HttpServletResponse?類分別模擬了HTTP請求和響應對象。HttpServletRequest?包含請求路徑和請求方法,HttpServletResponse?包含輸出流,用于向客戶端發送響應。

package com.qcby.webapps.servlet.req;public class HttpServletRequest {private String path;private String method;public String getPath() {return path;}public void setPath(String path) {this.path = path;}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}
}

HttpServletRequest?類封裝了 HTTP 請求的路徑和方法(GET、POST 等)。

package com.qcby.webapps.servlet.req;import java.io.IOException;
import java.io.OutputStream;public class HttpServletResponse {private OutputStream outputStream;public HttpServletResponse(OutputStream outputStream) {this.outputStream = outputStream;}public void writeServlet(String context) throws IOException {outputStream.write(context.getBytes());}
}

HttpServletResponse?類封裝了 HTTP 響應,提供了向客戶端輸出數據的方法。

6.?GenericServlet 和?HttpServlet 類

GenericServlet?是一個抽象類,提供了Servlet的基本實現,包括initdestroy方法。HttpServlet?繼承自GenericServlet,并實現了service方法,根據請求方法調用相應的doGetdoPost方法。

public abstract class GenericServlet implements Servlet {public void init() {System.out.println("------初始化servlet------");}public void destroy() {System.out.println("------實現servlet對象的銷毀------");}
}public abstract class HttpServlet extends GenericServlet {public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {if (request.getMethod().equals("GET")) {doGet(request, response);} else {doPost(request, response);}}protected abstract void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException;protected abstract void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException;
}

7.?Servlet 接口

Servlet?接口定義了Servlet的生命周期方法,包括initservicedestroy

package com.qcby.webapps.servlet;import com.qcby.webapps.servlet.req.HttpServletRequest;
import com.qcby.webapps.servlet.req.HttpServletResponse;import java.io.IOException;public interface Servlet {void init(); // Servlet 初始化void service(HttpServletRequest request, HttpServletResponse response) throws IOException; // 處理請求void destroy(); // 銷毀
}

?8.?MyTomcat 類

MyTomcat?類是項目的核心,負責啟動服務器并處理HTTP請求。它通過ServerSocket監聽指定端口,接收客戶端請求,解析請求內容,并根據請求路徑調用相應的Servlet。

package com.qcby;import com.qcby.webapps.servlet.HttpServlet;
import com.qcby.webapps.servlet.req.HttpServletRequest;
import com.qcby.webapps.servlet.req.HttpServletResponse;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;import static com.qcby.ServletConfigMapping.servletMap;public class MyTomcat {static HttpServletRequest request = new HttpServletRequest();public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(8484);while (true) {Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream();HttpServletResponse response = new HttpServletResponse(outputStream);int count = 0;while (count == 0) {count = inputStream.available();}byte[] bytes = new byte[count];inputStream.read(bytes);String context = new String(bytes);System.out.println(context);if (context.equals("")) {System.out.println("你輸入了一個空請求");} else {String firstLine = context.split("\\n")[0];request.setPath(firstLine.split("\\s")[1]);request.setMethod(firstLine.split("\\s")[0]);}if (servletMap.containsKey(request.getPath())) {System.out.println("存在于HashMap中");HttpServlet servlet = servletMap.get(request.getPath());servlet.service(request, response);} else {System.out.println("不存在于HashMap中");}}}
}

9.?ServletConfigMapping 類

ServletConfigMapping?類維護了URL與Servlet的映射關系。它通過SearchClassUtil掃描指定包下的類,利用反射機制獲取帶有@WebServlet注解的類,并將其實例化后存入servletMap中。

package com.qcby;import com.qcby.util.SearchClassUtil;
import com.qcby.util.WebServlet;
import com.qcby.webapps.servlet.HttpServlet;import java.util.HashMap;
import java.util.List;
import java.util.Map;public class ServletConfigMapping {public static Map<String, HttpServlet> servletMap = new HashMap<>();static {List<String> classNames = SearchClassUtil.searchClass();for (String path : classNames) {try {Class<?> clazz = Class.forName(path);WebServlet webServlet = clazz.getDeclaredAnnotation(WebServlet.class);HttpServlet servlet = (HttpServlet) clazz.newInstance();servletMap.put(webServlet.urlMapping(), servlet);System.out.println(servletMap);} catch (Exception e) {e.printStackTrace();}}}
}

四、運行流程

  1. 啟動 TomcatMyTomcat?類的?main?方法啟動,監聽 8484 端口。

  2. 接收請求:當有客戶端請求到來時,MyTomcat?解析請求的路徑和方法。

  3. 分發請求:根據請求路徑從?ServletConfigMapping.servletMap?中獲取對應的 Servlet 實例,并調用其?service?方法。

  4. 處理請求:Servlet 根據請求方法調用?doGet?或?doPost?方法,生成響應并返回給客戶端。

通過手寫一個簡易版的 Tomcat,我們深入理解了 Servlet 容器的工作原理。雖然這個簡易版 Tomcat 功能有限,但它涵蓋了 Servlet 容器的核心組件和運行機制。希望本文能幫助你更好地理解 Tomcat 和 Servlet 技術,并為后續深入學習打下堅實的基礎。?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71912.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71912.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71912.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

在 UniApp 開發的網站中使圖片能夠緩存,不一直刷新

在 UniApp 開發的網站中&#xff0c;要使圖片能夠緩存&#xff0c;不一直刷新&#xff0c;可以考慮以下幾種方法&#xff1a; 1. 使用適當的 HTTP 緩存頭 確保你的服務器在響應圖片時&#xff0c;返回合適的緩存控制 HTTP 頭。以下是一些常用的 HTTP 頭來控制緩存&#xff1a…

Makefile——make工具編譯STM32工程

一、Makefile相關指令 1.1、變量 符號含義替換追加:恒等于 1.2、隱含規則 符號含義%.o任意的.o文件*.o所有的.o文件 1.3、通配符 符號含義$^所有依賴文件$所有目標文件$<所有依賴文件的第一個文件 1.4、編譯器指令常用參數功能說明 符號含義舉例-E預處理&#xff0c;…

深入理解Linux文件系統權限:從基礎到高級應用全解析

1. 什么是文件系統權限&#xff1f;它是如何工作的&#xff1f; 文件權限的本質 想象你的電腦是一個大房子&#xff0c;每個文件和目錄都是房間里的物品。文件系統權限就像是一把鑰匙&#xff0c;決定誰能進房間、能看什么、能修改什么。 權限三要素&#xff1a; 讀&#xff…

C語言:6.22練習題數組解答

#include <stdio.h> #include <string.h> // 用于 strlen() int main() {char a[100];int j 0;// 從用戶輸入讀取字符串printf("請輸入一個字符串: ");fgets(a, sizeof(a), stdin);// 遍歷字符串中的每個字符for (int i 0; i < strlen(a); i) {if (…

一、docker的安裝

一、docker桌面 二、docker的配置文件 1、docker配置文件位置/etc/docker/daemon.json 使用json格式&#xff0c;graphdata-root {"graph":"/deploy/docker","registry-mirrors": ["https://8auvmfwy.mirror.aliyuncs.com"],"…

Matlab 多項式擬合點法線(二維)

文章目錄 一、簡介二、實現代碼三、實現效果一、簡介 這個思路其實很簡單,假設我們有一組曲線點,我們可以對其擬合曲線并計算其導數來獲取每個點的法向量,當然這一思路也可以擴展至三維。具體過程如下所示: 二、實現代碼 %% *********

DeepSeek-R1 論文閱讀總結

1. QA問答&#xff08;我的筆記&#xff09; Q1: DeepSeek如何處理可讀性問題&#xff1f; 通過構建冷啟動數據&#xff08;數千條長CoT數據&#xff09;微調基礎模型&#xff0c;結合多階段訓練流程&#xff08;RL訓練、拒絕采樣生成SFT數據&#xff09;&#xff0c;并優化輸…

Manus AI:多語言手寫識別的技術革命與未來圖景

摘要&#xff1a;在全球化浪潮下&#xff0c;跨語言溝通的需求日益迫切&#xff0c;但手寫文字的多樣性卻成為技術突破的難點。Manus AI憑借其多語言手寫識別技術&#xff0c;將潦草筆跡轉化為精準數字文本&#xff0c;覆蓋全球超百種語言。本文從技術原理、應用場景、行業價值…

Flutter——最詳細原生交互(MethodChannel、EventChannel、BasicMessageChannel)使用教程

MethodChannel&#xff08;方法通道&#xff09; 用途&#xff1a;實現 雙向通信&#xff0c;用于調用原生平臺提供的 API 并獲取返回結果。 場景&#xff1a;適合一次性操作&#xff0c;如調用相機、獲取設備信息等。 使用步驟&#xff1a; Flutter 端&#xff1a;通過 Meth…

Python控制語句-循環語句-while

1.若k為整形,下述while循環執行的次數為()。 k=1000 while k>1: print(k) k=k/2 A、9 B、10 C、11 D、100 答案:A。k=k/2意味著每循環一次,k的值就會變為原來的一半,直到k的值不大于1。 2.下面的代碼,哪些會輸出1,2,3三個數字( )。 A、 for i in range(3): print(i) …

十二天-雙指針技術:鏈表問題的高效解法

一、雙指針技術分類 1. 同速雙指針&#xff08;同向移動&#xff09; 特點&#xff1a;兩個指針以相同速度移動適用場景&#xff1a; 鏈表逆序查找倒數第 k 個元素刪除倒數第 n 個節點 2. 快慢雙指針&#xff08;異速移動&#xff09; 特點&#xff1a;一個指針每次移動 1 步…

【vllm】Qwen2.5-VL-72B-AWQ 部署記錄

版本&#xff1a;0.7.2 注意事項&#xff1a; export LD_LIBRARY_PATH/home/xxxxx/anaconda3/envs/xxxxx/lib/python3.10/site-packages/nvidia/nvjitlink/lib:$LD_LIBRARY_PATH # 如果報錯可能需要Also pip install --force-reinstall githttps://github.com/huggingface/tra…

深度學習與大模型-張量

大家好&#xff01;今天我們來聊聊張量&#xff08;Tensor&#xff09;。別被這個詞嚇到&#xff0c;其實它沒那么復雜。 什么是張量&#xff1f; 簡單來說&#xff0c;張量就是一個多維數組。你可以把它看作是一個裝數據的容器&#xff0c;數據的維度可以是一維、二維&#…

【前端面試題】Vu3常見的面試題

1.Vue3與 Vue2的核心區別有哪些&#xff1f; ? 響應式系統 ?&#xff1a; ? Vue2&#xff1a;通過Object.defineProperty 實現響應式。這種方式在處理對象屬性的添加和刪除時存在局限性&#xff0c;且無法直接監控數組的變化 ?;?Vue3&#xff1a;采用Proxy 實現響應式&…

Android 粘包與丟包處理工具類:支持多種粘包策略的 Helper 實現

在Android開發中&#xff0c;處理TCP/UDP通信時&#xff0c;粘包和丟包是常見的問題。粘包是指多個數據包被接收方一次性接收&#xff0c;導致數據包之間的界限不清晰&#xff1b;丟包則是指數據包在傳輸過程中丟失。為了處理這些問題&#xff0c;我們可以編寫一個幫助類 Packe…

【C++11】移動語義

回顧 const int c的c是可以被取地址的&#xff0c;盡管是常量。所以以是否為常量來判斷是否為右值是錯誤的。 左值與右值正確的區分方法是是否能夠被取地址。&#xff08;能被取地址也就代表著是一個持久狀態&#xff0c;即有持久的存儲空間的值&#xff09; 常見的左值有我們…

LangChain教程 - Agent -之 ZERO_SHOT_REACT_DESCRIPTION

在構建智能 AI 助手時&#xff0c;我們希望模型能夠智能地調用工具&#xff0c;以便提供準確的信息。LangChain 提供了 AgentType.ZERO_SHOT_REACT_DESCRIPTION&#xff0c;它結合了 ReAct&#xff08;Reasoning Acting&#xff09;策略&#xff0c;使得 LLM 可以基于工具的描…

移動Android和IOS自動化中常見問題

APP測試邏輯 在app編寫自動化測試用例時&#xff0c;通常會出現只是簡單的點點點過程&#xff0c;然而卻忽略了在實際的自動化實現過程中&#xff0c;軟件是對app元素的判斷來執行測試腳本。所以會出現在后期已經寫好自動化腳本之后還會對測試用例的更新。 App在測試時&#…

python高效試用17---兩個字符串組成一個新的字符串和兩個字符串組成元組作為key哪個更高效

在 Python 中&#xff0c;使用字符串連接 (str1 str2) 作為 key 和使用元組 ((str1, str2)) 作為 key 的效率差異&#xff0c;主要受以下因素影響&#xff1a; 哈希計算速度&#xff1a; 字符串連接 (str1 str2)&#xff1a;會創建一個新的字符串對象&#xff0c;并計算哈希…

深入淺出Java try-with-resources:告別資源泄漏的煩惱

一、為什么需要try-with-resources&#xff1f; 在Java開發中&#xff0c;我們經常需要處理各種資源&#xff1a;文件流、數據庫連接、網絡套接字等。這些資源都有一個共同特點——必須在使用后正確關閉。傳統的資源管理方式存在三大痛點&#xff1a; 代碼臃腫&#xff1a;每…