第四章:实现 Servlet 容器的基本功能-MiniTomcat系列
第四章:实现 Servlet 容器的基本功能
上一章内容:第三章:实现连接器(Connector)组件-MiniTomcat 系列
本章将深入探索 MiniTomcat 的核心进阶环节——实现 Servlet 容器的基本功能。这是服务器处理动态请求的关键步骤,通过合理的代码设计,我们将赋予 MiniTomcat 处理 Java Servlet 的能力,使其从静态资源服务器升级为支持动态内容的 Web 容器。
一、Servlet 容器:核心概念
(一)核心使命
在 MiniTomcat 架构中,Servlet 容器负责掌控处理动态请求的核心逻辑。其主要任务包括:
- 生命周期管理:管理 Servlet 的初始化(
init)、请求处理(service)及销毁(destroy)过程。 - 请求分发:当 HTTP 请求到达时,根据请求路径找到匹配的 Servlet,并调用其
service()方法进行处理。
(二)功能目标
- 请求与响应封装:通过
HttpServletRequest和HttpServletResponse接口封装 HTTP 请求数据(如路径、方法、头部)和响应数据,规范数据传递流程。 - 请求路径映射:实现简单的路径映射机制,根据客户端请求的 URI 定位到对应的 Servlet 实例,确保请求被准确处理。
二、项目结构升级
随着 Servlet 容器功能的加入,项目结构进行了相应调整,增加了请求/响应封装类及 Servlet 处理器。更新后的结构如下:
MiniTomcat
├─ src
│ ├─ main
│ │ ├─ java
│ │ │ ├─ com.daicy.minitomcat
│ │ │ │ ├─ CustomServletOutputStream.java // ServletOutputStream 封装
│ │ │ │ ├─ HttpConnector.java // 连接器类
│ │ │ │ ├─ HttpProcessor.java // 请求处理器(升级)
│ │ │ │ ├─ HttpServer.java // 主服务器类
│ │ │ │ ├─ HttpServletRequest.java // 请求封装类(接口实现)
│ │ │ │ ├─ HttpServletResponse.java // 响应封装类(接口实现)
│ │ │ │ ├─ ServletProcessor.java // Servlet 处理器
│ │ │ │ ├─ StaticResourceProcessor.java // 静态资源处理器
│ │ │ │ ├─ HelloServlet.java // 示例 Servlet
│ │ ├─ resources
│ │ │ ├─ webroot
│ │ │ │ ├─ index.html
├─ pom.xml三、代码实现剖析
(一)请求与响应的封装
1. HttpServletRequestImpl 类
该类实现了 HttpServletRequest 接口,负责收集并存储请求的关键信息(如方法、URI)。
package com.daicy.minitomcat;
import javax.servlet.http.HttpServletRequest;
public class HttpServletRequestImpl implements HttpServletRequest {
private String method;
private String requestUri;
public HttpServletRequestImpl(String method, String requestURI) {
this.method = method;
this.requestUri = requestURI;
}
// 此处省略其他接口方法的实现,主要聚焦于请求信息的存储
@Override
public String getRequestURI() {
return requestUri;
}
@Override
public String getMethod() {
return method;
}
// 其他方法需根据接口定义补充实现或抛出 UnsupportedOperationException
}2. HttpServletResponseImpl 类
该类实现了 HttpServletResponse 接口,负责管理输出流并构建响应数据。
package com.daicy.minitomcat;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
public class HttpServletResponseImpl implements HttpServletResponse {
private OutputStream outputStream;
public HttpServletResponseImpl(OutputStream outputStream) {
this.outputStream = outputStream;
}
@Override
public ServletOutputStream getOutputStream() {
return new CustomServletOutputStream(outputStream);
}
@Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(outputStream, true);
}
// 其他方法需根据接口定义补充实现
}(二)Servlet 处理器:请求调度
ServletProcessor 类负责根据请求路径查找并调用对应的 Servlet。本示例中采用简化的硬编码映射方式。
package com.daicy.minitomcat;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import static com.daicy.minitomcat.HttpProcessor.send404Response;
public class ServletProcessor {
private Map<String, Servlet> servletMappings = new HashMap<>();
public void process(HttpServletRequest request, HttpServletResponse response) {
String servletName = getServletName(request.getRequestURI());
try {
PrintWriter writer = response.getWriter();
if ("HelloServlet".equals(servletName)) {
writeResponseHeaders(writer, 200, "OK");
Servlet servlet;
// 简单的单例缓存逻辑
if (servletMappings.containsKey(servletName)) {
servlet = servletMappings.get(servletName);
} else {
servlet = new HelloServlet();
servlet.init(null);
servletMappings.put(servletName, servlet);
}
servlet.service(request, response);
} else {
send404Response(writer);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ServletException e) {
throw new RuntimeException(e);
}
}
private String getServletName(String path) {
if ("/hello".equals(path)) {
return "HelloServlet";
}
return null;
}
private void writeResponseHeaders(PrintWriter writer, int statusCode, String statusMessage) {
writer.println("HTTP/1.1 " + statusCode + " " + statusMessage);
writer.println("Content-Type: text/html; charset=UTF-8");
writer.println();
}
}(三)示例 Servlet:HelloServlet
HelloServlet 是一个标准的 Servlet 实现示例,展示了生命周期的基本方法。
package com.daicy.minitomcat;
import javax.servlet.*;
import java.io.IOException;
public class HelloServlet implements Servlet {
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("HelloServlet initialized.");
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest req, ServletResponse res) {
try {
res.getWriter().println("<html><body><h1>Hello from HelloServlet!</h1></body></html>");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String getServletInfo() {
return "";
}
@Override
public void destroy() {
System.out.println("HelloServlet destroyed.");
}
}(四)HttpProcessor 升级:动静分离
HttpProcessor 经过升级,能够根据请求路径区分静态资源与动态请求,并分发至不同的处理器。
package com.daicy.minitomcat;
import java.io.*;
import java.net.Socket;
public class HttpProcessor {
private Socket socket;
private final static ServletProcessor servletProcessor = new ServletProcessor();
private final static StaticResourceProcessor staticProcessor = new StaticResourceProcessor();
public HttpProcessor(Socket socket) {
this.socket = socket;
}
public void process() {
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
// 解析请求
HttpServletRequestImpl request = parseRequest(inputStream);
// 构建响应
HttpServletResponseImpl response = new HttpServletResponseImpl(outputStream);
if (null == request) {
return;
}
String uri = request.getRequestURI();
// 简单的动静分离判断
if (uri.endsWith(".html") || uri.endsWith(".css") || uri.endsWith(".js")) {
staticProcessor.process(request, response);
} else {
servletProcessor.process(request, response);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private HttpServletRequestImpl parseRequest(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String requestLine = reader.readLine();
if (requestLine == null || requestLine.isEmpty()) {
return null;
}
System.out.println("Request Line: " + requestLine);
String[] parts = requestLine.split(" ");
if (parts.length < 2) {
return null;
}
String method = parts[0];
String path = parts[1];
return new HttpServletRequestImpl(method, path);
}
static void send404Response(PrintWriter writer) {
sendResponse(writer, 404, "Not Found", "The requested resource was not found.");
}
private static void sendResponse(PrintWriter writer, int statusCode, String statusText, String message) {
String html = "<html><body><h1>" + statusCode + " " + statusText + "</h1><p>" + message + "</p></body></html>";
writer.println("HTTP/1.1 " + statusCode + " " + statusText);
writer.println("Content-Type: text/html; charset=UTF-8");
writer.println("Content-Length: " + html.length());
writer.println();
writer.println(html);
}
}四、测试与验证
启动服务器后,在浏览器中访问 http://localhost:8080/hello。若配置正确,页面将显示 HelloServlet 返回的内容:"Hello from HelloServlet!"。这表明 MiniTomcat 已成功具备处理动态 Servlet 请求的能力。
五、总结与展望
通过本章实现,我们掌握了以下核心内容:
- 请求响应封装:熟悉了
HttpServletRequest和HttpServletResponse在容器内部的实现原理。 - 容器基础架构:理解了 Servlet 生命周期管理及请求分发的基本流程。
- 动静分离:实现了静态资源与动态请求的初步分流处理。
这为后续拓展更复杂的功能(如注解映射、配置文件加载、过滤器链等)奠定了坚实基础。
项目源代码地址:
https://github.com/daichangya/MiniTomcat/tree/chapter4/mini-tomcat
说明:本文代码基于javax.servlet包(Java EE 规范),适用于 Tomcat 9 及以下版本。若使用 Tomcat 10+ 或 Jakarta EE 9+ 环境,包名需变更为jakarta.servlet,且需调整相关依赖配置。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。