手动实现 Tomcat 底层机制+ 自己设Servlet最终版本V3

news2024/11/27 14:46:40

文章目录

  • 实现任务阶段 3- 处理 Servlet
    • 分析+代码实现
      • ● 分析示意图
      • WyxRequestHandler
      • wyxResponse
      • wyxRequest
      • wyxServlet接口
      • wyxHttpServlet
      • wyxCalServlet
      • WebUtils
      • wyxTomcatV3
        • 代码里面的容器图

实现任务阶段 3- 处理 Servlet

分析+代码实现

● 分析示意图

在这里插入图片描述

WyxRequestHandler

1.这里我们可以对客户端/浏览器进行IO编程/交互

2.新增业务逻辑
(1) 判断uri是什么资源 => 工具方法
(2) 如果是静态资源,就读取该资源,并返回给浏览器 content-type text/html
(3) 因为目前并没有起到tomcat, 不是一个标准的web项目
(4) 把读取的静态资源放到 target/classes/cal.html
过滤,拦截 , 权限等待 => Handler… => 分发

  1. 有了filter机制,可以理解再调用servlet之前,先匹配filter
package com.wyxdu.tomcat.handler;


import com.wyxdu.tomcat.WyxTomcatV3;
import com.wyxdu.tomcat.http.WyxRequest;
import com.wyxdu.tomcat.http.WyxResponse;

import com.wyxdu.tomcat.servlet.WyxHttpServlet;
import com.wyxdu.tomcat.utils.WebUtils;


import java.io.*;
import java.net.Socket;


public class WyxRequestHandler implements Runnable {

    //定义Socket
    private Socket socket = null;

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

    @Override
    public void run() {

        //这里我们可以对客户端/浏览器进行IO编程/交互
        try {
       
            WyxRequest wyxRequest = new WyxRequest(socket.getInputStream());
     

            //这里我们可以同wyxResponse对象,返回数据给浏览器/客户端
            WyxResponse wyxResponse = new WyxResponse(socket.getOutputStream());

     
            //1. 得到 uri => 就是 servletUrlMapping 的 url-pattern
            String uri = wyxRequest.getUri();



            //=====================新增业务逻辑==========
            //(1) 判断uri是什么资源 => 工具方法
            //(2) 如果是静态资源,就读取该资源,并返回给浏览器 content-type text/html
            //(3) 因为目前并没有起到tomcat, 不是一个标准的web项目
            //(4) 把读取的静态资源放到 target/classes/cal.html
            //过滤,拦截 , 权限等待 => Handler.... => 分发
            if(WebUtils.isHtml(uri)) {//就是静态页面
                String content = WebUtils.readHtml(uri.substring(1));
                content = wyxResponse.respHeader + content;
                //得到outputstream , 返回信息(静态页面)给浏览器
                OutputStream outputStream = wyxResponse.getOutputStream();
                outputStream.write(content.getBytes());
                outputStream.flush();
                outputStream.close();
                socket.close();
                return;
            }


            //有了filter机制,可以理解再调用servlet之前,先匹配filter
            //1. 根据request对象封装的uri
            //2. 到 filterUrlMapping 去匹配
            //3. 如果匹配上就调用 filterMapping 对应的filer对象doFilter()
            //4. 如果没有匹配上,就直接走我们后的servlet/jsp/html.

            String servletName = WyxTomcatV3.servletUrlMapping.get(uri);
            if (servletName == null) {
                servletName = "";
            }
            //2. 通过uri->servletName->servlet的实例 , 真正的运行类型是其子类 WyxCalServlet
            WyxHttpServlet wyxHttpServlet =
                    WyxTomcatV3.servletMapping.get(servletName);
            //3. 调用service , 通过OOP的动态绑定机制,调用运行类型的 doGet/doPost
            if (wyxHttpServlet != null) {//得到
                wyxHttpServlet.service(wyxRequest, wyxResponse);
            } else {
                //没有这个servlet , 返回404的提示信息
                String resp = wyxResponse.respHeader + "<h1>404 Not Found</h1>";
                OutputStream outputStream = wyxResponse.getOutputStream();
                outputStream.write(resp.getBytes());
                outputStream.flush();
                outputStream.close();
            }

            
            socket.close();


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //最后一定确保socket要关闭
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

wyxResponse

当我们需要给浏览器返回数据时,可以通过wyxResponse 的输出流完成

public class wyxResponse {

    private OutputStream outputStream = null;

    //写一个http的响应头 => 先死后活
    public static final String respHeader = "HTTP/1.1 200 OK\r\n" +
            "Content-Type: text/html;charset=utf-8\r\n\r\n";

    //在创建 wyxResponse 对象时,传入的outputStream是和Socket关联的
    public wyxResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
    //当我们需要给浏览器返回数据时,可以通过wyxResponse 的输出流完成
    //
    public OutputStream getOutputStream() {
        return outputStream;
    }
}

wyxRequest

  1. wyxRequest 作用是封装http请求的数据 get /WyxCalServlet?num1=10&num2=30
  2. 比如 method(get) 、 uri(/wyxCalServlet) 、 还有参数列表 (num1=10&num2=30)
  3. wyxRequest 作用就等价原生的servlet 中的HttpServletRequest
  4. 一会走代码
  5. 这里考虑的是GET请求
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;

/**
 * 1. wyxRequest 作用是封装http请求的数据
 * get /WyxCalServlet?num1=10&num2=30
 * 2. 比如 method(get) 、 uri(/wyxCalServlet) 、 还有参数列表 (num1=10&num2=30)
 * 3. wyxRequest 作用就等价原生的servlet 中的HttpServletRequest
 * 4. 一会走代码
 * 5. 这里考虑的是GET请求
 */
public class wyxRequest {

    private String method;
    private String uri;
    //存放参数列表 参数名-参数值 => HashMap
    private HashMap<String, String> parametersMapping =
            new HashMap<>();
    private InputStream inputStream = null;


    //构造器=> 对http请求进行封装 => 可以写的代码封装成方法
    //inputStream 是和 对应http请求的socket关联
    public wyxRequest(InputStream inputStream) {
        this.inputStream = inputStream;
        //完成对http请求数据的封装..
        encapHttpRequest();
    }

    /**
     * 将http请求的相关数据,进行封装,然后提供相关的方法,进行获取
     */
    private void encapHttpRequest() {
        System.out.println("wyxRequest init()");
        try {
            //inputstream -> BufferedReader
            BufferedReader bufferedReader =
                    new BufferedReader(new InputStreamReader(inputStream, "utf-8"));

            //读取第一行
            /**
             * GET /wyxCalServlet?num1=10&num2=30 HTTP/1.1
             * Host: localhost:8080
             * User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Fi
             */
            String requestLine = bufferedReader.readLine();
            //GET - /WyxCalServlet?num1=10&num2=30 - HTTP/1.1
            String[] requestLineArr = requestLine.split(" ");
            //得到method
            method = requestLineArr[0];
            //解析得到 /WyxCalServlet
            //1. 先看看uri 有没有参数列表
            int index = requestLineArr[1].indexOf("?");
            if (index == -1) { //说明没有参数列表
                uri = requestLineArr[1];
            } else {
                //[0,index)
                uri = requestLineArr[1].substring(0, index);
                //获取参数列表->parametersMapping
                //parameters => num1=10&num2=30
                String parameters = requestLineArr[1].substring(index + 1);
                //num1=10 , num2=30 .... parametersPair= ["num1=10","num2=30" ]
                String[] parametersPair = parameters.split("&");
                //防止用户提交时 /wyxCalServlet?
                if (null != parametersPair && !"".equals(parametersPair)) {
                    //再次分割 parameterPair = num1=10
                    for (String parameterPair : parametersPair) {
                        //parameterVal ["num1", "10"]
                        String[] parameterVal = parameterPair.split("=");
                        if (parameterVal.length == 2) {
                            //放入到 parametersMapping
                            parametersMapping.put(parameterVal[0], parameterVal[1]);
                        }
                    }
                }
            }
            //这里不能关闭流 inputStream 和 socket关联
            //inputStream.close();

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

    //request对象有一个特别重要方法
    public String getParameter(String name) {
        if (parametersMapping.containsKey(name)) {
            return parametersMapping.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 "wyxRequest{" +
                "method='" + method + '\'' +
                ", uri='" + uri + '\'' +
                ", parametersMapping=" + parametersMapping +
                '}';
    }
}

wyxServlet接口

public interface wyxServlet {

    void init() throws Exception;

    void service(wyxRequest request, wyxResponse response) throws IOException;

    void destroy();
}

wyxHttpServlet

这里我们使用的了模板设计模式
让wyxHttpServlet 子类 wyxCalServlet 实现

public abstract class wyxHttpServlet implements wyxServlet {

    @Override
    public void service(wyxRequest request, wyxResponse response) throws IOException {
        //说明 equalsIgnoreCase 比较字符串内容是相同,不区别大小写
        if("GET".equalsIgnoreCase(request.getMethod())) {
            //这里会有动态绑定
            this.doGet(request,response);
        } else if("POST".equalsIgnoreCase(request.getMethod())) {
            this.doPost(request,response);
        }
    }

    //这里我们使用的了模板设计模式
    //让wyxHttpServlet 子类 wyxCalServlet 实现

    public abstract void doGet(wyxRequest request, wyxResponse response);
    public abstract void doPost(wyxRequest request, wyxResponse response);
}

wyxCalServlet

返回计算结果给浏览器

public class wyxCalServlet extends wyxHttpServlet {
    @Override
    public void doGet(wyxRequest request, wyxResponse response) {
        //java基础的 OOP 的动态绑定机制..
        //写业务代码,完成计算任务
        int num1 = WebUtils.parseInt(request.getParameter("num1"), 0);
        int num2 = WebUtils.parseInt(request.getParameter("num2"), 0);

        int sum = num1 + num2;

        //返回计算结果给浏览器
        //outputStream 和 当前的socket关联
        OutputStream outputStream = response.getOutputStream();
        String respMes = wyxResponse.respHeader
                + "<h1>" + num1 + " + " + num2 + " = " + sum + " wyxTomcatV3 - 反射+xml创建</h1>";
        try {
            outputStream.write(respMes.getBytes());
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doPost(wyxRequest request, wyxResponse response) {
        this.doGet(request, response);
    }

    @Override
    public void init() throws Exception {

    }

    @Override
    public void destroy() {

    }
}

WebUtils

1.将字符串转成数字方法
2.判断uri是不是html文件
3.根据文件名来读取该文件->String

public class WebUtils {

    //将字符串转成数字方法
    public static int parseInt(String strNum, int defaultVal) {
        try {
            return Integer.parseInt(strNum);
        } catch (NumberFormatException e) {
            System.out.println(strNum + " 不能转成数字");
        }
        return defaultVal;
    }

    //判断uri是不是html文件
    public static boolean isHtml(String uri) {

        return uri.endsWith(".html");
    }

    //根据文件名来读取该文件->String
    public static String readHtml(String filename) {
        String path = com.wyxdu.utils.WebUtils.class.getResource("/").getPath();
        StringBuilder stringBuilder = new StringBuilder();

        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(path + filename));
            String buf = "";
            while ((buf = bufferedReader.readLine()) != null) {
                stringBuilder.append(buf);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return stringBuilder.toString();
    }
}

wyxTomcatV3

第3版的Tomcat, 实现通过xml+反射来初始化容器

代码里面的容器图

在这里插入图片描述

package com.wyxdu.tomcat;

import com.wyxdu.tomcat.handler.wyxRequestHandler;
import com.wyxdu.tomcat.servlet.wyxHttpServlet;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.servlet.Filter;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 第3版的Tomcat, 实现通过xml+反射来初始化容器
 */
public class wyxTomcatV3 {

    //1. 存放容器 servletMapping
    // -ConcurrentHashMap
    // -HashMap
    // key            - value
    // ServletName    对应的实例

    public static final ConcurrentHashMap<String, wyxHttpServlet>
            servletMapping = new ConcurrentHashMap<>();


    //2容器 servletUrlMapping
    // -ConcurrentHashMap
    // -HashMap
    // key                    - value
    // url-pattern       ServletName

    public static final ConcurrentHashMap<String, String>
            servletUrlMapping = new ConcurrentHashMap<>();


    //你可以这里理解session, tomcat还维护一个容器
    public static final ConcurrentHashMap<String, HttpSession>
            sessionMapping = new ConcurrentHashMap<>();
    

    //你可以这里理解filter, tomcat还维护了filter的容器
    public static final ConcurrentHashMap<String, String>
            filterUrlMapping = new ConcurrentHashMap<>();

    public static final ConcurrentHashMap<String, Filter>
            filterMapping = new ConcurrentHashMap<>();
    
    public static void main(String[] args) {
        wyxTomcatV3 wyxTomcatV3 = new wyxTomcatV3();
        wyxTomcatV3.init();
        //启动wyxtomcat容器
        wyxTomcatV3.run();
    }


    //启动WyxTomcatV3容器
    public void run() {

        try {
            ServerSocket serverSocket = new ServerSocket(8080);
            System.out.println("=====wyxtomcatv3在8080监听======");
            while (!serverSocket.isClosed()) {
                Socket socket = serverSocket.accept();
                wyxRequestHandler wyxRequestHandler =
                        new wyxRequestHandler(socket);
                new Thread(wyxRequestHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //直接对两个容器进行初始化
    public void init() {
        //读取web.xml => dom4j =>
        //得到web.xml文件的路径 => 拷贝一份.
        String path = wyxTomcatV3.class.getResource("/").getPath();
        //System.out.println("path= " + path);
        //使用dom4j技术完成读取
        SAXReader saxReader = new SAXReader();
        
        try {
            Document document = saxReader.read(new File(path + "web.xml"));
            System.out.println("document= " + document);
            //得到根元素
            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");
                    //使用反射将该servlet实例放入到servletMapping
                    Element servletName = element.element("servlet-name");
                    Element servletClass = element.element("servlet-class");
                    servletMapping.put(servletName.getText(),
                            (wyxHttpServlet) Class.forName(servletClass.getText().trim()).newInstance());
                } else if ("servlet-mapping".equalsIgnoreCase(element.getName())) {
                    //这是一个servlet-mapping
                    //System.out.println("发现 servlet-mapping");

                    Element servletName = element.element("servlet-name");
                    Element urlPatter = element.element("url-pattern");
                    servletUrlMapping.put(urlPatter.getText(), servletName.getText());

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

        //验证,这两个容器是否初始化成功
        System.out.println("servletMapping= " + servletMapping);
        System.out.println("servletUrlMapping= " + servletUrlMapping);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/769781.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

暑期代码每日一练Day3:874. 模拟行走机器人

题目 874. 模拟行走机器人 分析 这道题就是个简单的模拟 主要有两点考察点&#xff1a; 对方向数组的运用 方向数组存储的是各个方向的单位向量&#xff0c;也即&#xff1a; 方向XY向北01向东10向南0-1向西-10 存储在数组中&#xff0c;则是方向数组&#xff1a; in…

vue3后台管理系统封装的普通表格组件

1.普通的表格组件效果 ComtableListR.vue组件 <template><div class"tableBox"><div class"btn-add"><a-space><a-upload v-model:file-list"fileList" v-if"hasImport" name"file" accept&qu…

关于Arduino IDE库文件存放路径问题总结(双版本)

在开发过程中,如果不注意,库文件存放路径很乱,如果在转移系统环境时,容易忘记备份。编译过程中出现多个可用引用包的位置,为了解决这些问题,要明白各文件夹的默认路径在哪,区别在哪,如有了解不对的地方请指正。 IDE安装目录(默认C盘,自定义可以其他盘符下)IDE升级可…

动态规划(四) —— 子序列和子串系列问题总结

前言 通过前面有关动态规划经典问题如背包问题、打家劫舍系列问题和股票投资问题的学习相信小伙伴跟荔枝一样对于动态规划题目有了一定的感觉。接下来再这篇文章中荔枝会继续梳理有关动态规划的经典系列问题——子序列和子串问题&#xff0c;给出解题的分析思路和具体的题解&am…

曲阜师范大学2023年暑期大一新生排位赛 题解

目录 A (1). Sum 详细点击&#xff1a;sum //整除分块 B (2). Sort C (3). String //字符串dp D (4). Factor ​​​​​​​ ​​​​​​​ //素数筛变式 E (5). Tree ​​​​​​​ …

复现Nature图表:GSEA分析及可视化包装函数

这篇帖子主要的目的是写一个转录组GSEA分析和可视化通用的函数。起因是我们想要复现一篇文章的GSEA可视化图片&#xff0c;这个Nature文章GSEA可视化挺好的&#xff1a; image.png &#xff08;reference&#xff1a;B-cell-specific checkpoint molecules that regulate anti…

vscode报警和报错没有颜色

前言 解决方法来源 https://www.zhihu.com/question/506531863 解决步骤 安装IPython conda install IPython打开/anaconda3/envs/mmagic3/lib/python3.8/site-packages&#xff0c;然后创建一个文件&#xff0c;sitecustomize.py&#xff0c;里面写入 import sys frome IP…

Python(二十一)intput()函数的高级使用

❤️ 专栏简介&#xff1a;本专栏记录了我个人从零开始学习Python编程的过程。在这个专栏中&#xff0c;我将分享我在学习Python的过程中的学习笔记、学习路线以及各个知识点。 ☀️ 专栏适用人群 &#xff1a;本专栏适用于希望学习Python编程的初学者和有一定编程基础的人。无…

Kafka消息队列基础入门和实战例子

1、Kafka 1.1 Kafka部署配置 1.1.1 下载Kafka 下载Kafka https://kafka.apache.org/downloads.html https://archive.apache.org/dist/kafka/2.4.1/kafka_2.11-2.4.1.tgz下载Scala-2.11版本 Scala-2.11经典版本解压 直接解压到某个目录&#xff0c;可以一起放在Java相关的…

MathType公式编辑器右边选项变灰

今天在写论文的时候&#xff0c;想要给公式添加编号&#xff0c;发现Word中的MathType好多选项都变灰了&#xff0c;然后查找了一些资料&#xff0c;最终解决&#xff0c;这里记录一下&#xff0c;方便以后查阅。 MathType公式编辑器右边选项变灰 问题描述解决方案禁止MathType…

C++---树形DP---树的中心(每日一道算法2023.7.19)

注意事项&#xff1a; 本题为"树形DP—树的最长路径"的近似题&#xff0c;同时涉及到 单链表模拟邻接表存储图 的操作&#xff0c;建议先理解那篇文章。 题目&#xff1a; 给定一棵树&#xff0c;树中包含 n 个结点&#xff08;编号1~n&#xff09;和 n−1 条无向边…

pandas清洗客户编码异常数据

前言 在不同行业中&#xff0c;我们经常会遇到一个麻烦的问题&#xff1a;数据清洗。尤其是当我们需要处理客户编码异常数据时&#xff0c;这个问题变得尤为重要。想象一下&#xff0c;许多银行都是以客户为单位管理数据的&#xff0c;因此每个客户都有一个独特的编码。在处理…

LiveGBS流媒体平台GB/T28181功能-海康NVR摄像机自带物联网卡摄像头注册GB/T28181国标平台看不到设备的时候如何抓包及排查

海康大华宇视华为等硬件NVR摄像机注册到LiveGBS国标平台看不到设备的时候如何抓包及排查 1、设备注册后查看不到1.1、是否是自带物联网卡的摄像头1.2、关闭萤石云1.3、防火墙排查1.4、端口排查1.5、IP地址排查1.6、设备TCP/IP配置排查1.7、设备多网卡排查1.8、设备接入配置参数…

实战:ELK环境部署并采集springboot项目日志

文章目录 前言技术积累ELK组成及功能框架搭建基础 EIK环境搭建elasticsearch配置相关kibana配置相关logstash配置相关elk目录下增加docker-compose文件查看elk目录文件树编排elk springboot集成logstashpom.xmllogback-spring.xml启动项目logstash采集日志 写在最后 前言 相信…

Java8 stream toMap、groupingBy、mapping的综合应用

文章目录 一、stream toMap、groupingBy、mapping的综合应用1、前提准备①、实体类②、数据准备 2、核心代码&#xff1a;3、运行结果 一、stream toMap、groupingBy、mapping的综合应用 1、前提准备 ①、实体类 package com.cfay.demo;import lombok.AllArgsConstructor; i…

LCD拼接屏、LED显示屏和OLED显示屏的主要区别

我们在生活或工作中经常看到大大小小的显示屏&#xff0c;但很多人却分不清楚这些屏到底属于哪一类&#xff0c;今天sostron与大家一起来分享下关于&#xff1a;LCD拼接屏、LED显示屏、OLED透明屏三者的区别。 LCD拼接屏、LED显示屏和OLED显示屏是不同类型的显示技术&#xff0…

【116个】网络安全测试相关面试真题

1、Burpsuite常用的功能是什么&#xff1f; 2、reverse_tcp和bind_tcp的区别&#xff1f; 3、拿到一个待检测的站或给你一个网站&#xff0c;你觉得应该先做什么&#xff1f; 4、你在渗透测试过程中是如何敏感信息收集的&#xff1f; 5、你平时去哪些网站进行学习、挖漏洞提交到…

这样创建客户帮助中心,效果超好!

创建一个有效的客户帮助中心是为了为客户提供优质的支持和服务。在这个数字化时代&#xff0c;客户期望能够快速找到所需的信息&#xff0c;并得到准确和及时的解答。本文将分享创建有效客户帮助中心的最佳实践&#xff0c;帮助您提供出色的客户体验并提升客户满意度。 1. 了解…

Banana Pi M2 Zero 运行 openHAB 回顾

首先我要透露的是&#xff0c;BPI 的工作人员向我发送了一台免费的 BPi M2 Zero 来执行这些测试。我相信我的评论是公平和公正的&#xff0c;但我想坦率地说明这一事实。 硬件简介 与 Raspberry Pi Zero W 相比&#xff0c;Banana Pi BPI-M2 Zero 具有令人印象深刻的规格。以下…

git进阶操作

一、git 基础概念1. 1.1 三种状态&#xff1a; 工作区&#xff08;unstage&#xff09;——已修改&#xff08;modified&#xff09; 暂存区&#xff08;stage&#xff09;——已暂存&#xff08;staged&#xff09; 对象区——已提交&#xff08;commited&#xff09; 工作…