手搓Tomcat

目录

Tomcat是什么?

前置工作准备

构建并启动Tomcat

处理Socket逻辑顺序

获取输入流并读取数据封装到Request

自定义Servlet对象

暂存响应体

按Http协议发送响应数据

部署Tomcat



Tomcat是什么?


Tomcat 是一个 Web 应用服务器(准确说是 Servlet 容器JSP 引擎),是目前 Java Web 开发中最常用的中间件之一。

本质:

  • Tomcat 是由 Apache 基金会开发和维护的开源 Web 服务器。

  • 它实现了 ServletJSP 规范,是 Java EE 规范的一部分。

  • Tomcat 本身不是完整的 Java EE 应用服务器(如 JBoss、GlassFish),但足以支撑大部分 Web 应用。

核心功能:

  • Socket 监听:在指定端口(默认 8080)监听来自浏览器的 HTTP 请求。

  • 请求解析:解析 HTTP 请求报文,把它封装成 HttpServletRequest 对象。

  • Servlet 管理:根据 URL 匹配到对应的 Servlet,调用其 service() 方法。

  • 响应返回:将 HttpServletResponse 的内容拼装成完整的 HTTP 响应报文,并写回浏览器。

  • 静态资源处理:直接返回 HTML、CSS、JS、图片等静态文件。

假如浏览器访问 http:// localhost:8080/test,随后根据 URL 发送 HTTP 报文给服务器:

GET /test HTTP/1.1
Host: localhost:8080

随后Tomcat通过 ServerSocket 在端口 8080 监听,收到请求后解析出请求方法(GET)、路径(/test)、协议版本(HTTP/1.1)、头部信息等。

之后Tomcat 根据配置找到 /test 对应的 Servlet,然后调用 Servlet 的 service() 方法,service 再根据请求方法选择doGet()或doPost()等方法。在 Servlet 中执行业务逻辑,例如查询数据库、处理数据等。

最后Servlet 使用  HttpServletResponse 设置响应头、状态码、响应体内容。Tomcat 将这些内容封装成完整的 HTTP 响应报文:

HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 12hello Eleven

浏览器收到响应后渲染页面。

所以实际上 Tomcat 是前端和后端之间的“桥梁”,它把低层的 TCP/HTTP 通信细节封装起来,让开发者只需要处理业务逻辑。

而我们想手写一个Tomcat主要是去尝试使用Socket处理Http协议,之后再模拟 Servlet 调用流程。


前置工作准备


首先我们引入 javax.servlet-api 依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.5.5</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>cn.tomcat.com</groupId><artifactId>tomcat-eleven</artifactId><version>0.0.1-SNAPSHOT</version><name>tomcat-eleven</name><description>tomcat-eleven</description><properties><java.version>17</java.version></properties><dependencies><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>4.0.1</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>3.8.2</version><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

之后我们先构造一个Tomcat启动类,然后创建一个start()方法用来启动Tomcat:

package cn.tomcat.com;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TomcatElevenApplication {/*** 启动*/public void start(){}public static void main(String[] args) {TomcatElevenApplication tomcatElevenApplication = new TomcatElevenApplication();tomcatElevenApplication.start();}}

构建并启动Tomcat


首先我们需要知道,浏览器向服务发起 HTTP 请求时,实际上是通过 TCP 建立连接,另外 http 协议的本质上是基于 TCP 协议的应用层协议:

  1. 浏览器执行 http://localhost:8080/test

  2. 它会向 localhost8080 端口发送一个 TCP 三次握手

  3. 建立连接后,浏览器会将 HTTP 报文(如 GET /index.html HTTP/1.1)通过 TCP 流发送过去。

而 Socket 是 Java 与底层 TCP 网络通信的接口,所以我们首先去启用 Socket 去监听,而 serverSocket.accept() 方法是阻塞方法,直到有连接到来才会继续执行。

然后为了保证主线程执行结束扔可以继续接受请求,我们使用while循环不断地去监听请求并处理,之后从线程池获取线程来处理socket的方法,所以这里我们去新建一个SocketProcesser类来去封装线程任务,以便交给线程池或新线程来执行,所以这个类就需要去引入Runnable。

首先完善启动Tomcat方法:

/*** 线程池*/
private final ExecutorService executorService = Executors.newFixedThreadPool(10);    /*** 启动*/
public void start(){try {// socket 连接 TCPServerSocket serverSocket = new ServerSocket(8080);while(true){// 监听Socket socket = serverSocket.accept();// 处理 socketexecutorService.execute(() -> new SocketProcesser(socket).run());}} catch (IOException e) {throw new RuntimeException(e);}
}

然后我们创建SocketProcesser类引入Runnable:

package cn.tomcat.com;import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;/*** 处理 socket*/
public class SocketProcesser implements Runnable {private Socket socket;public SocketProcesser(Socket socket) {this.socket = socket;}@Overridepublic void run() {processSocket(socket);}/*** 处理 socket* @param socket*/private void processSocket(Socket socket) {// 处理逻辑... }
}

而在processSocket方法内就可以添加对于Socket的处理逻辑了。


处理Socket逻辑顺序


逻辑顺序:

从 Socket 读取客户端请求 → 解析 HTTP 报文 → 封装为 Request/Response → 调用 Servlet → 返回响应


获取输入流并读取数据封装到Request


首先我们调用 socket.getInputStream() 从 TCP 连接 中获取输入流,准备接收浏览器发送的 HTTP 请求数据,之后创建字节数组 byte[] bytes = new byte[1024] 存储数据并使用 inputStream.read(bytes) 方法阻塞式读取数据,返回读到的字节数:

try (InputStream inputStream = socket.getInputStream()) {byte[] bytes = new byte[1024];int read = inputStream.read(bytes);if (read <= 0) return;// ...
}

之后将byte数据转换为字符串:

// 转成字符串
String requestText = new String(bytes, 0, read);
System.out.println("原始请求:\n" + requestText);

现在我们可以去浏览器访问 http://localhost:8080/test 地址来查看后端打印:

下面是Http协议结构:

可以发现在前面有请求方法+空格+URL地址+空格+协议版本,所以我们可以将这些数据封装到Request对象中:

package cn.tomcat.com;import javax.servlet.http.HttpServletRequest;
import java.io.OutputStream;
import java.net.Socket;public class Request {private String method; // 请求方法private String url; // 请求路径private String protocol; // 请求协议private Socket socket;  // socket连接public Request(String method, String url, String protocol, Socket socket) {this.method = method;this.url = url;this.protocol = protocol;this.socket = socket;}// Getter And Setter ...
}

之后我们将解析出来并封装Request:

第一行是请求行:

GET /test HTTP/1.1

用空格拆分:

  • parts[0] = "GET" → 请求方法。

  • parts[1] = "/test" → 请求路径(URL)。

  • parts[2] = "HTTP/1.1" → 协议版本。

// 按行拆分,第一行是请求行
String[] lines = requestText.split("\r\n");
if (lines.length > 0) {String requestLine = lines[0]; // 例如: GET /test HTTP/1.1String[] parts = requestLine.split(" ");if (parts.length >= 3) {String method = parts[0];    // GETString url = parts[1];       // /testString protocol = parts[2];  // HTTP/1.1// 封装到 Request 对象Request request = new Request(method, url, protocol, socket);}
}

自定义Servlet对象


Tomcat底层是使用HttpServlet,而内部实现了service(),doGet(),doPost()等方法。而在 Servlet 规范中,doGetdoPostdoPutdoDelete 等方法是用来处理不同 HTTP 请求方法 的回调方法。它们是 HttpServlet 类提供的钩子方法,当 Tomcat 收到特定类型的 HTTP 请求时,会调用这些方法,让开发者在其中编写自己的业务逻辑。

在底层,Tomcat 调用service()方法传入 Request + Response,其方法内部根据请求方法判断到底是去使用doGet还是doPost等方法:

所以我们来自定义一个 Servlet 对象去继承 HttpServlet 来实现这些方法:

service方法可以不用重构,我们先以doGet方法重写为例。

package cn.tomcat.com;import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;@WebServlet  // 在不编写 web.xml 的情况下注册 Servlet
public class ElevenServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println(req.getMethod());// 需要先告诉浏览器一下响应体多少个字节resp.addHeader("Content-Length", "12");resp.addHeader("Content-Type", "text/html;charset=utf-8");// 响应数据resp.getOutputStream().write("hello Eleven".getBytes());}
}

而原本的Service方法需要我们去传递ServletRequest与ServletResponse:

所以我们的request与response需要分别去实现HttpServletRequest与HttpServletResponse,这里我们不想全重写了,就直接通过抽象类来实现方法,之后request与response分别去继承抽象类就OK了:

package cn.tomcat.com;import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.*;public class AbstractHttpServletRequest implements HttpServletRequest {// 省略一堆的重写方法 ...
}
package cn.tomcat.com;import java.net.Socket;public class Request extends AbstractHttpServletRequest {private String method; // 请求方法private String url; // 请求路径private String protocol; // 请求协议private Socket socket;  // 客户端 socketpublic Request(String method, String url, String protocol, Socket socket) {this.method = method;this.url = url;this.protocol = protocol;this.socket = socket;}// GETTER AND SETTER// 这里强调HttpServletRequest实现的是StringBuffer getRequestURL()方法// 所以我们需要更改回去请求路径方法// 其他的也同理需要修改public StringBuffer getRequestURL() {return new StringBuffer(url);}// ...
}

response对象也同理:

package cn.tomcat.com;import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;public class AbstractHttpServletResponse implements HttpServletResponse {// 省略一堆的重写方法 ...
}

而响应信息主要有 响应状态码 + 状态描述信息 + 响应头headers,另外一个请求对应一个响应,所以在添加一个Request属性 :

package cn.tomcat.com;import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;public class Response extends AbstractHttpServletResponse {private int status = 200;private String msg = "OK";private Map<String,String> headers = new HashMap<>();private Request request;public Response(Request request) throws IOException {this.request = request;this.socketOutputStream = request.getSocket().getOutputStream();}@Overridepublic void setStatus(int i, String s) {this.status = i;this.msg = s;}@Overridepublic int getStatus() {return status;}public String getMsg() {return msg;}@Overridepublic void addHeader(String s, String s1) {headers.put(s, s1);}
}

这回我们就可以正常去使用service方法了:

package cn.tomcat.com;import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;/*** 处理 socket*/
public class SocketProcesser implements Runnable {private Socket socket;public SocketProcesser(Socket socket) {this.socket = socket;}@Overridepublic void run() {processSocket(socket);}/*** 处理 socket* @param socket*/private void processSocket(Socket socket) {try (InputStream inputStream = socket.getInputStream()) {byte[] bytes = new byte[1024];int read = inputStream.read(bytes);if (read <= 0) return;// 转成字符串String requestText = new String(bytes, 0, read);System.out.println("原始请求:\n" + requestText);// 按行拆分,第一行是请求行String[] lines = requestText.split("\r\n");if (lines.length > 0) {String requestLine = lines[0]; // 例如: GET /test HTTP/1.1String[] parts = requestLine.split(" ");if (parts.length >= 3) {String method = parts[0];    // GETString url = parts[1];       // /testString protocol = parts[2];  // HTTP/1.1// 封装到 Request 对象Request request = new Request(method, url, protocol, socket);// 封装到 Response 对象Response response = new Response(request);// 匹配ServletElevenServlet servlet = new ElevenServlet();// 调用Servlet的service方法,帮助我们判断到底要调用doGet还是doPost等方法servlet.service(request, response);// TODO 发送响应数据}}} catch (IOException e) {// 也需要构造一个Response去返回异常提示throw new RuntimeException(e);} catch (ServletException e) {throw new RuntimeException(e);}}
}

但是我们发现运行后会产生空指针异常,这是因为我们将HttpServletResponse内部方法重写,导致我们在doGet方法内部调用的 resp.getOutputStream() 方法没有重写,而该方法表示的意思的将二进制数据写入 HTTP 响应体,并发送给客户端,所以接下来我们需要完善该方法。


暂存响应体


查看我们抽象类可以发现,这个方法返回了ServletOutputStream对象:

而ServletOutputStream是个抽象类,所以我们也肯定要自己去重写一个ServletOutputStream:

而write()方法的实现如下:

所以我们应先去重写write()方法,为了让doGet全部执行结束判断是否异常之后在调用write方法,我们需要将这个响应体存储,:

package cn.tomcat.com;import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import java.io.IOException;public class ResponseServletOutputStream extends ServletOutputStream {private byte[] bytes = new byte[1024]; // 缓冲区private int pos = 0; // 缓冲区的位置@Overridepublic void write(int b) throws IOException {bytes[pos] = (byte) b;pos++;}public byte[] getBytes() {return bytes;}public int getPos() {return pos;}
}

之后重写方法:

package cn.tomcat.com;import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;public class Response extends AbstractHttpServletResponse {// ...@Overridepublic ResponseServletOutputStream getOutputStream() throws IOException {return responseServletOutputStream;}}

随后就该去执行发送响应码了。


按Http协议发送响应数据


我们发送响应数据是通过Complete方法,所以需要重写:

package cn.tomcat.com;import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;public class Response extends AbstractHttpServletResponse {// .../*** 完成响应*/public void complete() throws IOException {sendResponseLine();sendResponseHeaders();sendResponseBody();}}

在这里面我们先定义三个方法来按照Http协议规范一次发送响应行、响应头、响应体。

而发送,我们还需要使用socket对象:

package cn.tomcat.com;import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;public class Response extends AbstractHttpServletResponse {private int status = 200;private String msg = "OK";private Map<String,String> headers = new HashMap<>();private Request request;private OutputStream socketOutputStream;private ResponseServletOutputStream responseServletOutputStream = new ResponseServletOutputStream();public Response(Request request) throws IOException {this.request = request;this.socketOutputStream = request.getSocket().getOutputStream();}
}

那么接下来我们就尝试写发送响应行:

响应行格式: HTTP/1.1 + ' ' + 200 + ' ' + OK

public class Response extends AbstractHttpServletResponse {private byte SP = ' ';  // 空格private byte CR = '\r'; // 回车private byte LF = '\n'; // 换行// .../*** 发送响应行*/private void sendResponseLine() throws IOException {socketOutputStream.write(request.getProtocol().getBytes());socketOutputStream.write(SP);socketOutputStream.write(status);socketOutputStream.write(SP);socketOutputStream.write(msg.getBytes());socketOutputStream.write(CR);socketOutputStream.write(LF);}
}

发送响应头:

HTTP 协议规定:

Content-Type: text/html;charset=utf-8
Content-Length: 123
自定义头: 值

每个响应头占一行,格式为 键: 值,行尾以 \r\n 结束。
响应头结束后,还需要再写入一个空行(即仅包含 \r\n),表示头部部分结束,后面就是响应体。

private void sendResponseHeaders() throws IOException {if(!headers.containsKey("Content-Length")) {addHeader("Content-Length", String.valueOf(getOutputStream().getPos()));}if(!headers.containsKey("Content-Type")) {addHeader("Content-Type", "text/html;charset=utf-8");}for (Map.Entry<String,String> entry : headers.entrySet()) {String key = entry.getKey();String value = entry.getValue();socketOutputStream.write(key.getBytes());  // 写入键ocketOutputStream.write(":".getBytes());   // 写入:socketOutputStream.write(value.getBytes());// 写入值socketOutputStream.write(CR);              // 回车socketOutputStream.write(LF);              // 换行}// 头部结束后,再写一个空行socketOutputStream.write(CR);socketOutputStream.write(LF);
}

发送响应体:

而发送响应体就直接使用write方法传递:

private OutputStream socketOutputStream;
private ResponseServletOutputStream responseServletOutputStream = new ResponseServletOutputStream(); // 响应体@Override
public ResponseServletOutputStream getOutputStream() throws IOException {return responseServletOutputStream;
}
/*** 发送响应体*/
private void sendResponseBody() throws IOException {socketOutputStream.write(getOutputStream().getBytes());
}

完整代码:

package cn.tomcat.com;import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;public class Response extends AbstractHttpServletResponse {private byte SP = ' ';  // 空格private byte CR = '\r'; // 回车private byte LF = '\n'; // 换行private int status = 200;private String msg = "OK";private Map<String,String> headers = new HashMap<>();private Request request;private OutputStream socketOutputStream;private ResponseServletOutputStream responseServletOutputStream = new ResponseServletOutputStream(); // 响应体public Response(Request request) throws IOException {this.request = request;this.socketOutputStream = request.getSocket().getOutputStream();}@Overridepublic void setStatus(int i, String  s) {this.status = i;this.msg = s;}@Overridepublic int getStatus() {return status;}public String getMsg() {return msg;}@Overridepublic void addHeader(String s, String s1) {headers.put(s, s1);}@Overridepublic ResponseServletOutputStream getOutputStream() throws IOException {return responseServletOutputStream;}/*** 完成响应*/public void complete() throws IOException {sendResponseLine();sendResponseHeaders();sendResponseBody();}/*** 发送响应体*/private void sendResponseBody() throws IOException {socketOutputStream.write(getOutputStream().getBytes());}/*** 发送响应头*/private void sendResponseHeaders() throws IOException {if(!headers.containsKey("Content-Length")) {addHeader("Content-Length", String.valueOf(getOutputStream().getPos()));}if(!headers.containsKey("Content-Type")) {addHeader("Content-Type", "text/html;charset=utf-8");}for (Map.Entry<String,String> entry : headers.entrySet()) {String key = entry.getKey();String value = entry.getValue();socketOutputStream.write(key.getBytes());socketOutputStream.write(":".getBytes());socketOutputStream.write(value.getBytes());socketOutputStream.write(CR);socketOutputStream.write(LF);}socketOutputStream.write(CR);socketOutputStream.write(LF);}/*** 发送响应行*/private void sendResponseLine() throws IOException {socketOutputStream.write(request.getProtocol().getBytes());socketOutputStream.write(SP);socketOutputStream.write(status);socketOutputStream.write(SP);socketOutputStream.write(msg.getBytes());socketOutputStream.write(CR);socketOutputStream.write(LF);}
}

最后调用complete方法:

package cn.tomcat.com;import javax.servlet.ServletException;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;/*** 处理 socket*/
public class SocketProcesser implements Runnable {private Socket socket;public SocketProcesser(Socket socket) {this.socket = socket;}@Overridepublic void run() {processSocket(socket);}/*** 处理 socket* @param socket*/private void processSocket(Socket socket) {try (InputStream inputStream = socket.getInputStream()) {byte[] bytes = new byte[1024];int read = inputStream.read(bytes);if (read <= 0) return;// 转成字符串String requestText = new String(bytes, 0, read);System.out.println("原始请求:\n" + requestText);// 按行拆分,第一行是请求行String[] lines = requestText.split("\r\n");if (lines.length > 0) {String requestLine = lines[0]; // 例如: GET /test HTTP/1.1String[] parts = requestLine.split(" ");if (parts.length >= 3) {String method = parts[0];    // GETString url = parts[1];       // /testString protocol = parts[2];  // HTTP/1.1// 封装到 Request 对象Request request = new Request(method, url, protocol, socket);// 打印封装结果System.out.println("方法: " + request.getMethod());System.out.println("路径: " + request.getRequestURL());System.out.println("协议: " + request.getProtocol());// 封装到 Response 对象Response response = new Response(request);// 匹配ServletElevenServlet servlet = new ElevenServlet();// 调用Servlet的service方法,帮助我们判断到底要调用doGet还是doPost等方法servlet.service(request, response);// 发送响应数据response.complete();}}} catch (IOException e) {// 也需要构造一个Response去返回异常提示throw new RuntimeException(e);} catch (ServletException e) {throw new RuntimeException(e);}}
}

部署Tomcat


用过Tomcat的知道,Tomcat 的 webapps/ 目录是部署入口,所以我们先建立一个webapps目录:

这个hello就相当于一个项目,或者也可以称作一个Jar包,而在classes下就可以放入一些类,而tomcat关心的是这些项目或者类中哪里有servlet,然后根据servlet去匹配方法处理请求。

首先将我们的ElevenServlet.class文件放在classes/eleven/ElevenServlet.class下来伪造一个Servlet,之后将原本的ElevenServlet删除。那么现在就相当于我在tomcat下面部署了一个hello应用,而这个应用下面还有ElevenServlet,而在tomcat启动前首先需要完成部署App:

public class TomcatElevenApplication {// ...public static void main(String[] args) {TomcatElevenApplication tomcatElevenApplication = new TomcatElevenApplication();tomcatElevenApplication.deployApps(); // 部署APPtomcatElevenApplication.start();}
}

如何实现该方法呢?

首先肯定需要找到tomcat下有哪些应用,先拿到webApps文件夹,然后遍历内部应用,随后准备使用deployApp来比那里应用内的所有类:

/*** 遍历webapps目录*/
private void deployApps() {File webApps = new File(System.getProperty("user.dir"), "/webapps");if(webApps.exists()){for(String app : webApps.list()){deployApp(webApps, app);}}
}

之后编写deployApp方法,主要目的是判断当前应用下有哪些类继承了HttpServlet,然后在该类拿到@WebServlet注解值,并存储起来方便处理Socket时使用。

我们先创建存储类:

package cn.tomcat.com;import javax.servlet.Servlet;
import java.util.HashMap;
import java.util.Map;/*** 应用上下文*/
public class Context {/*** 应用名称*/private String appName;/*** url 映射*/private Map<String, Servlet> urlPatternMap = new HashMap<String, Servlet>();public Context(String appName) {this.appName = appName;}/*** 添加servlet* @param urlPattern* @param servlet*/public void addServlet(String urlPattern, Servlet servlet) {urlPatternMap.put(urlPattern, servlet);}/*** 根据url获取servlet* @param urlPattern* @return*/public Servlet getByUrlPattern(String urlPattern) {for (String key : urlPatternMap.keySet()) {if (urlPattern.contains(key)) {return urlPatternMap.get(key);}}return null;}
}

之后按照上面逻辑实现查找:

注意,加载类的时候要使用自定义类加载器,否则因为目录不在同一个,扫描不到classes:

package cn.tomcat.com;import java.net.URL;
import java.net.URLClassLoader;/*** 自定义类加载器*/
public class WebappClassLoader extends URLClassLoader {public WebappClassLoader(URL[] urls) {super(urls);}
}
/*** 保存Tomcat有哪些应用*/
private Map<String, Context> contextMap = new HashMap<>();/*** 遍历当前应用内所有类中是否有继承HttpServlet的,* 如果有,就将它添加到应用上下文* @param webApps* @param appName*/
private void deployApp(File webApps, String appName) {Context context = new Context(appName);// 当前应用下面有哪些ServletFile appDirectory = new File(webApps, appName); // hello文件夹File classesDirectory = new File(appDirectory, "classes"); // classes文件夹List<File> allFilePath = getAllFilePath(classesDirectory);for (File file : allFilePath) {if(file.getName().endsWith(".class")){// 是类文件// 思路:加载为Class对象,随后用反射判断是否继承了HttpServlet// 转换类加载格式String name = file.getPath();name = name.replace(classesDirectory.getPath() + "\\ ", "/");name = name.replace(".class", "");name = name.replace("\\", "/");// 类加载器加载类try {// 这样是加载不到的,因为应用不在这个cn.tomcat.com下
//                    Class<?> servletClass = Thread.currentThread().getContextClassLoader().loadClass(name);// 使用自定义的类加载器加载类,让它去加载classes目录WebappClassLoader webappClassLoader = new WebappClassLoader(new URL[]{classesDirectory.toURI().toURL()});Class<?> servletClass = webappClassLoader.loadClass(name);// 判断是否继承了HttpServletif(HttpServlet.class.isAssignableFrom(servletClass)){// 是HttpServlet的子类System.out.println("发现Servlet:" + name);// 解析URL对应的匹配规则if(servletClass.isAnnotationPresent(javax.servlet.annotation.WebServlet.class)){// 获取注解value值WebServlet webServlet = servletClass.getAnnotation(WebServlet.class);String[] urlPatterns = webServlet.urlPatterns();// 存储到上下文for (String urlPattern : urlPatterns) {System.out.println("发现URL:" + urlPattern);// 存储到Map中context.addServlet(urlPattern, (Servlet) servletClass.newInstance());}}}} catch (ClassNotFoundException e) {throw new RuntimeException(e);} catch (MalformedURLException e) {throw new RuntimeException(e);} catch (InstantiationException e) {throw new RuntimeException(e);} catch (IllegalAccessException e) {throw new RuntimeException(e);}}}// 部署完成,保存应用映射contextMap.put(appName, context);
}

最后我们去完善SocketProcesser内处理Socket方法:

西药修改的是我们的匹配Servlet,需要通过刚刚在TomcatElevenApplication保存到tomcat的map来根据url找到对应的Servlet。

/*** 处理 socket* @param socket*/
private void processSocket(Socket socket) {try (InputStream inputStream = socket.getInputStream()) {byte[] bytes = new byte[1024];int read = inputStream.read(bytes);if (read <= 0) return;// 转成字符串String requestText = new String(bytes, 0, read);System.out.println("原始请求:\n" + requestText);// 按行拆分,第一行是请求行String[] lines = requestText.split("\r\n");if (lines.length > 0) {String requestLine = lines[0]; // 例如: GET /test HTTP/1.1String[] parts = requestLine.split(" ");if (parts.length >= 3) {String method = parts[0];    // GETString url = parts[1];       // /testString protocol = parts[2];  // HTTP/1.1// 封装到 Request 对象Request request = new Request(method, url, protocol, socket);// 封装到 Response 对象Response response = new Response(request);//                    // 匹配Servlet
//                    ElevenServlet servlet = new ElevenServlet();
//                    // 调用Servlet的service方法,帮助我们判断到底要调用doGet还是doPost等方法
//                    servlet.service(request, response);// 判断请求是想访问哪些应用String requestUrl = request.getRequestURL().toString();// 例如: http://localhost:8080/test// 我们要获取 /test 这部分String contextPath = requestUrl.substring(requestUrl.indexOf("/", 7), requestUrl.indexOf(":", 7));// 从应用中获取 ServletContext context = tomcatElevenApplication.getContextMap().get(contextPath);Servlet servlet = context.getByUrlPattern(url);if (servlet != null) {servlet.service(request, response);// 发送响应数据response.complete();} else {new DefaultServlet().service(request, response);// 404response.setStatus(404, "Not Found");response.complete();}}}} catch (IOException e) {// 也需要构造一个Response去返回异常提示throw new RuntimeException(e);} catch (ServletException e) {throw new RuntimeException(e);}
}

这里为了让没找到也有对应的Servlet,我们设置一个默认的Servlet:

package cn.tomcat.com;import javax.servlet.http.HttpServlet;public class DefaultServlet extends HttpServlet {}

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

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

相关文章

Linux网络:初识网络

文章目录1. 网络发展1.1 独立模式1.2 网络互联1.3 局域网LAN1.4 广域网WAN2. 认识 “协议”2.1 什么是协议&#xff1f;2.2 为什么要有协议&#xff1f;2.3 深入了解协议序&#xff1a;开网络之篇章&#xff0c;建网络之基础&#xff0c;将近2月过去&#xff0c;暑假期间不曾有…

文件检查与拷贝-简化版

本篇继续来学习shell脚本&#xff0c;对上一篇的文件检查与拷贝脚本进行简化修改。 1 功能说明 在Linux系统中&#xff0c;通过一个shell脚本&#xff0c;实现将一个目录中的所有文件&#xff08;包括子目录中的&#xff09;&#xff0c;拷贝到顶一个指定的目录&#xff0c;要求…

DCA1000 AWR1843 环境安装

mmWaveStudio GUI设计用于表征和评估TI Radar器械。mmWaveStudio通过SPI向mmWave设备发送命令来配置和控制mmWave设备。使用DCA 1000 EVM或TSW 1400 EVM板捕获ADC数据&#xff0c;并在Matlab中处理数据&#xff0c;结果显示在GUI中。mmWaveStudio GUI利用C DLL和一组API通过FTD…

机器学习实操项目01——Numpy入门(基本操作、数组形状操作、复制与试图、多种索引技巧、线性代数)

上一章&#xff1a;【从 0 到 1 落地】机器学习实操项目目录&#xff1a;覆盖入门到进阶&#xff0c;大学生就业 / 竞赛必备 下一章&#xff1a; 机器学习核心知识点目录&#xff1a;机器学习核心知识点目录 机器学习实战项目&#xff1a;【从 0 到 1 落地】机器学习实操项目目…

【vscode】如何离线下载vsxi插件,且在无网环境下离线安装插件-2026最新实验教程

文章目录插件市场也可以从APP进入无网环境下安装插件插件市场 https://marketplace.visualstudio.com/vscode 也可以从APP进入 这里以下载python插件为例 选择版本 无网环境下安装插件

vue2 侦听器watch

一、watch 核心作用监测数据变化&#xff1a;当被监听的数据发生改变时&#xff0c;自动执行指定的处理函数处理副作用&#xff1a;适合执行异步操作&#xff08;如接口请求&#xff09;、复杂逻辑处理等 “副作用” 代码二、基础语法&#xff08;3 种写法&#xff09;简单写法…

今天继续学习Linux系统中shell脚本

首先继续上次的内容看一下另一个案例案例&#xff1a;持续检查服务器负载uptime查看负载情况&#xff08;也可以用top命令&#xff09;[rootlocalhost ~]# uptime22:11:26 up 7:05, 3 users, load average: 0.00, 0.00, 0.00#!/bin/bash #Function:持续检查服务器负载,如果负…

Win系统下配置PCL库第一步之下载Visual Studio和Qt 5.15.2(超详细)

之前在上篇文章Win系统下配置PCL库_windows pcl库 下载-CSDN博客中提到配置PCL库的教程是下载Visual Studio和Qt 5.15.2&#xff0c;后续在测试中我发现前面这两步很重要&#xff0c;一般Qt在线下载器选项选不好的话Qt是装的Qt6&#xff0c;在VTK编译的时候Qt6往往需要C17编译&…

openCV3.0 C++ 学习笔记补充(自用 代码+注释)---持续更新 四(91-)

环境&#xff1a;OpenCV3.2.0 VS201791、合并Y方向重叠的轮廓以轮廓的最小垂直外接矩形框的y为依据&#xff0c;合并y重叠的轮廓。数学逻辑&#xff1a;几何合并的数学表达坐标系统&#xff1a;假设矩形由左上角坐标(x, y)和宽高(width, height)定义。合并公式&#xff1a;合并…

numpy数组的升维和降维的方法集锦

为适配计算包对numpy数组的维度要求&#xff0c;对numpy数组进行升维或降维转化&#xff0c;是非常常见的操作。这里尝试通过多种方式对numpy数组进行升维或降维。1 数组升维1.1 np.expand_dims在0维升维&#xff0c;示例如下a np.array([1,2,3,4,5]) np.expand_dims(a, axis0…

介绍 Python Elasticsearch Client 的 ES|QL 查询构建器

作者&#xff1a;来自 Elastic Miguel Grinberg 学习如何使用 ES|QL 查询构建器&#xff0c;这是一个新的 Python Elasticsearch client 功能&#xff0c;可以更轻松地使用熟悉的 Python 语法构建 ES|QL 查询。 想要获得 Elastic 认证吗&#xff1f;快来了解下一期 Elasticsear…

三坐标测量仪:高精度测量内径检测手段及其实际运用

在工业制造领域中&#xff0c;内径尺寸的精准度直接关系到产品的装配性能、运行稳定性乃至使用寿命。传统检测方法如卡尺、内径千分尺等难以满足高精度、复杂结构件的需求。三坐标测量仪技术的出现&#xff0c;打破了这一困境&#xff0c;成为当前工业领域实现高精度内径检测的…

DIPMARK:一种隐蔽、高效且具备鲁棒性的大语言模型水印技术

摘要水印技术为通过在数据中嵌入隐蔽信息来保障数据安全提供了一种很有前景的方法。该领域的一个首要挑战在于&#xff0c;在水印嵌入过程中保持原始数据的分布。我们的研究拓展并优化了现有的水印框架&#xff0c;着重强调了保持分布&#xff08;DiP&#xff09;水印的重要性。…

IMU传感器价格与高精度惯性导航系统供应商分析

本段将对IMU传感器价格及高精度惯性导航系统的市场情况进行概览。IMU传感器作为惯性导航的重要组成部分&#xff0c;其价格水平受到技术、需求和供应商竞争等多重因素的影响。随着无人机、自动驾驶车辆等新兴应用场景的兴起&#xff0c;IMU传感器的市场需求逐渐攀升。这不仅带动…

3-9〔OSCP ◈ 研记〕❘ WEB应用攻击▸利用REST API提权

郑重声明&#xff1a; 本文所有安全知识与技术&#xff0c;仅用于探讨、研究及学习&#xff0c;严禁用于违反国家法律法规的非法活动。对于因不当使用相关内容造成的任何损失或法律责任&#xff0c;本人不承担任何责任。 如需转载&#xff0c;请注明出处且不得用于商业盈利。 …

UE5 基础应用 —— 07 - 角色蓝图 简单使用

目录 一、角色蓝图 1.1 Pawn / Character 1.2 角色基类 1.3 角色基类设置 1.3.1 基础设置 1.3.2 角色移动和相机旋转 1.3.3 角色移动 —— 锁定视角 1.3.4 角色跳跃 1.4 角色派生类设置 1.4.1 添加动画蓝图 一、角色蓝图 1.1 Pawn / Character Pawn / Character 有什…

流畅的Python(二) 丰富的序列

流畅的Python 第二章&#xff1a;丰富的序列 摘要&#xff1a;在日常Python开发中&#xff0c;我们频繁与各种数据结构打交道&#xff0c;其中序列类型&#xff08;如列表、元组、字符串&#xff09;是基石。然而&#xff0c;你是否曾因对它们理解不深&#xff0c;而在性能优化…

嵌入式 - ARM6

一、按键1. 初始化key.c手册C32 - IOMUXC1. 复用功能配置IOMUXC_SW_MUX_CTL_PAD_UART1_CTS_B: 低四位&#xff08;0101&#xff09; IOMUXC_SetPinMux(IOMUXC_UART1_CTS_B_GPIO1_IO18, 0);SION(信号监控)1: 0 //0 DISABLED — Input Path is determined by functionality MUX_…

菊水PBZ电源在蓄电池充放电测试中的应用探讨

通过高速双极性电源PBZ系列进行蓄电池恒流&#xff0c;恒压充电的方法 对于仪器厂商来说&#xff0c;要求“请按照使用说明书使用”是产品的使用方针&#xff0c;或者说是正确用法。但是&#xff0c;作为具有代表性的通用产品&#xff0c;直流电源的实际使用方法可谓五花八门&…

Zephyr嵌入式实时操作系统安装配置

Zephyr简介 Zephyr 是一款由 Linux 基金会 托管的开源实时操作系统(RTOS),专为资源受限的嵌入式设备(从微控制器到小型边缘计算节点)设计,广泛应用于物联网(IoT)、工业自动化、消费电子、医疗设备、汽车电子等领域。其核心优势在于轻量级、高可配置性和对多架构硬件的广…