上一章内容:第三章:实现连接器(Connector)组件-MiniTomcat系列
在这一部分,我们将实现一个基础的 Servlet 容器,从而使 MiniTomcat 能够处理动态请求。Servlet 容器的核心是管理 Servlet 生命周期,并在 HTTP 请求到达时调用合适的 Servlet 处理逻辑。
4.1 功能目标
实现
HttpServletRequest
和HttpServletResponse
:封装 HTTP 请求和响应数据。请求路径映射:根据请求路径找到对应的 Servlet 并调用其
service()
方法处理请求。
4.2 代码结构
以下是 MiniTomcat 的更新后代码结构,我们在 com.daicy.minitomcat
包中添加了 HttpServletRequestImpl
、HttpServletResponseImpl
、ServletProcessor
和 HelloServlet
、CustomServletOutputStream
等类。
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
4.3 代码实现
4.3.1 实现 HttpServletRequest
和 HttpServletResponse
类
HttpServletRequest
用于封装请求的路径、方法和头部信息,HttpServletResponse
用于封装响应数据。
package com.daicy.minitomcat;
public class HttpServletRequestImpl implements HttpServletRequest {
private String method;
private String requestUri;
public HttpServletRequestImpl(String method, String requestURI) {
this.method = method;
this.requestUri = requestURI;
}
.....
}
package com.daicy.minitomcat;
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 {
PrintWriter writer = new PrintWriter(outputStream, true);
return writer;
}
....
}
4.3.2 创建 ServletProcessor
类
ServletProcessor
用于根据请求路径找到对应的 Servlet 类,并调用其 service()
方法处理请求。
package com.daicy.minitomcat;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import static com.daicy.minitomcat.HttpProcessor.send404Response;
public class ServletProcessor {
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");
HelloServlet servlet = new HelloServlet();
servlet.service(request, response);
} else {
send404Response(writer);
}
} catch (IOException e) {
e.printStackTrace();
}
}
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();
}
}
4.3.3 示例 Servlet:HelloServlet
HelloServlet
是一个示例 Servlet,实现了 service()
方法,并返回简单的响应内容。
package com.daicy.minitomcat;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class HelloServlet {
public void service(HttpServletRequest request, HttpServletResponse response) {
try {
response.getWriter().println("<html><body><h1>Hello from HelloServlet!</h1></body></html>");
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.3.4 修改 HttpProcessor
处理动态请求
在 HttpProcessor
中,我们根据路径选择是处理静态资源还是动态请求,并使用 ServletProcessor
处理动态请求。
package com.daicy.minitomcat;
import java.io.*;
import java.net.Socket;
public class HttpProcessor {
private Socket socket;
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")) {
StaticResourceProcessor staticProcessor = new StaticResourceProcessor();
staticProcessor.process(request, response);
} else {
ServletProcessor processor = new ServletProcessor();
processor.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(" ");
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);
}
}
4.4 测试
启动服务器并访问 http://localhost:8080/hello
,可以看到由 HelloServlet
返回的响应内容:“Hello from HelloServlet!”
4.5 学习收获
通过实现 Servlet 容器的基础功能,我们可以学到:
请求与响应封装:通过实现
HttpServletRequest
和HttpServletResponse
,掌握了封装和传递请求与响应数据的技巧。
这一实现为后续的 Servlet 映射、配置文件支持等功能奠定了基础。