写一个迷你版的Tomcat

写一个迷你版的Tomcat

一、前言 

当我们的Web运行的时候,从浏览器发出的请求,必然首先到达tomcat中,之后由tomcat进行处理,由此要考虑tomcat要进行哪些处理,首先便是提供Socket服务,之后对于请求进行分发,把请求和产生的响应封装成request和response
  (1)提供Socket服务
  (2)封装请求/响应对象
  (3)将不同的请求映射到具体的Servlet处理
image.png
这里我们采用nio实现。

二、代码实现

1)封装请求对象:通过对HTTP协议进行解析,拿到了HTTP请求头的方法和URL。
public class MyRequest {

    private String url;

    private String method;

    public MyRequest(String httpRequest) {
        String httHed = httpRequest.split("\n")[0];
        url = httHed.split("\\s")[1];
        method = httHed.split("\\s")[0];
        System.out.println(this);
    }

    public String getUrl() {
        return url;
    }

    public String getMethod() {
        return method;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public void setMethod(String method) {
        this.method = method;
    }
}
2)封装响应对象:基于HTTP协议的格式进行输出写入。
public class MyResponse {

    private SocketChannel channel;

    public MyResponse(SocketChannel channel) {
        this.channel = channel;
    }

    public void write(String content) {
        try {
            StringBuffer httpResponse = new StringBuffer();
            httpResponse.append("HTTP/1.1 200 OK\n").append("Content-Type: text/html\n")
                .append("\r\n").append("<html><body>").append(content).append("</body></html>");
            ByteBuffer outbuffer = ByteBuffer.wrap(httpResponse.toString().getBytes());
            channel.write(outbuffer);
            channel.close();
        } catch (Exception e) {

        } finally {

        }

    }

}
3)servlet请求处理基类:Tomcat是满足Servlet规范的容器,所以Tomcat需要提供API:doGet/doPost/service。
public abstract class MyServlet {

    public void service(MyRequest myRequest, MyResponse myResponse) {
        if (myRequest.getMethod().equalsIgnoreCase("POST")) {
            doPost(myRequest, myResponse);
        } else if (myRequest.getMethod().equalsIgnoreCase("GET")) {
            doGet(myRequest, myResponse);
        }
    }

    public void doGet(MyRequest myRequest, MyResponse myResponse) {

    }

    public void doPost(MyRequest myRequest, MyResponse myResponse) {

    }

}
4)Servlet实现类:提供1个实现类,用于测试。
public class FirstServlet extends MyServlet {

    @Override
    public void doGet(MyRequest myRequest, MyResponse myResponse) {
        super.doGet(myRequest, myResponse);
    }

    @Override
    public void doPost(MyRequest myRequest, MyResponse myResponse) {
        super.doPost(myRequest, myResponse);
    }

    @Override
    public void service(MyRequest myRequest, MyResponse myResponse) {
        myResponse.write("测试");
    }
}
5)Servlet配置:对比之前在web开发中,会在web.xml中通过和指定哪个URL交给哪个servlet来处理。
public class ServletMappingConfig {
    public static List<ServletMapping> servletMappingList = new ArrayList<>();

    static {
        servletMappingList.add(new ServletMapping("get", "/get", "com.spring.tomcat.FirstServlet"));
    }
}

6)启动类:

tomcat的处理流程:把URL对应处理的Servlet关系形成,解析HTTP协议,封装请求/响应对象,利用反射实例化具体的Servlet进行处理。

public class MyTomcat {

    private Selector selector;
    private Integer port = 8080; // 定义8080端口
    private Map<String, String> urlServletMapping = new HashMap<>(); // 存储url和对应的类

    public MyTomcat(Integer port) throws IOException {
        this.port = port;
        init();
        for (ServletMapping servletMapping : ServletMappingConfig.servletMappingList) {
            urlServletMapping.put(servletMapping.getUrl(), servletMapping.getClazz());
        }
    }

    public void init() throws IOException {
        // 创建一个选择器
        selector = Selector.open();
        // 创建一个ServerSocketChanel
        ServerSocketChannel channel = ServerSocketChannel.open();
        // 设置非阻塞
        channel.configureBlocking(false);
        // 打开一个ServerSocket
        ServerSocket serverSocket = channel.socket();
        // 地址
        InetSocketAddress address = new InetSocketAddress(this.port);
        // 绑定
        serverSocket.bind(address);
        // 注册事件
        channel.register(this.selector, SelectionKey.OP_ACCEPT);

    }

    public void start() throws IOException {
        while (true) {
            this.selector.select();
            Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                if (key.isAcceptable()) {
                    // 这个请求是客户端的连接请求事件
                    accept(key);
                } else if (key.isReadable()) {
                    // 如果这个请求是读事件
                    read(key);
                }
            }
        }
    }

    private void accept(SelectionKey key) throws IOException {
        // 事件中传过来的,key我们把这个通道拿到
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
        SocketChannel channel = serverSocketChannel.accept();
        // 把这个设置为非阻塞
        channel.configureBlocking(false);
        // 注册读事件
        channel.register(selector, SelectionKey.OP_READ);
    }

    private void read(SelectionKey key) throws IOException {
        // 创建一个缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        SocketChannel channel = (SocketChannel)key.channel();
        // 我们把通道的数据填入缓冲区
        channel.read(buffer);
        String request = new String(buffer.array()).trim();
        System.out.println("客户端的请求内容" + request);
        // 把我们的html内容返回给客户端
        MyRequest myRequest = new MyRequest(request);
        MyResponse myResponse = new MyResponse(channel);
        dispatch(myRequest, myResponse);
    }

    // 分发请求
    @SuppressWarnings("unchecked")
    public void dispatch(MyRequest myRequest, MyResponse myResponse) {
        String clazz = urlServletMapping.get(myRequest.getUrl());
        try {
            Class<MyServlet> myServletClass = (Class<MyServlet>)Class.forName(clazz);
            if (myServletClass != null) {
                MyServlet myservlet = myServletClass.newInstance();
                myservlet.service(myRequest, myResponse);
            }

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

    public static void main(String[] args) throws Exception {

        MyTomcat server = new MyTomcat(8080);
        server.start();

    }

}

7)启动,测试

image.png