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

news2024/12/24 1:05:49

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

  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

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

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

相关文章

华为网络篇 单区域OSPF-32

难度1复杂度1 目录 一、相关原理 二、实验拓扑 三、实验步骤 四、实验过程 总结 一、相关原理 OSPF&#xff08;Open Shortest Path First&#xff09;是一种链路状态路由协议&#xff0c;它是由IETF的OSPF工作组开发的公有协议&#xff0c;所有的厂商都可以使用它。相比静…

算法01 跟左神刷题01

题目一 给定一个有序数组arr&#xff0c;代表坐落在X轴上的点&#xff0c;给定一个正数K&#xff0c;代表绳子的长度 返回绳子最多压中几个点?即使绳子边缘处盖住点也算盖住。 题的理解 贪心也行&#xff0c; 然后比如绳子的最右端到了973 绳子长度为100 而这是个有序数组…

Docker是什么, 为什么这么火

Docker本质 Docker 本质其实是 LXC 之类的增强版&#xff0c;它本身不是容器&#xff0c;而是容器的易用工具。容器是 linux 内核中的技术&#xff0c;Docker 只是把这种技术在使用上简易普及了。Docker 在早期的版本其核心就是 LXC 的二次封装发行版。 Docker 作为容器技术的一…

LLM 回答更加准确的秘密:为检索增强生成(RAG)添加引用源

如何让你的大模型变得更强&#xff1f;如何确定其获取信息来源的准确性&#xff1f; 想要回答这两个问题&#xff0c;就不得不提到今天文章的主角——RAG。RAG&#xff0c;也就是检索增强生成&#xff08;Retrieval-augmented generation) &#xff0c;可以弥补现有 LLM 应用能…

WPF CommunityToolkit.Mvvm

文章目录 前言ToolkitNuget安装简单使用SetProperty&#xff0c;通知更新RealyCommandCanExecute 新功能&#xff0c;代码生成器ObservablePropertyNotifyCanExecuteChangedForRelayCommand其他功能对应关系 NotifyPropertyChangedFor 前言 CommunityToolkit.Mvvm&#xff08;…

“去哪儿旅行”Java工程师内推资格——直入笔试,圆你大厂梦,放心啦,来源可靠,给你的梦想提提速!

岗位信息 Java开发工程师&#xff08;2024&#xff09; 所在地&#xff1a;北京市海淀区 工作职责: 按照需求,负责技术平台或业务支持系统的设计、维护,以及代码开发工作; 根据开发规范编写各种开发文档及项目文档; 协助需求方进行技术调研; 不断提高产品的代码质量,参与…

详解C#-static void Main(string[] args)

目录 简介: 举例: 输出结果:​编辑 总结&#xff1a; 简介: 在C#中static void Main(string[] args)这个句话有什么作用&#xff0c;分别代表什么意思&#xff01;&#xff01; 这句话是入口函数的声明&#xff0c;指定了C#程序的入口点&#xff0c;并定义了一个名为”Mai…

虹科分享 | 温度边缘效应对冻干成品含水量的影响(上)——原理和现象

01 冻干流程简介 生物制药成品的冻干过程通常包括以下三个步骤&#xff1a; 预冻&#xff08;Freezing&#xff09; 在冻干过程中&#xff0c;首先需要将生物制药成品冷冻至非常低的温度&#xff0c;通常在-40℃至-80℃之间。这有助于将水分转变为冰晶&#xff0c;减少冻干过…

低成本高收益,五金店小程序的秘密武器

如今&#xff0c;随着移动互联网的快速发展&#xff0c;小程序成为了许多企业进行线上业务拓展的重要方式之一。对于那些不懂代码的人来说&#xff0c;制作一个小程序可能会让人觉得困难重重。但是&#xff0c;现在&#xff0c;借助乔拓云平台&#xff0c;不懂代码的人也能轻松…

BUUCTF [安洵杯 2019]easy_serialize_php 1 详细讲解

题目来自buuctf&#xff0c;这是一题关于php序列化逃逸的题 1. 题目 题目给出的代码 <?php$function $_GET[f];function filter($img){$filter_arr array(php,flag,php5,php4,fl1g);$filter /.implode(|,$filter_arr)./i;return preg_replace($filter,,$img); }if($_S…

JVM——类文件结构

文章目录 一 概述二 Class 文件结构总结2.1 魔数2.2 Class 文件版本2.3 常量池2.4 访问标志2.5 当前类索引,父类索引与接口索引集合2.6 字段表集合2.7 方法表集合2.8 属性表集合 一 概述 在 Java 中&#xff0c;JVM 可以理解的代码就叫做字节码&#xff08;即扩展名为 .class …

File 类的用法, InputStream和Reader, OutputStream和Writer 的用法

前言 普通的文件长这样&#xff1a; 其实目录也是一种特殊文件&#xff1a; 一、文件前缀知识 &#xff08;一&#xff09;绝对路径和相对路径 以盘符开头的的路径&#xff0c;叫做绝对路径&#xff0c;如&#xff1a;D:\360Downloads\cat.jpg 以.或..开头的路径&#xff0c…

iOS UIAlertController控件

ios 9 以后 UIAlertController取代UIAlertView和UIActionSheet UIAlertControllerStyleAlert和UIAlertControllerStyleActionSheet。 在UIAlertController中添加按钮和关联输入框 UIAlertAction共有三种类型&#xff0c;默认&#xff08;UIAlertActionStyleDefault&#xff0…

网络通信原理计算IP地址都网络号 主机范围 可用个数(第四十四课)

计算192.168.1.1的网络号ID IP 地址中计算=⇒网络ID计算:默认网络位不变 注解位全0 一 IP地址的网络ID 方法一 192.168.1.1 为例 IP地址的网络号ID 11000000.10101000.00000001.00000001 1111111.11111111.11111111.00000000 逻辑与运算 (二进制) = 192.168.1.0 (十进制) …

Crimson:高性能,高扩展的新一代 Ceph OSD

背景 随着物理硬件的不断发展&#xff0c;存储软件所使用的硬件的情况也一直在不断变化。 一方面&#xff0c;内存和 IO 技术一直在快速发展&#xff0c;硬件的性能在极速增加。在最初设计 Ceph 的时候&#xff0c;通常情况下&#xff0c;Ceph 都是被部署到机械硬盘上&#x…

React 全栈体系(二)

第二章 React面向组件编程 一、基本理解和使用 1. 使用React开发者工具调试 2. 效果 2.1 函数式组件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>1_函数式组件</title> </head> &l…

mac垃圾清理软件有哪些

随着使用时间的增加&#xff0c;mac系统会产生一些垃圾文件&#xff0c;影响系统的性能和稳定性。为了保持mac系统的高效&#xff0c;用户需要定期使用mac垃圾清理软件来清理系统缓存、日志、语言包等无用文件。CleanMyMac是一款功能强大的mac垃圾清理软件&#xff0c;它可以帮…

Allegro单位显示设置【mil/mm】

Allegro单位显示设置【mil/mm】 SetupUser PreferencesDisplayElementshowmeasure_altunits选择millimeters即可。 测量命令下就同时显示两个单位了

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集

文章目录 Ⅰ C part1 面向对象编程1 头文件与类的声明1.1 c vs cpp关于数据和函数1.2 头文件与类1.2.1 头文件1.2.2 class的声明1.2.3 模板初识 2 构造函数2.1 inline 函数2.2 访问级别2.3 ctor 构造函数2.3.1 ctor 的写法2.3.2 ctor/函数 重载2.3.3 ctor 放在 private 区 2.4 …

计算机竞赛 python图像检索系统设计与实现

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python图像检索系统设计与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c…