手写tomcat

本文记录了手写tomcat的关键步骤,阅读大约需要10分钟,文末有压缩包可下载验证

1. 模拟Tomcat两个功能

  1. 接收http请求
  2. 返回资源

2. 步骤

  1. 使用Socket接收请求
  2. 将请求信息封装成Request,返回信息封装成Response
  3. 处理静态资源,若找不到则在页面上显示404
  4. 启动时加载web.xml,保存url与servlet的映射关系
  5. 处理动态资源,若找不到则在页面上显示404
  6. 使用多线程优化请求处理

3. 项目结构

image-20201107183458372

3.1 Bootstrap 启动类

package server;

import ...

public class Bootstrap {

    /** 端口 */
    int port = 8080;
    /** servlet容器 */
    Map<String, HttpServlet> servletMap = new HashMap<>();

    /**
     * 启动入口
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.start();
    }

    private void start() throws Exception {
        // 1. 启动时加载web.xml,保存url与servlet的映射关系
        loadServlet();
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("MiniCat start on port:" + port);
        while (true) {
            // 2. 使用Socket接收请求
            Socket socket = serverSocket.accept();
            // 3. 将请求信息封装成Request,返回信息封装成Response
            Request request = new Request(socket.getInputStream());
            Response response = new Response(socket.getOutputStream());
            if (request.getUrl().contains("html")) {
                // 4. 处理静态资源,若找不到则在页面上显示404
                response.outputHtml(request.getUrl());
            } else {
                // 5. 处理动态资源,若找不到则在页面上显示404
                HttpServlet httpServlet = servletMap.get(request.getUrl());
                if (httpServlet == null) {
                    response.output(HttpProtocolUtil.getHttpHeader404());
                } else {
                    httpServlet.service(request, response);
                }
            }

            socket.close();
        }
    }

    /**
     * 加载并解析web.xml,保存url与servlet的映射关系
     */
    private void loadServlet() throws Exception {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element rootElement = document.getRootElement();
        List<Node> servletNodes = rootElement.selectNodes("//servlet");
        for (Node servlet : servletNodes) {
            // <servlet-name>hello</servlet-name>
            String servletName = servlet.selectSingleNode("servlet-name").getStringValue();
            // <servlet-class>server.servlet.HelloServlet</servlet-class>
            String servletClass = servlet.selectSingleNode("servlet-class").getStringValue();

            // 使用Xpath表达式,根据servletName查找url-pattern
            Element servletMapping = (Element) rootElement
            .selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
            String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();

            // 保存映射关系
            servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
        }
    }

}

3.2 Request 解析请求信息

package server.pojo;

import ...

@Data
public class Request {
    /** 请求方式:GET POST */
    private String method;
    /** 资源路径: / 或者 /index.html*/
    private String url;
    /** 请求信息 */
    private InputStream inputStream;

    /**
     * 在构造器中,将请求信息封装成Request
     * @param inputStream
     */
    public Request(InputStream inputStream) throws IOException {
        this.inputStream = inputStream;

        int count = 0;
        while (count == 0) {
            // 获取准备到来的字节数的估计值
            count = inputStream.available();
        }

        // 读进来并缓存在字节数组中
        byte[] bytes = new byte[count];
        inputStream.read(bytes);

        // 获取请求头信息的第一行,解析method和url
        // GET / HTTP/1.1
        String header = new String(bytes);
        String first = header.split("\\n")[0];
        String[] s = first.split(" ");
        this.method = s[0];
        this.url = s[1];
        System.out.println("===>method:" + method + ", url:" + url);
    }

}

3.3 Response 封装响应信息

package server.pojo;

import ...

@Data
public class Response {

    private OutputStream outputStream;

    public Response(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    /**
     * 根据url查找静态资源的绝对路径,并写到response
     * @param path
     */
    public void outputHtml(String path) throws IOException {
        // 获取静态资源的绝对路径
        String absolutePath = StaticResourceUtil.getAbsolutePath(path);

        // 读取静态资源文件
        File file = new File(absolutePath);
        if (file.exists() && file.isFile()) {
            // 读取静态资源文件,输出静态资源
            StaticResourceUtil.outputStaticResource(new FileInputStream(file), outputStream);
        } else {
            // 找不到文件,输出404
            output(HttpProtocolUtil.getHttpHeader404());
        }
    }

    /**
     * 使用输出流输出指定字符串
     * @param content
     */
    public void output(String content) throws IOException {
        outputStream.write(content.getBytes());
    }
}

项目压缩包

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

火车站卖橘子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值