【手写一个Tomcat】SimpleTomcat-01

150 阅读3分钟

在这里插入图片描述

前言

本文实现一个简易Tomcat,遵循【Tomcat】第八篇:150代码手写Tomcat...

实现

http.TomcatRequest

TomcatRequest.java

package com.sample.http;

import java.io.IOException;
import java.io.InputStream;

public class TomcatRequest {

    /**
     * 请求方法 get post delete put
     */
    private String method;

    private String url;

    public TomcatRequest(InputStream in) {
        try {
            // 1.content用来保存InputStream中的http请求信息
            String content = "";
            byte[] buff = new byte[1024];
            int len = 0;
            if ((len = in.read(buff)) > 0) {
                content = new String(buff, 0, len);
            }

            // 2.对http请求信息进行处理,得到Method与Url
            String line = content.split("\\n")[0];
            String[] arr = line.split("\\s");
            this.method = arr[0];
            this.url = arr[1].split("\\?")[0];

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回url
     * @return String
     */
    public String getUrl() {
        return this.url;
    }

    /**
     * 返回请求方法
     * @return String
     */
    public String getMethod() {
        return this.method;
    }
}

http.TomcatResponse

TomcatResponse.java

package com.sample.http;

import java.io.IOException;
import java.io.OutputStream;

public class TomcatResponse {

    private OutputStream out;

    public TomcatResponse(OutputStream out) {
        this.out = out;
    }

    public void write(String s) throws IOException {
        StringBuilder sb = new StringBuilder();
        // 因为写出的内容要被http协议解析,所以要符合http协议规范,有其要求的响应头(主要是状态码和响应格式)
        sb.append("HTTP/1.1 200 OK\n")
                .append("Content-Type: text/html;\n")
                .append("\r\n")
                .append(s);
        // IO流写出
        this.out.write(sb.toString().getBytes());
    }
}

http.TomcatServlet

TomcatServlet.java

package com.sample.http;

import java.io.IOException;

public abstract class TomcatServlet {

    // 这里的request与response都是Tomcat对象创建好然后传进来的
    public void service(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
        if ("GET".equalsIgnoreCase(tomcatRequest.getMethod())) {
            doGet(tomcatRequest, tomcatResponse);
        }
        else {
            doPost(tomcatRequest, tomcatResponse);
        }
    }

    // 这里是模板方法模式,交给子类去具体实现
    protected abstract void doPost(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException;

    protected abstract void doGet(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException;

}

SimpleTomcat

SimpleTomcat.java

package com.sample;

import com.sample.http.TomcatRequest;
import com.sample.http.TomcatResponse;
import com.sample.http.TomcatServlet;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * Tomcat核心类
 */
public class SimpleTomcat {

    private int port = 8080;

    private ServerSocket serverSocket;

    private Map<String, TomcatServlet> servletMapping = new HashMap<>();

    private Properties webxml = new Properties();

    private void init() {
        try {
            // 1.加载web.properties文件
            String WEB_INF = this.getClass().getResource("/").getPath();
            FileInputStream fileInputStream = new FileInputStream(WEB_INF + "web.properties");
            webxml.load(fileInputStream);

            // 2.遍历配置文件,寻找url与servlet映射关系配置
            for (Object o : webxml.keySet()) {
                String key = o.toString();
                // 以url结尾的key就是要映射的路径,下面是两条配置示例:
                // servlet.one.url=/firstServlet.do
                // servlet.one.className=com.yzh.tomcat.servlet.FirstServlet
                if (key.endsWith(".url")) {
                    // 去掉.url就是servlet的name(servlet.one)
                    String servletName = key.replaceAll("\\.url$", "");

                    // 2.1 获取到url(/first.do)
                    String url = webxml.getProperty(key);

                    // 2.2 获取对应servlet全类名(com.yzh.tomcat...FirstServlet),并通过反进行实例化
                    String className = webxml.getProperty(servletName + ".className");
                    // 注:这里是将所有Servlet都强转为TomcatServlet,所以一定要继承TomcatServlet
                    TomcatServlet tomcatServlet = (TomcatServlet) Class.forName(className).newInstance();

                    // 3.将url与servlet实例保存到servletMapping中(单例模式)
                    servletMapping.put(url, tomcatServlet);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 启动tomcat
     */
    public void start() {
        // 1.调用init,目的是得到servletMapping的映射关系
        init();

        try {
            // 2.通过BIO创建socket的服务端,在指定端口开始监听
            serverSocket = new ServerSocket(this.port);
            System.out.println("SimpleTomcat已启动,监听的端口是" + this.port);

            // 3.用一个死循环持续等待并处理用户请求
            while (true) {
                Socket client = serverSocket.accept();
                // process是具体处理请求的逻辑,参数是当前连接的Socket
                process(client);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 具体处理请求
     * 1.创建IO流,并包装成Request与Response
     * 2.获取请求Url,取出对应Servlet进行处理
     */
    private void process(Socket client) throws IOException {
        // 1.获取IO流,并封装成Request与Response
        InputStream in = client.getInputStream();
        OutputStream out = client.getOutputStream();
        // 注:这里要明白,每次请求的Request和Response都是不同的(因为连接时的socket不同),他俩的作用域仅为当前会话
        TomcatRequest request = new TomcatRequest(in);
        TomcatResponse response = new TomcatResponse(out);

        // 获取请求URL,寻找相应Servlet进行处理
        String url = request.getUrl();
        // 2.判断改url是否有对应的Servlet实例
        if (servletMapping.containsKey(url)) {
            // 如果有,调用service方法进行处理
            servletMapping.get(url).service(request, response);
        }
        else {
            // 如果没有,写出404
            response.write("404 - Not Found");
        }

        // 3.关闭本次连接相关资源
        out.flush();
        out.close();
        in.close();
        client.close();
    }

    public static void main(String[] args) {
        new SimpleTomcat().start();
    }
}

servlet.FirstServlet

package com.sample.servlet;

import com.sample.http.TomcatRequest;
import com.sample.http.TomcatResponse;
import com.sample.http.TomcatServlet;

import java.io.IOException;

public class FirstServlet extends TomcatServlet {

    @Override
    protected void doPost(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
        tomcatResponse.write("this is FirstServlet!");
    }

    @Override
    protected void doGet(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
        doPost(tomcatRequest, tomcatResponse);
    }
}

servlet.SecondServlet

SecondServlet.java

package com.sample.servlet;


import com.sample.http.TomcatRequest;
import com.sample.http.TomcatResponse;
import com.sample.http.TomcatServlet;

import java.io.IOException;

public class SecondServlet extends TomcatServlet {

    @Override
    protected void doPost(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
        tomcatResponse.write("Hello world!");
    }

    @Override
    protected void doGet(TomcatRequest tomcatRequest, TomcatResponse tomcatResponse) throws IOException {
        doPost(tomcatRequest, tomcatResponse);
    }
}

配置

web.properties

servlet.one.url=/firstServlet
servlet.one.className=com.sample.servlet.FirstServlet

servlet.two.url=/secondServlet
servlet.two.className=com.sample.servlet.SecondServlet

文件结构

在这里插入图片描述

运行

运行我们的 SimpleTomcat.java 在这里插入图片描述

在浏览器中输入

http://localhost:8080/firstServlet

或者

http://localhost:8080/secondServlet

可以看到

在这里插入图片描述

参考

blog.csdn.net/weixin_4393…