手把手系列—手写tomcat

今天一起徒手创建了一个精简版的tomcat,相信大家对tomcat运行原理会有更加深入的理解

前置知识

  1. java注解、反射

  2. java网络编程

  3. servlet基础

回顾tomcat

tomcat的概念

在这里插入图片描述
Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。对于一个初学者来说,可以这样认为,当在一台机器上配置好Apache 服务器,可利用它响应(下的一个应用)页面的访问请求。实际上Tomcat是Apache 服务器的扩展,但运行时它是独立运行的,所以当你运行tomcat 时,它实际上作为一个与Apache 独立的进程单独运行的。诀窍是,当配置正确时,Apache 为HTML页面服务,而Tomcat 实际上运行JSP 页面和Servlet。

tomcat正常的接收了浏览器发送的请求
tomcat根据请求映射到响应的servlet

请求原始信息如下:

GET /aaa/xxx HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: JSESSIONID=6E593EFE70D7DE906671505FDBAEEAAC; Hm_lvt_d214947968792b839fd669a4decaaffc=1603700593,1603779032; Idea-661643a5=d5722ef7-628d-418c-960f-6f353393ba3b; cookie_lang=1
Upgrade-Insecure-Requests: 1
Cache-Control: max-age=0

熟悉过apache的Tomcat之后,下面自己尝试一下

自建xml版本tomcatv1.0

思路分析
在这里插入图片描述
根据上面的思路,需要设计一下几个java类

  • tomcat启动类
  • 请求实体类
  • 响应实体类
  • servlet抽象类
  • servlet的具体实现类

1. 创建maven项目,引入相关jar

<dependencies>
        <!-- 引入hutool工具,解析xml文件-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.4</version>
        </dependency>
        <!-- 引入反射jar包-->
        <dependency>
            <groupId>org.reflections</groupId>
            <artifactId>reflections</artifactId>
            <version>0.9.11</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
    </dependencies>

2. 创建请求类

package com.aaa.tomcat;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.InputStream;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/9/29 19:47
 * @description : 自创请求类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RequestAAA {
    private String method;
    private String url;
    private InputStream inputStream;

    public RequestAAA(InputStream inputStream) throws Exception {
        byte[] buf = new byte[1024];
        //定义一个请求字符串
        String requestStr = "";
        //处理inputStream,获取请求方式和请求地址
        int count = 0;
        if ((count = inputStream.read(buf)) > 0) {
            requestStr = new String(buf, 0, count);
        }
        /**
         * GET /aaa/xxx HTTP/1.1
         * Host: localhost:8080
         */
        //获取第一行
        String head = requestStr.split("\n")[0];
        this.method = head.split("\\s")[0];
        this.url = head.split("\\s")[1];
    }
}

3. 创建响应类

package com.aaa.tomcat;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

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

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/9/29 19:47
 * @description : 自创相应类
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseAAA {
    private OutputStream outputStream;

    /**
     * @param context
     * @return void
     * @create by: Teacher陈
     * @description:将内容写回浏览器
     * @create time: 2021/1/20 10:37
     */
    public void write(String context) throws IOException {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("HTTP/1.1 200\r\n");
        stringBuffer.append("Content-Type: text/html\r\n");
        stringBuffer.append("\r\n");
        stringBuffer.append("<html>");
        stringBuffer.append("<body>");
        stringBuffer.append(context);
        stringBuffer.append("</body>");
        stringBuffer.append("</html>");
        outputStream.write(stringBuffer.toString().getBytes());
        outputStream.flush();
        outputStream.close();
    }
}

4. 创建servlet映射类

package com.aaa.tomcat;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/9/29 19:47
 * @description :
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ServletMappingAAA {
    /**
     * 对应web.xml中的servlet url-pattern
     */
    private String url;
    /**
     * 对应web.xml中的servlet-class
     */
    private String clazz;
}

5. 创建servlet抽象类

package com.aaa.tomcat;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/9/29 19:49
 * @description : 我自己的servlet
 */
public abstract class ServletAAA {
    /**
     * 处理get请求
     *
     * @param req
     * @param resp
     */
    public abstract void doGet(RequestAAA req, ResponseAAA resp);

    /**
     * 处理post请求
     *
     * @param req
     * @param resp
     */
    public abstract void doPost(RequestAAA req, ResponseAAA resp);

    public void service(RequestAAA req, ResponseAAA resp) {
        if ("GET".equalsIgnoreCase(req.getMethod())) {
            doGet(req, resp);
        } else if ("POST".equalsIgnoreCase(req.getMethod())) {
            doPost(req, resp);
        }
    }
}

6. 创建servlet实现类

package com.aaa.tomcat;

import java.io.IOException;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/9/29 19:57
 * @description :
 */
public class HelloServlet extends ServletAAA{
    @Override
    public void doGet(RequestAAA req, ResponseAAA resp) {
        this.doPost(req,resp);
    }

    @Override
    public void doPost(RequestAAA req, ResponseAAA resp) {
        System.out.println("HelloServlet is called");
        try {
            resp.write("I Love You");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

7. 创建tomcat类

package com.aaa.tomcat;

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;

/**
 * @author :Teacher陈
 * @date :Created in 2021/1/20 11:00
 * @description:AAA的tomcat
 * @modified By:
 * @version: 1
 */
public class TomcatAAA {
    /**
     * 1:通过serverSoket启动一个监听程序,监听8080端口
     * 2:socket对象可以获取inputstream请求字节流,还可以获取响应outputStream
     * 3:从请求中解析http报文,获取请求方式和请求地址
     * 4:根据请求地址获取servlet类,然后根据请求方式执行servlet中对应的逻辑处理方法
     * 5:构建响应对象,响应给浏览器
     */
    private int port = 9090;
    /**
     * 定义一个servlet映射集合
     */
    private Map<String, String> urlServletMap = new HashMap();

    public TomcatAAA(int port) {
        this.port = port;
    }

    public TomcatAAA() {
    }

    /**
     * @return
     * @create by: Teacher陈
     * @description: 启动tomcat
     * @create time: 2021/1/20 11:04
     */
    public void start() {
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
            serverSocket = new ServerSocket(this.port);
            System.out.println("tomcat is running");
            while (true) {
                //等待客户端请求
                socket = serverSocket.accept();
                 //获取客户端发送的信息
                InputStream inputStream = socket.getInputStream();
                //响应客户端的信息
                OutputStream outputStream = socket.getOutputStream();
                //构建请求和响应
                RequestAAA requestAAA = new RequestAAA(inputStream);
                ResponseAAA responseAAA = new ResponseAAA(outputStream);
                //获取servlet
                ServletMappingAAA servletMappingAAA = new ServletMappingAAA("/xxx", "com.aaa.tomcat.HelloServlet");
                Class<ServletAAA> servletAAAClass = (Class<ServletAAA>) Class.forName(servletMappingAAA.getClazz());
                ServletAAA servletAAA = servletAAAClass.newInstance();
                servletAAA.service(requestAAA, responseAAA);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

8. 测试

运行main方法在浏览器中访问
在这里插入图片描述
只能访问一个servlet,而且是硬编码,不灵活

创建tomcatv1.1类,支持读取web.xml中单个servlet

resources目录下添加web.xml文件

<web-app>
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.aaa.tomcat.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/xxx</url-pattern>
    </servlet-mapping>
</web-app>

修改TomcatAAA文件

在tomcat启动的时候加载所有的url和servlet的映射关系

package com.aaa.tomcat;

import cn.hutool.core.util.XmlUtil;
import org.w3c.dom.Document;

import javax.xml.xpath.XPathConstants;
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;

/**
 * @author :Teacher陈
 * @date :Created in 2021/1/20 11:00
 * @description:AAA的tomcat
 * @modified By:
 * @version: 1
 */
public class TomcatAAA {
    /**
     * 1:通过serverSoket启动一个监听程序,监听8080端口
     * 2:socket对象可以获取inputstream请求字节流,还可以获取响应outputStream
     * 3:从请求中解析http报文,获取请求方式和请求地址
     * 4:根据请求地址获取servlet类,然后根据请求方式执行servlet中对应的逻辑处理方法
     * 5:构建响应对象,响应给浏览器
     */
    private int port = 9090;
    /**
     * 定义一个servlet映射集合
     */
    private Map<String, String> urlServletMap = new HashMap();

    public TomcatAAA(int port) {
        this.port = port;
    }

    public TomcatAAA() {
    }

    /**
     * @author : 尚腾飞(838449693@qq.com)
     * @version : 1.0
     * @createTime : 2022/9/29 22:55
     * @description :
     */
    public void start() {
        //在tomcat启动的时候加载所有的url和servlet的映射关系
        Document docResult= XmlUtil.readXML("web.xml");
        Object clazz = XmlUtil.getByXPath("//web-app/servlet/servlet-class", docResult, XPathConstants.STRING);
        Object url = XmlUtil.getByXPath("//web-app/servlet-mapping/url-pattern", docResult, XPathConstants.STRING);
        urlServletMap.put(url.toString(),clazz.toString());
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
            serverSocket = new ServerSocket(this.port);
            System.out.println("tomcat is running");
            while (true) {
                //等待客户端请求
                socket = serverSocket.accept();
                 //获取客户端发送的信息
                InputStream inputStream = socket.getInputStream();
                //响应客户端的信息
                OutputStream outputStream = socket.getOutputStream();
                //构建请求和响应
                RequestAAA requestAAA = new RequestAAA(inputStream);
                ResponseAAA responseAAA = new ResponseAAA(outputStream);
                //获取servlet
                for (Map.Entry<String, String> entry : urlServletMap.entrySet()) {
                    if (entry.getKey().equals(requestAAA.getUrl())){
                        Class<ServletAAA> servletAAAClass = (Class<ServletAAA>) Class.forName(entry.getValue());
                        ServletAAA servletAAA = servletAAAClass.newInstance();
                        servletAAA.service(requestAAA, responseAAA);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                assert socket != null;
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

创建tomcatV1.2,读取web.xml中多个servlet

上面的案例可以读取单个servlet配置信息,真实的web.xml中包含多个servlet配置。

web.xml文件

<web-app>
    <servlet>
        <servlet-name>hello</servlet-name>
        <servlet-class>com.aaa.tomcat.HelloServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>hello</servlet-name>
        <url-pattern>/xxx</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>test</servlet-name>
        <servlet-class>com.aaa.tomcat.TestServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>test</servlet-name>
        <url-pattern>/yyy</url-pattern>
    </servlet-mapping>
</web-app>

创建读取web.xml的工具类

package com.aaa.tomcat;

import cn.hutool.core.util.XmlUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import java.util.*;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/9/29 23:02
 * @description :
 */
public class MyXmlUtil {
    /**
     * 读取web.xml文件
     * @return Map<String, String>
     */
    public static Map<String, String> loadWebXml() {
        //加载web.xml
        Document docResult = XmlUtil.readXML("web.xml");
        //将Document转换成Element
        Element element = docResult.getDocumentElement();
        //按照元素名称servlet查找所有的元素
        List<Element> elementList = XmlUtil.getElements(element, "servlet");
        //key servletName value clazz
        Map<String, String> servletMap = new HashMap();
        //遍历servlet元素
        for (Element elementServlet : elementList) {
            //获取单个servlet元素的所有孩子
            NodeList childNodes = elementServlet.getChildNodes();
            List<String> tempList = new ArrayList<String>();
            //遍历所有孩子节点
            for (int i = 0; i < childNodes.getLength(); i++) {
                //去除多余的特殊字符和空格
                String textContent = childNodes.item(i).getTextContent().trim().replace("\\n", "")
                        .replace("\\t", "");
                if (textContent != "" && textContent.length() > 0) {
                    tempList.add(textContent);
                }
            }
            //key servletName value clazz 封装map集合
            servletMap.put(tempList.get(0), tempList.get(1));
        }
        //key servletName value url
        Map<String, String> servletMappingMap = new HashMap();
        List<Element> elementListMapping = XmlUtil.getElements(element, "servlet-mapping");
        for (Element elementMapping : elementListMapping) {
            NodeList childNodes = elementMapping.getChildNodes();
            List<String> tempList = new ArrayList<String>();
            for (int i = 0; i < childNodes.getLength(); i++) {
                String textContent = childNodes.item(i).getTextContent().trim().replace("\\n", "")
                        .replace("\\t", "");
                if (textContent != "" && textContent.length() > 0) {
                    tempList.add(textContent);
                }
            }
            servletMappingMap.put(tempList.get(0), tempList.get(1));
        }
        //合并两个map
        Set<String> keySet = servletMappingMap.keySet();
        Map<String, String> finalMap = new HashMap();
        for (String s : keySet) {
            finalMap.put(servletMappingMap.get(s), servletMap.get(s));
        }
        System.out.println(finalMap.toString());
        return finalMap;
    }
}

tomcat启动类

package com.aaa.tomcat;

import cn.hutool.core.util.XmlUtil;
import org.w3c.dom.Document;

import javax.xml.xpath.XPathConstants;
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;

/**
 * @author :Teacher陈
 * @date :Created in 2021/1/20 11:00
 * @description:AAA的tomcat
 * @modified By:
 * @version: 1
 */
public class TomcatAAA {
    /**
     * 1:通过serverSoket启动一个监听程序,监听8080端口
     * 2:socket对象可以获取inputstream请求字节流,还可以获取响应outputStream
     * 3:从请求中解析http报文,获取请求方式和请求地址
     * 4:根据请求地址获取servlet类,然后根据请求方式执行servlet中对应的逻辑处理方法
     * 5:构建响应对象,响应给浏览器
     */
    private int port = 9090;
    /**
     * 定义一个servlet映射集合
     */
    private Map<String, String> urlServletMap = new HashMap();

    public TomcatAAA(int port) {
        this.port = port;
    }

    public TomcatAAA() {
    }

    /**
     * @author : 尚腾飞(838449693@qq.com)
     * @version : 1.0
     * @createTime : 2022/9/29 22:55
     * @description :
     */
    public void start() {
        //在tomcat启动的时候加载所有的url和servlet的映射关系
        urlServletMap = MyXmlUtil.loadWebXml();
        ServerSocket serverSocket = null;
        Socket socket = null;
        try {
            serverSocket = new ServerSocket(this.port);
            System.out.println("tomcat is running");
            while (true) {
                //等待客户端请求
                socket = serverSocket.accept();
                 //获取客户端发送的信息
                InputStream inputStream = socket.getInputStream();
                //响应客户端的信息
                OutputStream outputStream = socket.getOutputStream();
                //构建请求和响应
                RequestAAA requestAAA = new RequestAAA(inputStream);
                ResponseAAA responseAAA = new ResponseAAA(outputStream);
                //获取servlet
                for (Map.Entry<String, String> entry : urlServletMap.entrySet()) {
                    if (entry.getKey().equals(requestAAA.getUrl())){
                        Class<ServletAAA> servletAAAClass = (Class<ServletAAA>) Class.forName(entry.getValue());
                        ServletAAA servletAAA = servletAAAClass.newInstance();
                        servletAAA.service(requestAAA, responseAAA);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                assert socket != null;
                socket.close();
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

这时HelloServlet和TestServlet两个servlet都可以访问

创建注解版本tomcatV2.0

1. 删除web.xml中的配置

2. 自建注解版本tomcatV2.0

创建servlet注解
package com.aaa.tomcat;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author 尚腾飞(838449693@qq.com)
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyWebServlet {
    /**
     * 需要拦截的url,可以是多个
     * @return
     */
    String[] urlPatterns() default {};
}
创建Servlet
package com.aaa.tomcat;

import java.io.IOException;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/9/29 19:57
 * @description :
 */
@MyWebServlet(urlPatterns = "/aaa")
public class AnnServlet extends ServletAAA {
    @Override
    public void doGet(RequestAAA req, ResponseAAA resp) {
        this.doPost(req, resp);
    }

    @Override
    public void doPost(RequestAAA req, ResponseAAA resp) {
        System.out.println("AnnServlet is called");
        try {
            System.out.println("response is running ");
            resp.write(" I miss you ");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package com.aaa.servlet;

import com.aaa.tomcat.MyWebServlet;
import com.aaa.tomcat.RequestAAA;
import com.aaa.tomcat.ResponseAAA;
import com.aaa.tomcat.ServletAAA;

import java.io.IOException;

/**
 * @author : 尚腾飞(838449693@qq.com)
 * @version : 1.0
 * @createTime : 2022/9/29 19:57
 * @description :
 */
@MyWebServlet(urlPatterns = "/bbb")
public class Ann2Servlet extends ServletAAA {
    @Override
    public void doGet(RequestAAA req, ResponseAAA resp) {
        this.doPost(req, resp);
    }

    @Override
    public void doPost(RequestAAA req, ResponseAAA resp) {
        System.out.println("AnnServlet is called");
        try {
            System.out.println("response is running ");
            resp.write(" I miss you sister");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
扫描并解析注解

在MyXmlUtil类中添加方法

	/**
     * 扫描所有的sevlet注解
     * @param packageName
     * @return Map<String, String>
     */
    public static Map<String, String> loadServletAnn(String packageName) {
        if(packageName==null|| "".equals(packageName)){
            packageName="com.aaa.servlet";
        }
        Reflections reflections= new Reflections(packageName);
        Set<Class<?>> classSet = reflections.getTypesAnnotatedWith(MyWebServlet.class);
        Map<String,String> map = new HashMap<String, String>();
        for (Class<?> aClass : classSet) {
            MyWebServlet annotation = aClass.getAnnotation(MyWebServlet.class);
            map.put(annotation.urlPatterns()[0],aClass.getName());
        }
        return map;
    }

tomcat启动类中运用loadServletAnn方法扫描servlet包下的两个带MyWebServlet注解的类,启动main方法

 urlServletMap = MyXmlUtil.loadServletAnn("com.aaa.servlet");
浏览器测试

在这里插入图片描述
在这里插入图片描述
六不六?

代码
https://gitee.com/c90005471/tomcat-aaa
头条
喜欢的话,可以加个关注
https://www.toutiao.com/article/6919772102732268039/?log_from=9960c1f8066e6_1664411254396
手写tomcat视频
视频链接:https://pan.baidu.com/s/1CU-n8qe3jPZNzeqtse1ORQ
提取码:32tj

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

打乒乓球只会抽

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

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

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

打赏作者

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

抵扣说明:

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

余额充值