第四章:实现 Servlet 容器的基本功能-MiniTomcat系列

daicy
发布于 2024-11-11 / 8 阅读
0
0

第四章:实现 Servlet 容器的基本功能-MiniTomcat系列

上一章内容:第三章:实现连接器(Connector)组件-MiniTomcat系列

在这一部分,我们将实现一个基础的 Servlet 容器,从而使 MiniTomcat 能够处理动态请求。Servlet 容器的核心是管理 Servlet 生命周期,并在 HTTP 请求到达时调用合适的 Servlet 处理逻辑。

4.1 功能目标

  • 实现 HttpServletRequestHttpServletResponse:封装 HTTP 请求和响应数据。

  • 请求路径映射:根据请求路径找到对应的 Servlet 并调用其 service() 方法处理请求。

4.2 代码结构

以下是 MiniTomcat 的更新后代码结构,我们在 com.daicy.minitomcat 包中添加了 HttpServletRequestImplHttpServletResponseImplServletProcessorHelloServletCustomServletOutputStream等类。

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 实现 HttpServletRequestHttpServletResponse

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 容器的基础功能,我们可以学到:

  1. 请求与响应封装:通过实现 HttpServletRequestHttpServletResponse,掌握了封装和传递请求与响应数据的技巧。

这一实现为后续的 Servlet 映射、配置文件支持等功能奠定了基础。


评论