如何仿写简易tomcat 实现思路+代码详细讲解

仿写之前,我们要搞清楚都要用到哪些技术

  1. 自定义注解,比如Tomcat使用的是@Servlet,我们可以定义一个自己的@MyServlet
  2. 构造请求体和返回体,比如tomcat使用HttpRequest,我们可以自己定义myHttpRequest
  3. java去遍历一个指定目录,然后获取到.java文件,再获取到带有@MyServlet注解的类
  4. 然后将这个注解里的path和这个类本身映射成map
  5. 通过反射去调用该类的方法(doGet、doPost)
  6. 还需要用到socket来监听消息,并且对监听到的消息进行处理

第一步:自定义注解

@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface MyServlet {
    String path() default "";
}

第二步:定义HttpRequest以及HttpResponse、

public class MyHttpRequest {
    //定义一个map,用来存放请求体中的参数,key是参数名称,value是参数值
    public Map<String,String> map = new HashMap<>();

    public String getParameter(String key){
        return map.get(key);
    }
}
public class MyHttpResponse {
    public OutputStream outputStream;

    public static final String responsebody = "HTTP/1.1 200+\r\n" + "Content-Type:text/html+\r\n"
            + "\r\n";

    public MyHttpResponse(OutputStream outputStream) {
        this.outputStream = outputStream;
    }
}

第三步:遍历整个目录,把Java文件放入list中

    private static void func(File file){
        File[] files = file.listFiles();
        String s;
        for (File file1 : files) {
            if (file1.isDirectory()){
                func(file1);
            }
            if (file1.isFile()){
                //取src之后的名字
                s = file1.toString().split("src")[1];
                //去掉src后边的第一个\,得到全类名
                s = s.substring(1);
                //判断是不是以.java结尾的文件
                if (s.length() >=5 && s.substring(s.length() - 5).equals(".java")){
                    //把全类名中的\替换成.
                    s = s.replace('\\','.');
                    //去掉后缀名.java
                    s = s.substring(0,s.length()-5);
                    //把类名加入到list中
                    javaclasses.add(s);
                }

            }
        }
    }

第四步:找出带有Servlet注解的Java文件,并把注解中的path,类对象放入到map中

    public static void getServlet() throws ClassNotFoundException {
        for (int i = 0; i < javaclasses.size(); i++) {
            String path = javaclasses.get(i);
            Class<?> cl = Class.forName(path);
            if (cl.isAnnotationPresent(MyServlet.class)){
                servletMap.put(cl.getAnnotation(MyServlet.class).path(),cl);
            }
        }
    }

第五步:创建socket连接

InetAddress localHost = InetAddress.getLocalHost();
        System.out.println("localhost" + localHost);
        ServerSocket serverSocket = new ServerSocket(8080, 10, localHost);
        System.out.println("等待建立连接");
        Socket server = serverSocket.accept();
        System.out.println("连接已建立");

第六步:定义线程接收报文

        HttpAcceptThread httpAcceptThread = new HttpAcceptThread(server);
        Thread accept = new Thread(httpAcceptThread);
        accept.start();
        accept.join();

HttpAcceptThread类内容如下:

class HttpAcceptThread implements Runnable{
    private Socket socket;
    ArrayList<String> strings = new ArrayList<>();

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

    @Override
    public void run() {
        System.out.println("开始接收http");
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String s;
            while ((s = reader.readLine()).length() != 0){
                try {
                    strings.add(s);
                    System.out.println(s);
                } catch (Exception e){
                    System.out.println("接收Http进程结束");
                    break;
                }
            }
            System.out.println("接收http进程结束");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}

第七步:处理httprequest,也就是通过反射去调用doGet和doPost方法

这一步有些复杂,尤其是对url切割时,但我给每一步都加了注释,方便理解

             GET /address1?a=111&b=222

   private static void requestHttp(Socket socket,String http) throws IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        //GET /address1?a=111&b=222(拿获取到的这个url举例)
        //先通过空格判断是GET还是POST
        String requestStyle = http.split(" ")[0];
        if (requestStyle.equals("GET")){
            //如果是GET,取空格后面部分,即/address1?a=111&b=222
            String httpPathAndParameter = http.split(" ")[1];
            //定义httpPath
            String httpPath;
            //创建httpRequest对象
            MyHttpRequest myHttpRequest = new MyHttpRequest();
            //通过索引位置判断url里边有没有带?
            if (httpPathAndParameter.indexOf("?") != -1){
                //如果有,由于有个/,因此我们要先拿到address1?a=111&b=222这部分
                httpPath = httpPathAndParameter.substring(1);
                //获取问号前面部分,即address1,\\作为转义字符使用
                httpPath = httpPath.split("\\?")[0];
                System.out.println(httpPath);

                //获取问号后面部分的所有参数
                String parameterString = httpPathAndParameter.split("\\?")[1];
                //使用&分开
                String[] parameters = parameterString.split("&");
                for (int i = 0; i < parameters.length; i++) {
                    //把参数及其值仿佛request的map中
                    myHttpRequest.map.put(parameters[i].split("=")[0],parameters[i].split("=")[1]);
                }
            } else {
                //如果不存在?,也就说明不存在参数,我们只需要获取httpPath
                httpPath = httpPathAndParameter.substring(1);
                System.out.println(httpPath);
            }

            //创建HttpResponse对象
            OutputStream outputStream = socket.getOutputStream();
            MyHttpResponse myHttpResponse = new MyHttpResponse(outputStream);

            //反射调用doGet
            Class servletClass = servletMap.get(httpPath);
            Method doGet = servletClass.getMethod("doGet", MyHttpRequest.class, MyHttpResponse.class);
            doGet.invoke(servletClass.newInstance(),myHttpRequest,myHttpResponse);
        } else {
            //如果不是Get请求,也按照同样的步骤,先取出/address1
            String httpPath = http.split(" ")[1];
            //去掉/,只留下address1
            httpPath = httpPath.substring(1);
            System.out.println(httpPath);

            MyHttpRequest myHttpRequest = new MyHttpRequest();
            OutputStream outputStream = socket.getOutputStream();
            MyHttpResponse myHttpResponse = new MyHttpResponse(outputStream);
            //根据httpPath取出类信息
            Class servletClass = servletMap.get(httpPath);
            //获取doPost方法
            Method doPost = servletClass.getMethod("doPost", MyHttpRequest.class, MyHttpResponse.class);
            //调用doPost方法
            doPost.invoke(servletClass.newInstance(),myHttpRequest,myHttpResponse);
        }
    }

最后一步:把上面这些方法整合起来,在主方法中调用,同时定义好全局变量

public class MyTomcat {
    //用于存放Java类的全类名
    public static ArrayList<String> javaclasses = new ArrayList<>();

    //用于存放Servlet的类对象,其中key是Servlet的url,value是servlet的类对象
    public static HashMap<String,Class> servletMap = new HashMap<>();

    public static void main(String[] args) throws ClassNotFoundException, IOException, InterruptedException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
        String inputPath = "D:\\JavaProject\\practice\\src\\tomcat";
        File file = new File(inputPath);
        //获取.java后缀文件,并获取全类名
        func(file);
        System.out.println(javaclasses);
        //获取带有servlet注解的类对象,并放到map中。
        getServlet();
        System.out.println(servletMap);

        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println("localhost" + localHost);
        ServerSocket serverSocket = new ServerSocket(8080, 10, localHost);

        System.out.println("等待建立连接");
        Socket server = serverSocket.accept();
        System.out.println("连接已建立");

        //定义线程接收http报文
        HttpAcceptThread httpAcceptThread = new HttpAcceptThread(server);
        Thread accept = new Thread(httpAcceptThread);
        accept.start();
        accept.join();

        //处理请求
        requestHttp(server,httpAcceptThread.strings.get(0));
    }

然后就可以进行测试了,在测试类上方加上我们已经定义好的@MyServlet注解

@MyServlet(path = "address1")
public class Servlet1 {

    public void doGet(MyHttpRequest request, MyHttpResponse response) throws IOException {
        System.out.println("address1 GET响应:");
        System.out.println("a=" + request.getParameter("a"));
        System.out.println("\n响应的http如下:");
        String resp = MyHttpResponse.responsebody + "<!DOCTYPE html>\n" +
                "<html>\n" +
                "<head>\n" +
                "    <meta charset=\"utf-8\" />\n" +
                "</head>\n" +
                "<body>\n" +
                " \n" +
                "    <form name=\"my_form\" method=\"POST\">\n" +
                "        <input type=\"button\" value=\"按下\" onclick=\"alert('你按下了按钮')\">\n" +
                "    </form>\n" +
                " \n" +
                "</body>\n" +
                "</html>";
        System.out.println(resp);
        response.outputStream.write(resp.getBytes());
        response.outputStream.flush();
        response.outputStream.close();
    }

    public void doPost(MyHttpRequest request, MyHttpResponse response) throws IOException {
        System.out.println("\n响应的http如下:");
        String resp = MyHttpResponse.responsebody +
                "{\"sorry\":\"we only respond to method GET now\"},\r\n" +
                "";
        System.out.println(resp);
        response.outputStream.write(resp.getBytes());
        response.outputStream.flush();
        response.outputStream.close();
    }
}

然后启动项目

 可以看到本机ip地址,然后通过浏览器地址栏访问

 这样就实现了一个简单的tomcat

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Matlab工具NIFTI包的基本功能函数

Matlab工具NIFTI包的基本功能函数 Nifti 格式最初是为神经影像学发明的。神经影像信息学技术计划&#xff08;NIFTI&#xff09;将 NIfTI 格式预设为 ANALYZE7.5 格式的替代品。它最初的应用领域是神经影像&#xff0c;但是也被用在其他领域。这种格式的主要特点就是它包含两个…

Docker容器:docker基础概述、docker安装、docker网络

文章目录 一.docker容器概述1.什么是容器2. docker与虚拟机的区别2.1 docker虚拟化产品有哪些及其对比2.2 Docker与虚拟机的区别 3.Docker容器的使用场景4.Docker容器的优点5.Docker 的底层运行原理6.namespace的六项隔离7.Docker核心概念 二.Docker安装 及管理1.安装 Docker1.…

【Unity造轮子】制作一个简单的2d抓勾效果(类似蜘蛛侠的技能)

文章目录 前言开始1. 实现简单的抓勾效果2. 高阶钩爪效果 源码参考完结 前言 欢迎阅读本文&#xff0c;本文将向您介绍如何使用Unity游戏引擎来实现一个简单而有趣的2D抓勾效果&#xff0c;类似于蜘蛛侠的独特能力。抓勾效果是许多动作游戏和平台游戏中的常见元素&#xff0c;…

zookeeper案例

目录 案例一&#xff1a;服务器动态上下线 服务端&#xff1a; &#xff08;1&#xff09;先获取zookeeper连接 &#xff08;2&#xff09;注册服务器到zookeeper集群&#xff1a; &#xff08;3&#xff09;业务逻辑&#xff08;睡眠&#xff09;&#xff1a; 服务端代码…

区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归时间序列区间预测

区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归时间序列区间预测 目录 区间预测 | MATLAB实现QRBiLSTM双向长短期记忆神经网络分位数回归时间序列区间预测效果一览基本介绍模型描述程序设计参考资料 效果一览 基本介绍 区间预测 | MATLAB实现QRBiLSTM双向长短…

SpringBoot案例 调用第三方接口传输数据

一、前言 最近再写调用三方接口传输数据的项目&#xff0c;这篇博客记录项目完成的过程&#xff0c;方便后续再碰到类似的项目可以快速上手 项目结构&#xff1a; 二、编码 这里主要介绍HttpClient发送POST请求工具类和定时器的使用&#xff0c;mvc三层架构编码不做探究 pom.x…

dom靶场

靶场下载地址&#xff1a; https://www.vulnhub.com/entry/domdom-1,328/ 一、信息收集 获取主机ip nmap -sP 192.168.16.0/24netdiscover -r 192.168.16.0/24端口版本获取 nmap -sV -sC -A -p 1-65535 192.168.16.209开放端口只有80 目录扫描 这里扫描php后缀的文件 g…

设计模式之责任链模式【Java实现】

责任链&#xff08;Chain of Resposibility&#xff09; 模式 概念 责任链&#xff08;chain of Resposibility&#xff09; 模式&#xff1a;为了避免请求发送者与多个请求处理者耦合在一起&#xff0c;于是将所有请求的处理者 通过前一对象记住其下一个对象的引用而连成一条…

Oracle 使用 CONNECT_BY_ROOT 解锁层次结构洞察:在 SQL 中导航数据关系

CONNECT_BY_ROOT 是一个在 Oracle 数据库中使用的特殊函数&#xff0c;它通常用于在层次查询中获取根节点的值。在使用 CONNECT BY 子句进行层次查询时&#xff0c;通过 CONNECT_BY_ROOT 函数&#xff0c;你可以在每一行中获取根节点的值&#xff0c;而不仅仅是当前行的值。 假…

打印出二进制的奇数位和偶数位

void print(int a) {int i0;printf("奇数位&#xff1a;");for(i30;i>0;i-2){printf("%d ",(a>>i)&1);}printf("\n");printf("偶数位&#xff1a;");for(i31;i>1;i-2){printf("%d ",(a>>i)&1);} …

Kotlin runBlocking launch多个协程读写mutableListOf时序

Kotlin runBlocking launch多个协程读写mutableListOf时序 import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlockingfun main(args: Array<String>) {var lists mutableListOf<String>()runBlocking {launch {r…

【boost网络库从青铜到王者】第五篇:asio网络编程中的同步读写的客户端和服务器示例

文章目录 1、简介2、客户端设计3、服务器设计3.1、session函数3.2、StartListen函数3、总体设计 4、效果测试5、遇到的问题5.1、服务器遇到的问题5.1.1、不用显示调用bind绑定和listen监听函数5.1.2、出现 Error occured!Error code : 10009 .Message: 提供的文件句柄无效。 [s…

09- DMA(DirectMemoryAccess直接存储器访问)

DMA 09 、DMA(DirectMemoryAccess直接存储器访问)DMA配置流程 09 、DMA(DirectMemoryAccess直接存储器访问) DMA配置流程 dma.c文件 main.c文件 详见《stm32中文参考手册》表57。

基于php驾校驾驶理论考试模拟系统

驾校驾驶理论考试模拟系统&#xff0c;是基于php编程语言&#xff0c;mysql数据库进行开发&#xff0c;本系统分为用户和管理员两个角色&#xff0c;其中用户可以注册登陆系统&#xff0c;查看考试规则&#xff0c;进行驾照考试&#xff0c;查看考试得分&#xff0c;考试错题&a…

hdu8-Congruences(中国剩余定理)

Problem - 7363 (hdu.edu.cn) 参考&#xff1a;2023杭电暑假多校8 题解 3 5 7 10 | JorbanS_JorbanS的博客-CSDN博客 题解&#xff1a;&#xff08;中国剩余定理 增量法&#xff09; 注意验证和特判&#xff0c;此题中 pi 两两互质&#xff0c;可用CRT和增量法&#xff0c;当…

设计模式之门面模式(Facade)的C++实现

1、门面模式提出 在组件的开发过程中&#xff0c;某些接口之间的依赖是比较紧密的&#xff0c;如果某个接口发生变化&#xff0c;其他的接口也会跟着发生变化&#xff0c;这样的代码违背了代码的设计原则。门面设计模式是在外部客户程序和系统程序之间添加了一层中间接口&…

Android上架商城 隐私政策需要网页 没有怎么办

Android开发的项目上架商城的时候会需要你填写url&#xff0c;但其实并不需要真的去发布一个网站 使用腾讯文档新建文档 填写隐私政策 点击生成网页 再将网址填写即可 下面我找到的一个隐私政策文档供大家参考 将XXXX应用一键替换为自己的应用 将XXXXXX公司一键替换为公司 …

Docker容器与虚拟化技术:Docker镜像创建、Dockerfile实例

目录 一、理论 1.Docker镜像的创建方法 2.Docker镜像结构的分层 3.Dockerfile 案例 4.构建Systemctl镜像&#xff08;基于SSH镜像&#xff09; 5.构建Tomcat 镜像 6.构建Mysql镜像 二、实验 1.Docker镜像的创建 2. Dockerfile 案例 3.构建Systemctl镜像&#xff08;…

web后端解决跨域问题

目录 什么是跨域问题 为什么限制访问 解决 什么是跨域问题 域是指从一个域名的网页去请求另一个域名的资源。比如从www.baidu.com 页面去请求 www.google.com 的资源。但是一般情况下不能这么做&#xff0c;它是由浏览器的同源策略造成的&#xff0c;是浏览器对js施加的安全…

[oneAPI] 手写数字识别-卷积

[oneAPI] 手写数字识别 手写数字识别参数与包加载数据模型训练过程结果 oneAPI 比赛&#xff1a;https://marketing.csdn.net/p/f3e44fbfe46c465f4d9d6c23e38e0517 Intel DevCloud for oneAPI&#xff1a;https://devcloud.intel.com/oneapi/get_started/aiAnalyticsToolkitSam…