在上一篇文章中,我們實現了一個基礎的 Java Web 服務器,僅能處理靜態資源請求。本文將在其基礎上進行擴展,加入 Servlet 請求處理能力,構建一個簡易的 Servlet 容器。這不僅能加深對 Tomcat 等成熟容器原理的理解,更是掌握 Java Web 底層機制的關鍵一步。
容器啟動後,在 8080 端口監聽請求。收到請求時,解析 URI:若以 /servlet/ 開頭,委派給 ServletProcessor 處理;否則交由 StaticResourceProcessor 處理靜態資源。核心類包括 HttpServer1、Request、Response、ServletProcessor1 以及示例 Servlet PrimitiveServlet。
<!-- Servlet API 2.3 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.3</version> </dependency>
package ex02.pyrmont.first;
import java.net.*;
import java.io.*;
import ex02.pyrmont.*;
public class HttpServer1 {
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
public static void main(String[] args) {
new HttpServer1().await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (true) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
Request request = new Request(input);
request.parse();
if (request.getUri().equals(SHUTDOWN_COMMAND)) break;
Response response = new Response(output);
response.setRequest(request);
if (request.getUri().startsWith("/servlet/")) {
new ServletProcessor1().process(request, response);
} else {
new StaticResourceProcessor().process(request, response);
}
socket.close();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
}
}
package ex02.pyrmont;
import java.io.File;
public class Constants {
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
public static final String WEB_SERVLET_ROOT = System.getProperty("user.dir") + File.separator + "target" + File.separator + "classes";
}
負責解析 HTTP 請求,提取 URI。核心邏輯在 parse() 方法中讀取輸入流,parseUri() 從請求行中截取 URI(如 /index.html)。其他 ServletRequest 方法暫時返回默認值。
sendStaticResource() 讀取文件並寫入輸出流,構建簡易 HTTP 響應頭。若文件不存在返回 404。getWriter() 返回 PrintWriter 供 Servlet 寫入響應體。
package ex02.pyrmont;
import java.io.IOException;
public class StaticResourceProcessor {
public void process(Request request, Response response) {
try {
response.sendStaticResource();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package ex02.pyrmont.first;
import java.net.*;
import java.io.*;
import javax.servlet.*;
import ex02.pyrmont.*;
public class ServletProcessor1 {
public void process(Request request, Response response) {
String uri = request.getUri();
String servletName = uri.substring(uri.lastIndexOf("/") + 1);
URLClassLoader loader = null;
try {
loader = new URLClassLoader(new URL[]{new URL(null, "file:" + Constants.WEB_SERVLET_ROOT, null)});
} catch (IOException e) { System.out.println(e.toString()); }
Class<?> myClass = null;
try { myClass = loader.loadClass(servletName); }
catch (ClassNotFoundException e) { System.out.println(e.toString()); }
Servlet servlet = null;
try {
servlet = (Servlet) myClass.newInstance();
servlet.service((ServletRequest) request, (ServletResponse) response);
} catch (Exception e) { System.out.println(e.toString()); }
catch (Throwable e) { System.out.println(e.toString()); }
}
}
import javax.servlet.*;
import java.io.*;
public class PrimitiveServlet implements Servlet {
public void init(ServletConfig config) throws ServletException { System.out.println("init"); }
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("from service");
PrintWriter out = response.getWriter();
out.println("Hello. Roses are red.");
out.print("Violets are blue.");
}
public void destroy() { System.out.println("destroy"); }
public String getServletInfo() { return null; }
public ServletConfig getServletConfig() { return null; }
}
靜態資源請求正常;Servlet 請求僅輸出第一句 "Hello. Roses are red.",因為 PrintWriter 的 println() 會刷新緩衝區,導致第二句未顯示。此外,存在嚴重安全隱患:用戶可將 ServletRequest、ServletResponse 向下轉型為內部類 Request、Response,直接調用 parse()、sendStaticResource() 等內部方法。
為防止內部類被直接訪問,引入 RequestFacade 和 ResponseFacade,僅暴露 ServletRequest 和 ServletResponse 接口定義的方法。
package ex02.pyrmont.second;
import javax.servlet.*;
import ex02.pyrmont.Request;
public class RequestFacade implements ServletRequest {
private ServletRequest request;
public RequestFacade(Request request) { this.request = request; }
// 委派所有 ServletRequest 方法給內部 request 對象
public Object getAttribute(String name) { return request.getAttribute(name); }
// ... 其他方法同理委派
}
package ex02.pyrmont.second;
import javax.servlet.*;
import ex02.pyrmont.Response;
public class ResponseFacade implements ServletResponse {
private ServletResponse response;
public ResponseFacade(Response response) { this.response = response; }
// 委派所有 ServletResponse 方法
public PrintWriter getWriter() throws IOException { return response.getWriter(); }
// ... 其他方法同理委派
}
package ex02.pyrmont.second;
// ... imports
public class ServletProcessor2 {
public void process(Request request, Response response) {
// ... 類加載與實例化邏輯同前 ...
RequestFacade requestFacade = new RequestFacade(request);
ResponseFacade responseFacade = new ResponseFacade(response);
servlet.service(requestFacade, responseFacade);
}
}
本文實現了從簡易 Web 服務器到支持 Servlet 的容器演進,通過外觀模式解決了安全性問題。後續可進一步完善:生命周期管理(init/destroy)、多線程處理、配置文件解析、過濾器鏈等,逐步逼近生產級容器架構。
參考資料:《深入剖析 Tomcat》