首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

從零構建簡易 Servlet 容器:核心原理與代碼實戰

     編輯:Java 綜合教學

前言

在上一篇文章中,我們實現了一個基礎的 Java Web 服務器,僅能處理靜態資源請求。本文將在其基礎上進行擴展,加入 Servlet 請求處理能力,構建一個簡易的 Servlet 容器。這不僅能加深對 Tomcat 等成熟容器原理的理解,更是掌握 Java Web 底層機制的關鍵一步。

程序執行流程

容器啟動後,在 8080 端口監聽請求。收到請求時,解析 URI:若以 /servlet/ 開頭,委派給 ServletProcessor 處理;否則交由 StaticResourceProcessor 處理靜態資源。核心類包括 HttpServer1、Request、Response、ServletProcessor1 以及示例 Servlet PrimitiveServlet。

核心代碼實現

1. 依賴配置

<!-- Servlet API 2.3 -->
<dependency>
  <groupId>javax.servlet</groupId>
  <artifactId>servlet-api</artifactId>
  <version>2.3</version>
</dependency>

2. 服務器主類 HttpServer1

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

3. 常量類

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

4. Request 類:實現 ServletRequest 接口

負責解析 HTTP 請求,提取 URI。核心邏輯在 parse() 方法中讀取輸入流,parseUri() 從請求行中截取 URI(如 /index.html)。其他 ServletRequest 方法暫時返回默認值。

5. Response 類:實現 ServletResponse 接口

sendStaticResource() 讀取文件並寫入輸出流,構建簡易 HTTP 響應頭。若文件不存在返回 404。getWriter() 返回 PrintWriter 供 Servlet 寫入響應體。

6. 靜態資源處理器

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

7. Servlet 處理器(第一版)

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

8. 示例 Servlet

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 接口定義的方法。

RequestFacade

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); }
    // ... 其他方法同理委派
}

ResponseFacade

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(); }
    // ... 其他方法同理委派
}

升級版 ServletProcessor2

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》

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved