1、简要概述web容器原理

  1. servlet生命周期方法:init(), service(), destory()
    大部分的框架,如struts, springmvc等都是基于servlet作为主入口,实际上是WEB请求的一个入口。
  2. J2EE中有两个非常重要的对象:HttpRequest,HttpResponse,但是这两个对象从来不是由程序员单独创建的,而是由web容器自动创建的。

1.1 从模拟tomcat开始

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

public class SimpleServer {
    //模拟tomcat
    public static void main(String[] args) {
        int port = 8080;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is listening on port " + port);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                // 开启新线程处理客户端,支持多连接
                new Thread(() -> handleClient(clientSocket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleClient(Socket clientSocket) {
        System.out.println("New client connected:" + clientSocket.getRemoteSocketAddress());
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            out = new PrintWriter(clientSocket.getOutputStream(), true);

            String clientMessage;
            while ((clientMessage = in.readLine()) != null) {
                System.out.println("Client says: " + clientMessage);
                out.println(clientMessage);
            }

        } catch (SocketException e) {
            // 【重点】捕获客户端强制断开连接异常
            String msg = e.getMessage();
            if (msg.contains("中止了一个已建立的连接") || msg.contains("Connection aborted")) {
                System.out.println("WARN:客户端强制断开连接");
            } else {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) in.close();
                if (out != null) out.close();
                if (!clientSocket.isClosed()) {
                    clientSocket.close();
                }
                System.out.println("Client disconnected");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

浏览器输入:localhost:8080,查看控制台输出

"D:\Program Files\Java\jdk-21\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2026.1\lib\idea_rt.jar=55073" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath D:\project\deepseek\DNTomcat\out\production\DNTomcat SimpleServer
Server is listening on port 8080
New client connected:/[0:0:0:0:0:0:0:1]:55087
Client says: GET / HTTP/1.1
Client says: Host: localhost:8080
Client says: Connection: keep-alive
Client says: Cache-Control: max-age=0
Client says: sec-ch-ua: "Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"
Client says: sec-ch-ua-mobile: ?0
Client says: sec-ch-ua-platform: "Windows"
Client says: Upgrade-Insecure-Requests: 1
Client says: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Client says: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Client says: Sec-Fetch-Site: none
Client says: Sec-Fetch-Mode: navigate
Client says: Sec-Fetch-User: ?1
Client says: Sec-Fetch-Dest: document
Client says: Accept-Encoding: gzip, deflate, br, zstd
New client connected:/[0:0:0:0:0:0:0:1]:55089
New client connected:/[0:0:0:0:0:0:0:1]:55088
Client says: Accept-Language: zh-CN,zh;q=0.9
Client says: 
WARN:客户端强制断开连接
Client disconnected
Client says: GET / HTTP/1.1
Client says: Host: localhost:8080
Client says: Connection: keep-alive
Client says: Cache-Control: max-age=0
Client says: sec-ch-ua: "Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"
Client says: sec-ch-ua-mobile: ?0
Client says: sec-ch-ua-platform: "Windows"
Client says: Upgrade-Insecure-Requests: 1
Client says: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Client says: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Client says: Sec-Fetch-Site: none
Client says: Sec-Fetch-Mode: navigate
Client says: Sec-Fetch-User: ?1
Client says: Sec-Fetch-Dest: document
Client says: Accept-Encoding: gzip, deflate, br, zstd
Client says: Accept-Language: zh-CN,zh;q=0.9
Client says: 
WARN:客户端强制断开连接
Client disconnected
New client connected:/[0:0:0:0:0:0:0:1]:55090
Client says: GET / HTTP/1.1
Client says: Host: localhost:8080
Client says: Connection: keep-alive
Client says: Cache-Control: max-age=0
Client says: sec-ch-ua: "Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"
Client says: sec-ch-ua-mobile: ?0
Client says: sec-ch-ua-platform: "Windows"
Client says: Upgrade-Insecure-Requests: 1
Client says: User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36
Client says: Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Client says: Sec-Fetch-Site: none
Client says: Sec-Fetch-Mode: navigate
Client says: Sec-Fetch-User: ?1
Client says: Sec-Fetch-Dest: document
Client says: Accept-Encoding: gzip, deflate, br, zstd
Client says: Accept-Language: zh-CN,zh;q=0.9
Client says: 
WARN:客户端强制断开连接
Client disconnected
New client connected:/[0:0:0:0:0:0:0:1]:55091
Client disconnected
Client disconnected

从上面的clientMessage参数可以获取到请求的数据,包括我们日常开发中常见的url,method,Connection: keep-alive等

2. 自定义Http的servlet

整体设计思路

1.基于当前 SimpleServer 简易 TCP 服务改造,模拟 HTTP 1.0 短连接;
2.自定义 HttpServletRequest:解析客户端 HTTP 请求行、请求头、请求参数;
3.自定义 HttpServletResponse:封装输出流、响应状态码、响应头、响应正文;
4.抽象 HttpServlet 父类,提供 doGet() 模板方法;
5.改造 handleClient 读取原始 TCP 字节,封装请求对象,调用 doGet 输出 HTTP 响应。

2.1. 模拟 HttpServletRequest

package http.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 模拟 HttpServletRequest,解析HTTP请求行、请求头、GET参数
 */
public class HttpServletRequest {

    // 请求行:GET /demo?name=test HTTP/1.1
    private String method;
    private String requestUri;
    private String httpVersion;
    // 纯路径
    private String path;
    // GET查询参数
    private Map<String, String> parameterMap = new HashMap<>();
    // 请求头集合
    private Map<String, String> headerMap = new HashMap<>();
    // 原始输入流
    private BufferedReader reader;

    public HttpServletRequest(BufferedReader reader) throws IOException {
        this.reader = reader;
        parseRequestLine();
        parseHeaders();
    }

    // 解析第一行 请求行 GET /xxx HTTP/1.1
    private void parseRequestLine() throws IOException {
        String line = reader.readLine();
        if (line == null) return;
        String[] parts = line.split(" ");
        this.method = parts[0];
        String uri = parts[1];
        this.httpVersion = parts[2];

        // 拆分路径与GET参数
        if (uri.contains("?")) {
            String[] uriArr = uri.split("\\?");
            this.path = uriArr[0];
            parseQueryParams(uriArr[1]);
        } else {
            this.path = uri;
        }
        this.requestUri = uri;
    }

    // 解析URL拼接的GET参数
    private void parseQueryParams(String queryStr) {
        String[] params = queryStr.split("&");
        for (String param : params) {
            String[] kv = param.split("=");
            if (kv.length == 2) {
                parameterMap.put(kv[0], kv[1]);
            }
        }
    }

    // 解析所有请求头(空行停止)
    private void parseHeaders() throws IOException {
        String line;
        while ((line = reader.readLine()) != null && !line.isEmpty()) {
            String[] header = line.split(":", 2);
            headerMap.put(header[0].trim(), header[1].trim());
        }
    }

    // ========== 对外提供API ==========
    public String getMethod() {
        return method;
    }

    public String getRequestURI() {
        return requestUri;
    }

    public String getPathInfo() {
        return path;
    }

    public String getParameter(String name) {
        return parameterMap.get(name);
    }

    public Map<String, String> getParameterMap() {
        return parameterMap;
    }

    public String getHeader(String name) {
        return headerMap.get(name);
    }
}

2.2 模拟 HttpServletResponse

package http.impl;

import java.io.PrintWriter;

/**
 * 模拟 HttpServletResponse,输出标准HTTP响应报文
 */
public class HttpServletResponse {
    private PrintWriter out;
    private int status = 200;
    private String msg = "OK";
    private StringBuilder body = new StringBuilder();

    public HttpServletResponse(PrintWriter out) {
        this.out = out;
    }

    // 设置响应状态码
    public void setStatus(int code, String message) {
        this.status = code;
        this.msg = message;
    }

    // 向响应体写入内容
    public void write(String content) {
        body.append(content);
    }

    // 一次性输出完整HTTP报文到客户端
    public void flush() {
        // 响应首行 HTTP/1.1 200 OK
        out.printf("HTTP/1.1 %d %s\r\n", status, msg);
        // 基础响应头
        out.println("Content-Type: text/html;charset=utf-8");
        out.println("Server: SimpleJavaMockTomcat");
        out.println("Connection: close");
        out.println(); // 空行分隔头与正文
        // 响应正文
        out.print(body);
        out.flush();
    }
}

2.3 抽象 HttpServlet 模板父类

package http.impl;

public abstract class HttpServlet {
    /**
     * 子类重写此方法处理GET请求
     */
    protected abstract void doGet(HttpServletRequest request, HttpServletResponse response);

    /**
     * 统一分发请求
     */
    public void service(HttpServletRequest request, HttpServletResponse response) {
        String method = request.getMethod();
        if ("GET".equalsIgnoreCase(method)) {
            doGet(request, response);
        } else {
            response.setStatus(405, "Method Not Allowed");
            response.write("仅支持GET请求");
        }
        response.flush();
    }
}

2.4 业务 Servlet 实现类

package http.impl;

public class DemoServlet extends HttpServlet{

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        // 1. 获取路径与GET参数
        String path = request.getPathInfo();
        String name = request.getParameter("name");
        String ua = request.getHeader("User-Agent");

        // 2. 组装HTML响应
        response.write("<h1>模拟GET请求处理成功</h1>");
        response.write("<p>请求路径:" + path + "</p>");
        response.write("<p>GET参数name:" + (name == null ? "无" : name) + "</p>");
        response.write("<p>客户端UA:" + ua + "</p>");
    }
}

2.5 改造SimpleServer.java

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import http.impl.DemoServlet;
import http.impl.HttpServlet;
import http.impl.HttpServletRequest;
import http.impl.HttpServletResponse;

public class SimpleServer {

    //模拟简易Tomcat
    public static void main(String[] args) {
        int port = 8080;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Mock Tomcat 启动,监听端口:" + port);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                // 开启独立线程处理每个客户端HTTP连接
                new Thread(() -> handleClient(clientSocket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleClient(Socket clientSocket) {
        System.out.println("新客户端接入:" + clientSocket.getRemoteSocketAddress());
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            out = new PrintWriter(clientSocket.getOutputStream(), true);

            // 1. 把原始IO封装成模拟Request/Response
            HttpServletRequest request = new HttpServletRequest(in);
            HttpServletResponse response = new HttpServletResponse(out);

            // 2. 创建业务Servlet,分发执行doGet
            HttpServlet servlet = new DemoServlet();
            servlet.service(request, response);

        } catch (SocketException e) {
            String msg = e.getMessage();
            if (msg.contains("中止了一个已建立的连接") || msg.contains("Connection aborted")) {
                System.out.println("WARN:客户端强制断开连接");
            } else {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) in.close();
                if (out != null) out.close();
                if (!clientSocket.isClosed()) {
                    clientSocket.close();
                }
                System.out.println("客户端连接关闭");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.6 测试

1.启动 SimpleServer2.浏览器访问地址:http://127.0.0.1:8080/demo?name=zhangsan
"D:\Program Files\Java\jdk-21\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2026.1\lib\idea_rt.jar=55374" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath D:\project\deepseek\DNTomcat\out\production\DNTomcat SimpleServer
Mock Tomcat 启动,监听端口:8080
新客户端接入:/127.0.0.1:55395
新客户端接入:/127.0.0.1:55396
客户端连接关闭
客户端连接关闭

在这里插入图片描述

更多推荐