手动实现Tomcat底层机制

手动实现Tomcat底层机制_第1张图片

1. 创建maven-web项目

第一步

高版本
手动实现Tomcat底层机制_第2张图片
低版本
手动实现Tomcat底层机制_第3张图片
手动实现Tomcat底层机制_第4张图片
手动实现Tomcat底层机制_第5张图片

第二步
手动实现Tomcat底层机制_第6张图片
创建完成
手动实现Tomcat底层机制_第7张图片

1.1 配置阿里maven镜像

  1. 把D:\Program Files\IntelliJ IDEA 2022.3.2\plugins\maven\lib\maven3\conf里的settings.xml复制到C:\Users\97896.m2
    手动实现Tomcat底层机制_第8张图片
  2. 修改C:\Users\97896.m2下的settings.xml
    手动实现Tomcat底层机制_第9张图片
<mirror>
      <id>maven-default-http-blockerid>
      <mirrorOf>external:http:*mirrorOf>
      <name>Pseudo repository to mirror external repositories initially using HTTP.name>
      <url>http://0.0.0.0/url>
      <blocked>trueblocked>
mirror>
  1. 配置IDEA
    手动实现Tomcat底层机制_第10张图片
    pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0modelVersion>
  <groupId>com.zzwgroupId>
  <artifactId>zzwtomcatartifactId>
  <packaging>warpackaging>
  <version>1.0-SNAPSHOTversion>
  <name>zzwtomcat Maven Webappname>
  <url>http://maven.apache.orgurl>
  <dependencies>
    <dependency>
      <groupId>junitgroupId>
      <artifactId>junitartifactId>
      <version>3.8.1version>
      <scope>testscope>
    dependency>
    
    
    <dependency>
      <groupId>javax.servletgroupId>
      <artifactId>javax.servlet-apiartifactId>
      <version>3.0.1version>
      <scope>providedscope>
    dependency>
  dependencies>
  <build>
    <finalName>zzwtomcatfinalName>
  build>
project>

1.2 新建Servlet项目

  1. 目录结构
    手动实现Tomcat底层机制_第11张图片
    手动实现Tomcat底层机制_第12张图片
    手动实现Tomcat底层机制_第13张图片
  2. Tomcat
    手动实现Tomcat底层机制_第14张图片
    手动实现Tomcat底层机制_第15张图片
  3. web.xml
    手动实现Tomcat底层机制_第16张图片
  4. 前端页面
    手动实现Tomcat底层机制_第17张图片
  5. Servlet
    手动实现Tomcat底层机制_第18张图片
    字符串拼接简易方法
    手动实现Tomcat底层机制_第19张图片
  1. 工具类
    手动实现Tomcat底层机制_第20张图片

2. Tomcat整体架构分析

Tomcat有三种运行模式(BIO, NIO, APR), 采用BIO线程模型来模拟Tomcat如何接收客户端请求, 解析请求, 调用Servlet, 并返回结果的机制流程
手动实现Tomcat底层机制_第21张图片

项目整体目录结构
手动实现Tomcat底层机制_第22张图片

2.1 基于socket开发服务端

Content-Type: text/html;charset=gbk 给浏览器响应时设置成gbk

/**
 * @author 赵志伟
 * @version 1.0
 * 这是第一个版本的tomcat, 可以完成接收浏览器的请求, 并返回信息
 */
@SuppressWarnings({"all"})
public class ZzwTomcatVersion1 {
    public static void main(String[] args) throws IOException {
        //1.在服务端监听8080端口
        ServerSocket serverSocket = new ServerSocket(8080);
        while (!serverSocket.isClosed()) {
            System.out.println("服务端Tomcat在 8080端口 等待连接");
            //如果有连接, 就创建一个socket, 这个socket是服务端和客户端的连接通道
            Socket socket = serverSocket.accept();

            System.out.println("接收浏览器发送的数据");
            InputStream inputStream = socket.getInputStream();
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            String message = "";
            while ((message = bufferedReader.readLine()) != null) {
                //判断长度是否为0
                if (message.length() == 0) {
                    break;
                }
                System.out.println(message);
            }

            //我们的Tomcat以 http协议方式 回送数据给浏览器
            OutputStream outputStream = socket.getOutputStream();
            //构建一个http响应的消息头
            // \r\n代表换行
            // 响应头和响应体之间有个空行
            String responseHeader = "HTTP/1.1 200\r\n" +
                    "Content-Type: text/html;charset=gbk\r\n\r\n";
            String response = responseHeader + "你好, 世界 521";
            System.out.println("\ntomcat响应给浏览器的数据\n" + response);
            outputStream.write(response.getBytes());//因为是字节输出流, 所以要按照字节的方式返回

            //关闭流
            outputStream.flush();
            outputStream.close();
            bufferedReader.close();
            socket.close();
        }
    }
}

2.2 BIO线程模型

手动实现Tomcat底层机制_第23张图片
线程

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwRequestHandler 是一个线程对象
 * 用来处理 http请求
 */
@SuppressWarnings({"all"})
public class ZzwRequestHandler implements Runnable {
    private Socket socket;

    public ZzwRequestHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        //对客户端和浏览器进行IO操作
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = socket.getInputStream();
            BufferedReader bufferedReader//inputStream->bufferedReader 方便按行读取
                    = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            System.out.println("当前线程=" + Thread.currentThread().getId());
            System.out.println("==============Tomcat Version2 接收到的数据如下==============");
            String message = "";
            while ((message = bufferedReader.readLine()) != null) {
                if (message.length() == 0) {
                    break;
                }
                System.out.println(message);
            }
            //构建一个http响应头
            //响应头和响应体之间有两个换行 \r\n\r\n
            String responseHeader = "HTTP/1.1 200\r\n" +
                    "Content-Type: text/html;charset=gbk\r\n\r\n";
            String response = responseHeader + "

你好,世界

"
; System.out.println("==============Tomcat Version2 返回的数据如下=============="); System.out.println(response); //得到输出流,将数据封装成 http响应格式 返回给浏览器/客户端 outputStream = socket.getOutputStream(); outputStream.write(response.getBytes());//这个方法把字符串转成字节数组 socket.close();//一定要确保socket关闭 } catch (IOException e) { throw new RuntimeException(e); } finally { try { outputStream.flush(); outputStream.close(); inputStream.close(); if (socket != null) { socket.close(); } } catch (IOException e) { throw new RuntimeException(e); } } } }
/**
 * @author 赵志伟
 * @version 1.0
 * 这是第二个版本的tomcat, 可以调用线程
 */
@SuppressWarnings({"all"})
public class ZzwTomcatVersion2 {
    public static void main(String[] args) throws IOException {
        //1.在服务端监听8080端口
        ServerSocket serverSocket = new ServerSocket(8080);
        //只有servletSocket没有关闭, 就一直等待 浏览器/客户端 连接
        while (!serverSocket.isClosed()) {
            System.out.println("服务端Tomcat version2 在 8080端口 等待连接");
            //如果有连接, 就创建一个socket, 这个socket是服务端和客户端的连接通道
            Socket socket = serverSocket.accept();

            //不能直接调用run方法, 要调用start
            ZzwRequestHandler zzwRequestHandler = new ZzwRequestHandler(socket);
            new Thread(zzwRequestHandler).start();
        }
    }
}

2.3 处理Servlet

模仿Servlet规范
手动实现Tomcat底层机制_第24张图片
手动实现Tomcat底层机制_第25张图片

  1. ZzwRequest对象
/**
 * @author 赵志伟
 * @version 1.0
 * 1.ZzwRequest作用 封装http请求的数据
 * get /zzwCalServlet?num1=12&num2=21
 * 2.比如 请求方法method(get/post), uri(/zzwCalServlet), 参数(num1=12&num2=21)
 * 3.ZzwRequest等价于原生Servlet中的 HttpServletRequest
 * 4.这里只考虑get请求
 */
@SuppressWarnings({"all"})
public class ZzwRequest {

    private String method;
    private String uri;
    //存放参数列表 参数名-参数值 => 数据结构HashMap
    private HashMap<String, String> argsMapping = new HashMap<String, String>();

    //inputStream 是和对应http请求的socket关联
    public ZzwRequest(InputStream inputStream) {
        init(inputStream);
    }

    public void init(InputStream inputStream) {
        //inputStream->bufferedReader
        BufferedReader bufferedReader = null;
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
            /**读取第一行(读取请求行)
             * GET /calServlet?num1=-87&num2=89 HTTP/1.1
             */
            String line1 = bufferedReader.readLine();
            String[] line1s = line1.split(" ");
            //获取method
            method = line1s[0];
            //获取uri
            int index = line1s[1].indexOf("?");
            if (index == -1) {//说没后面没有参数列表
                uri = line1s[1];
            } else {//这里有参数情况
                uri = line1s[1].substring(0, index);
                //args=>num1=-87&num2=89
                String args = line1s[1].substring(index + 1);//直接截取到最后
                //argsPair => ["num1=-87","num2=89"]
                String[] argsPair = args.split("&");
                for (String argPair : argsPair) {
                    String[] argVal = argPair.split("=");
                    if (argVal.length == 2) {
                        //放入到argsMapping
                        argsMapping.put(argVal[0], argVal[1]);
                    }
                }
            }
            //这里inputStream和socket关联, 不能在这里关闭
            //inputStream.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public String getParameter(String name) {
        if (argsMapping.containsKey(name)) {
            return argsMapping.get(name);
        } else {
            return "";
        }
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUri() {
        return uri;
    }

    public void setUri(String uri) {
        this.uri = uri;
    }

    @Override
    public String toString() {
        return "ZzwRequest{" +
                "method='" + method + '\'' +
                ", uri='" + uri + '\'' +
                ", argsMapping=" + argsMapping +
                '}';
    }
}
  1. ZzwResponse对象
/**
 * @author 赵志伟
 * @version 1.0
 * 1.ZzwResponse对象 可以封装OutputStream(和socket关联)
 * 2.可以通过ZzwResponse对象 返回HTTP响应给客户端
 * 3.ZzwResponse对象的作用等价于原生的Servlet的HttpServletResponse
 */
@SuppressWarnings({"all"})
public class ZzwResponse {
    private OutputStream outputStream;
    //设置一个http响应头
    private static final String responseHeader = "HTTP/1.1 200\r\n" +
            "Content-Type: text/html;charset=gbk\r\n\r\n";
    private String response;

    public ZzwResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    public OutputStream getOutputStream() {
        return outputStream;
    }

    public void setOutputStream(OutputStream outputStream) {
        this.outputStream = outputStream;
    }

    public String getResponseHeader() {
        return responseHeader;
    }

    public String getResponse() {
        return response;
    }

    public void setResponse(String response) {
        this.response = responseHeader + response;
    }
}
  1. ZzwRequestHandler改进
public class ZzwRequestHandler implements Runnable {
    private Socket socket;
    public ZzwRequestHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        //对客户端和浏览器进行IO操作
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = socket.getInputStream();
            ZzwRequest zzwRequest = new ZzwRequest(inputStream);
            String num1 = zzwRequest.getParameter("num1");
            String num2 = zzwRequest.getParameter("num2");
            System.out.println("num1=" + num1);
            System.out.println("num2=" + num2);
            System.out.println("zzwRequest=" + zzwRequest);

            ZzwResponse zzwResponse = new ZzwResponse(socket.getOutputStream());
            zzwResponse.setResponse("

刀剑神域

"
); outputStream = zzwResponse.getOutputStream(); outputStream.write(zzwResponse.getResponse().getBytes()); socket.close();//一定要确保socket关闭 } catch (IOException e) { throw new RuntimeException(e); } finally { try { outputStream.flush(); outputStream.close(); inputStream.close(); if (socket != null) { socket.close(); } } catch (IOException e) { throw new RuntimeException(e); } } } }
  1. ZzwServlet接口
/**
 * @author 赵志伟
 * @version 1.0
 * 搭建结构, 由实现类写内容
 */
public interface ZzwServlet {
    void init() throws Exception;

    void service(ZzwRequest request, ZzwResponse response) throws IOException;

    void destroy();
}
  1. ZzwHttpServlet类
/**
 * @author 赵志伟
 * @version 1.0
 */
public abstract class ZzwHttpServlet implements ZzwServlet {
    public void service(ZzwRequest request, ZzwResponse response) throws IOException {
        //equalsIgnoreCase 比较字符串内容并忽略大小写
        if (request.getMethod().equalsIgnoreCase("GET")) {
            this.doGET(request, response);
        } else if (request.getMethod().equalsIgnoreCase("POST")) {
            this.doPost(request, response);
        }
    }
    
    //这里是模板设计模式,让 ZzwHttpServlet的子类来实现
    public abstract void doGET(ZzwRequest request, ZzwResponse response);

    public abstract void doPost(ZzwRequest request, ZzwResponse response);
}
  1. ZzwCalServlet实现类
public class ZzwCalServlet extends ZzwHttpServlet {

    public void doGET(ZzwRequest request, ZzwResponse response) {
        doPost(request, response);
    }

    public void doPost(ZzwRequest request, ZzwResponse response) {
        String num1 = request.getParameter("num1");
        String num2 = request.getParameter("num2");
        int sum = WebUtils.parseInt(num1, 0) + WebUtils.parseInt(num2, 0);
        try {
            OutputStream outputStream = response.getOutputStream();
            response.setResponse("

" + num1 + "+" + num2 + "=" + sum + " ZzwTomcatVersion3

"
); outputStream.write(response.getResponse().getBytes()); } catch (IOException e) { throw new RuntimeException(e); } } public void init() throws Exception { } public void destroy() { } }
  1. ZzwRequestHandler改进2
/**
 * @author 赵志伟
 * @version 1.0
 * ZzwRequestHandler 是一个线程对象
 * 用来处理 http请求
 */
@SuppressWarnings({"all"})
public class ZzwRequestHandler implements Runnable {
    private Socket socket;

    public ZzwRequestHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        //对客户端和浏览器进行IO操作
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();

            ZzwRequest zzwRequest = new ZzwRequest(inputStream);
            ZzwResponse zzwResponse = new ZzwResponse(outputStream);

            //创建ZzwCalServlet对象
            ZzwCalServlet zzwCalServlet = new ZzwCalServlet();
            zzwCalServlet.doGET(zzwRequest, zzwResponse);

            socket.close();//一定要确保socket关闭
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                outputStream.flush();
                outputStream.close();
                inputStream.close();
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2.4 容器实现

手动实现Tomcat底层机制_第26张图片
手动实现Tomcat底层机制_第27张图片
因为我们的Servlet是自己设计的, web.xml检查报红, 直接忽略, 同时要在target/classes目录手动拷贝一份web.xml(平时是自动拷贝)
手动实现Tomcat底层机制_第28张图片
如果想要取消报红, 那么
手动实现Tomcat底层机制_第29张图片

shortcuts: ctrl+b 定位到声明的位置
手动实现Tomcat底层机制_第30张图片

  1. ZzwTomcatVersion3
/**
 * @author 赵志伟
 * @version 1.0
 * 第3版Tomcat, 实现通过xml+反射 初始化容器
 */
@SuppressWarnings({"all"})
public class ZzwTomcatVersion3 {
    /*
        容器 servletMapping
         - ConcurrentHashMap
         - HashMap
               key        -       value
           ServletName          Servlet实例
     */
    //因为ZzwHttpServlet是所有业务Servlet的父类, 所以这里可以存放子类Servlet的对象
    public static final ConcurrentHashMap<String, ZzwHttpServlet> servletMapping
            = new ConcurrentHashMap<String, ZzwHttpServlet>();
    /*
        容器 servletMapping
         - ConcurrentHashMap
         - HashMap
               key       -      value
           url-pattern       ServletName
     */
    public static final ConcurrentHashMap<String, String> servletUriMapping
            = new ConcurrentHashMap<String, String>();

    //直接对两个容器进行初始化
    public void init() {
        //读取web.xml文件 => dom4j
        // 得到web.xml文件[拷贝一份]的路径 => 定位到target/classes
        String path = ZzwTomcatVersion3.class.getResource("/").getPath();
        //使用dom4j完成xml文件的提取
        // 获取解析器
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(new File(path + "web.xml"));// 加不加/都行
            System.out.println(document);
            // 获取rootElement
            Element rootElement = document.getRootElement();
            List<Element> elements = rootElement.elements();
            // 遍历元素并过滤出 servlet servlet-mapping
            for (Element element : elements) {
                if ("servlet".equalsIgnoreCase(element.getName())) {
                    //如果这是一个servlet配置
                    //System.out.println("servlet\n" + element);
                    String servletName = element.element("servlet-name").getText();
                    String servletClass = element.element("servlet-class").getText();
                    //使用反射将该servlet实例放入到servletMapping集合
                    servletMapping.put(servletName, (ZzwHttpServlet) Class.forName(servletClass).newInstance());

                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    //如果这是一个servlet-mapping配置
                    //System.out.println("servlet-mapping\n" + element);
                    Element urlPattern = element.element("url-pattern");
                    Element serlvetName = element.element("servlet-name");
                    servletUriMapping.put(urlPattern.getText(), serlvetName.getText());
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        System.out.println(servletMapping);
        System.out.println(servletUriMapping);
    }

    public static void main(String[] args) {
        //String path = ZzwTomcatVersion3.class.getResource("/").getPath();
        //System.out.println(path);
        ZzwTomcatVersion3 zzwTomcatVersion3 = new ZzwTomcatVersion3();
        zzwTomcatVersion3.init();
        //启动zzwTomcatVersion3容器
        zzwTomcatVersion3.run();
    }

    启动ZzwTomcatVersion3容器, 这只是一个普通方法
    public void run() {
        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            while (!serverSocket.isClosed()) {
                System.out.println("服务端Tomcat version3 在 8080端口 等待连接");
                Socket socket = serverSocket.accept();
                ZzwRequestHandler zzwRequestHandler = new ZzwRequestHandler(socket);
                new Thread(zzwRequestHandler).start();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
  1. ZzwRequestHandler改进3
/**
 * @author 赵志伟
 * @version 1.0
 * ZzwRequestHandler 是一个线程对象
 * 用来处理 http请求
 */
@SuppressWarnings({"all"})
public class ZzwRequestHandler implements Runnable {
    private Socket socket;

    public ZzwRequestHandler(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            ZzwRequest zzwRequest = new ZzwRequest(socket.getInputStream());//socket关闭后,这些流也就没有了
            ZzwResponse zzwResponse = new ZzwResponse(socket.getOutputStream());

            //1.得到uri => servletUriMapping的urlPattern
            String uri = zzwRequest.getUri();
            String servletName = ZzwTomcatVersion3.servletUriMapping.get(uri);
            //2.uri->得到servletName->得到servlet实例, 其真正的运行类型是其子类 ZzwCalServlet
            //  细节:这里的servletName可能是空, ConcurrentHashMap的get(空)会报错, HashMap的get(空)不会报错
            //  解决方案一: 换成HashMap
            //  解决方案二: 如果是空换成空串
            servletName = (servletName == null) ? "" : servletName;
            ZzwHttpServlet zzwHttpServlet = ZzwTomcatVersion3.servletMapping.get(servletName);
            //3.调用service方法, 通过OOP的动态绑定机制 调用到真正运行类型的doGet或者doPost
            if (zzwHttpServlet != null) {
                zzwHttpServlet.service(zzwRequest, zzwResponse);
            } else {
                //请求的地址不存在, 返回404
                zzwResponse.setResponse("

404 Not Found!

"
); OutputStream outputStream = zzwResponse.getOutputStream(); outputStream.write(zzwResponse.getResponse().getBytes()); outputStream.flush(); outputStream.close(); } socket.close();//一定要确保socket关闭 } catch (IOException e) { throw new RuntimeException(e); } finally { try { if (socket != null) { socket.close(); } } catch (IOException e) { throw new RuntimeException(e); } } } }

2.5 访问静态页面

在ZzwTomcatHandler类的try代码块的中上位置加入以下代码
手动实现Tomcat底层机制_第31张图片

if (!WebUtils.isExist(uri.substring(1))) {
                OutputStream outputStream = zzwResponse.getOutputStream();
                zzwResponse.setResponseBody("404 Not Found!");
                outputStream.write(zzwResponse.getResponse().getBytes());
                outputStream.flush();
                outputStream.close();
                socket.close();
                return;
            }
if (WebUtils.isHtml(uri)) {
                String html = WebUtils.readHtml(uri.substring(1));
                OutputStream outputStream = zzwResponse.getOutputStream();
                zzwResponse.setResponseBody(html);
                outputStream.write(zzwResponse.getResponse().getBytes());
                outputStream.flush();
                outputStream.close();
                socket.close();
                return;
            }

在WebUtils工具类中增加以下代码

public static boolean isExist(String fileName) {
        String path = WebUtils.class.getResource("/").getPath();
        File file = new File(path + fileName);
        return file.exists();
    }

public static boolean isHtml(String uri) {
        return uri.endsWith(".html");
    }

    //读取该网页
    public static String readHtml(String htmlName) {
        String path = WebUtils.class.getResource("/").getPath();
        StringBuffer stringBuffer = new StringBuffer();
        String line = "";
        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(path + htmlName));
            while ((line = bufferedReader.readLine()) != null) {
                stringBuffer.append(line);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return stringBuffer.toString();
    }
```

你可能感兴趣的:(JavaWeb,tomcat,java,intellij-idea)