DIY破产版tomcat


我们应该都知道,tomcat是一个web服务器,是servlet的实现容器,负责解析http数据包、封装成respect发送给service( )等等等等…
但是或许很多不知道其底层的实现原理,停留在了功能使用的层面了,对于这个被封装好的“中间人”了解太少了
在这里插入图片描述

今天我们就好好了解一下这个中间人,DIY一把tomcat,看一看他的内在是怎样的

需求分析

首先就是需求分析了,因为我们今天的重点是DIYtomcat,所有该项目的功能就简单一点,做一个简单的计算器就好了
请添加图片描述
请添加图片描述
实现这么一个简单的功能可能对于学过web的xdm来说应该是非常简单的吧,那么今天的目标就是不使用原生的tomcat、service去实现这个功能!

架构分析

设计框架,是实现一个项目最重要的部分,框架设计好了才能减少代码的冗余部分,提高编写的效率
请添加图片描述
tomcat内部其实还有很多组件,但是我们只列出了我们将会涉及到的部分,最特别的应该就是这个socket网络编程部分了,在tomcat内部维护着这么一个组件,在接收到了客户端发送过来的http请求之后,他就会分发一个线程去处理该请求,当然各个线程之间是独立并列的关系,每个线程就会根据客户发送过来的URI去判断是要访问动态资源还是静态资源,然后展开不同的操作。

然后就是实现思路了

思路

首先我会将它分成三个部分来完成实现
请添加图片描述
第一部分:首先,我们得打通客户端与服务端之间的连接,只有打通了连接我们才能开展后续的工作,在这个部分我们得要能在服务端获取到来自客户端的http数据包, 在客户端收到来自服务端的内容

第二部分:就是实现线程的分发了,让客户端每发送一次请求过来就安排一个线程对象去处理他

第三、第四部分:这也是最重要的最难的一部分,就是要实现servlet的功能,包括编写service()、request、respone、doget(),只有实现了这些才能算是一个像样的tomcat,还可以实现区分静态动态页面的功能

第一版

这一版实现的功能是打通客户端、服务器之间的连接,这个其实对于有接触过java基础网络编程部分的xdm应该都可以大概猜到,就是使用流的形式发送,虽然流跟网络编程是两个分开的内容,但是他们也是密不可分的,通过他们两者的结合才能实现网络上的传输。
那么第一步就是要接收到来自客户端的http请求了,因为我们是DIYtomcat嘛,嘿嘿,所有我们也可以占用8080端口,然后就很简单的实现了接收功能
在这里插入图片描述
在这里插入图片描述
然后接下来就是要让游览器接收到来自服务端发送的数据了

因为我们是模仿tomcat实现的,tomcat回应客户端就是以http报文的形式,所有我们可以模仿一下tomcat发送过去的响应头格式
在这里插入图片描述
当然这里直接发送文本内容也是可以的,只不过就是在网页上看不到响应头的内容而已,既然都打算做盗版tomcat了,就模仿得像一点点。。。
这里不是说所有都一定要加上,我就只加上了响应行、MIME信息而已
在这里插入图片描述
还有一点是很重要的,就是响应报文的格式,响应头跟响应体之间,是必须空一行出来的,不信的可以试一试,不空这一行内容是输出不了的,模仿着他的格式我们就写出来了响应头“respHeader”和响应体“resp”
在这里插入图片描述
在这里插入图片描述
好了,到此 第一版本的盗版tomcat已经完成。。。。

第二版

在第二版本,我们主要的实现的功能就是为每次请求都安排一个线程去处理,线程之间的关系是独立并列的,这里也可以使用线程池技术。
这个版本内容也很好实现,只需要创建一个线程对象,每一次发送请求过来,就new一个线程对象就可以实现了,而线程对象要实现的功能就是流的传输,在第一版中已经写过了,复制粘贴就好了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第三版

终于到了这个小小小项目的核心部分了,实现servlet的功能了,首先让我们先想一想,servlet的组成有什么?
首先我们要创建一个servlet对象,就必须得先继承httpservlet类或者实现servlet接口,这个大家都懂,让我们来看看他的继承体系
在这里插入图片描述
从这里我们可以看出来一个servlet的继承体系还是挺复杂的,所以在我们的盗版中就有必要简化一下,因为我们也不是要实现所有的功能,有很多结构是非必要的。
以下就是我们简化的继承体系图
在这里插入图片描述
然后就是还有一个很重要的组成部分就是,request跟respone对象了,不管是使用doget()、dopost()、service()方法都会涉及到他们
每次http发送报文过来以后,他的数据包就会被服务器(tomcat)解析封装成一个request对象,发送给servlet使用,通过这个request对象中,我们可以获取到来客户端发送的信息
然后在生成request对象的同时,也会生成一个respone对象一并发送给servlet,通过这个respone对象
servlet可以将操作完成之后的数据发回给服务器,服务器再重新解析封装成一个http响应报文,发回给客户端
所以说我们也很有必要创建一对request,respone对象以接收发送数据


第一步就是创建myServlet接口了,我们先参考一下原生的servlet是怎么样的
在这里插入图片描述
里面有一些方法参数是用不上的,例如config、getservletconfig( )之类的。。。
所以在我的盗版中是这样的
在这里插入图片描述
只保留了核心的生命周期的部分,其余的舍弃掉,service()中的两个对象也是不用原生的对象,而是使用我们自己的。。。

再下一步就是MyHttpServlet了
也是一样先参考原生的结构
首先是原生的GenericServlet
在这里插入图片描述
再然后就是原生的HttpServlet
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
这里我只列出了一些我们用到的部分
首先从上面的信息的可以得到,这两个类都是被abstract修饰的,所有第一点就是我们的myHttpServlet类也必须是抽象类
然后重点看这个原生的HttpServlet
在这里面声明了我们常用的doget(),dopost()…一系列方法,那么第二点就是我们的myHttpServlet类中也必须声明这几个方法
再然后就是看service(),从里面可以看出来,是通过request对象获取当前对象的method,也是他的方法类型“GET”或者“POST”,同个这个这个字符串来调用对应的方法。。。
噢噢~~~~~~,然后我们就懂了,是通过这个service方法来调用具体的操作方法,那么这个service()就是我们的核心,实现类实现通过调用service(),调用对应的doXXX(),

具体实现 ↓
在这里插入图片描述
然后就到实现myRequest,myRespone对象了
我们就以get方式为例,思考一下实现myRequest需要什么

首先,我们要知道request对象是干嘛的?
它是用于封装来自客户端信息的一个对象,那思路就清晰了,就是通过接收到的流!,获取需要的信息
我们可以看一下在原生tomcat的抓包情况,可以请求头的第一行 ,也就是请求行,得到什么?

  1. 请求方式,也就是get 、post…
  2. 就是工程路径或者说资源路径
  3. 还有就是参数名和参数值

所有,通过这个请求行就可以获取这么多有用的信息了,我们只需要截取需要的部分,分别封装成不同的对象,就可以实现request的部分功能了

在这里插入图片描述

规律

首先,可以发现每一部分都是由空格分开,第一部分就是我们需要的请求方式,然后后面资源路径与参数列表以“ ?” 分隔,参数列表之间以 “ & ”分隔
通过这些发现的规律我们就基本可以实现封装数据的功能了

public class MyRequest {
    private String method;
    private String uri;
    private Map<String,String> argsMap=new HashMap<>();//用于保存参数的容器
    InputStream inputStream=null;

    public MyRequest(InputStream inputStream){//在构造器中解析分离出我们需要的信息
        try {
            this.inputStream=inputStream;

            BufferedReader reader =new BufferedReader(new InputStreamReader(inputStream));

            String respLine = reader.readLine();//获取请求行内容

            String[] strings = respLine.split(" ");

            method=strings[0];//第一部分就是请求方式

            int index = strings[1].indexOf("?");

            if(index==-1){//说明字符串没有包含 ? 代表着没有传参
                uri=strings[1];
            }else{//有参数,分隔
                uri=strings[1].substring(0,index);

                String args=strings[1].substring(index+1);

                if(args.length() != 0){//说明传参列表不为空
                    String[] split = args.split("&");

                    for(String str:split){
                        String[] s = str.split("=");

                        if(s.length==2){//如果分隔开以后数组长度为2 说明 他的形式是 name=123   这样子的  符合要求
                            argsMap.put(s[0],s[1]);
                        }
                    }
                }
            }

            System.out.println(" mehthod = "+method);
            System.out.println(" uri ="+uri);
            System.out.println(argsMap);

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


    public String getParameter(String name){

        if(argsMap.containsKey(name)){
            return argsMap.get(name);
        }else{
            return null;
        }
    }


    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;
    }
}

虽然是又臭又长的第一段 不过我们还是解决了问题

在这里插入图片描述
然后就到了respone了,他的作用是将处理好的数据装好拿给tomcat服务器,他的活相对来说简单的很多,就是只需要拿到一个当前对象的输出流,供对象调用就行了
在这里插入图片描述
也有些兄嘚会说这个这个类创建得有点鸡肋
在现在这种情况下,确实是有点鸡肋,socket创建输出流,然后再放进myRespone对象中,然后servlet对象再把他取出来,中间无缘无故多加了一层嵌套关系,不过他的鸡肋是因为他的业务功能的单一简单,如果需要实现的功能复杂化一些,比如说什么重定向啊,addcookie啊什么的她就不会这样子的啦

这一步也编写好了以后就是调用了
在这里插入图片描述
然后好的
在这里插入图片描述
他又成功了!

不过他现在的弊端是对象的创建已经被写死了,无论你uri访问什么他都是这个加法的功能,如果想实现乘法的运算就得修改代码,这很不可取,所有下个版本就得实现动态的调用,就是真正意义上的servlet了

第四版

在这个版本我需要实现正在意义上的servlet功能了,就是实现动态绑定
首先,也是让我们想一想,原生的servlet是如何实现绑定的?

  • 一个就是在web.xml中配置
    在这里插入图片描述

  • 另外一个就是注解
    在这里插入图片描述

那我们肯定是挑一个简单的,所以挑了web.xml

思路

我们可以根据web.xml,创建2个容器用于存储web.xml 文件中的 servlet部分 和servlet—mapping部分
然后通过读取与匹配,得到对应对象的全类名,然后再通过反射动态地生成对象
原生的web.xml为什么要配置全类名,不就是为了让我们使用反射获取么。。。

请添加图片描述

我们可以使用web.xml配置文件填写对应的配置文件参数,当然重新创建一个文件也是可以的,因为我们不是原生的servlet程序,所有web.xml文件是不识别我们的内容的,需要我们自己识别内容,获取全类名后反射调用,然后ok完工!
好了接下一个问题就是,如何识别web.xml?
说到读取xml文件,那就必须得用dom4j技术了,这里就不介绍dom4j了 ,他是专门为读取xml文件而生~ ,由于我们使用的不是原生的servlet,web.xml是无法识别,所有他会报错,但是我们可以不管他

然后就是实现的代码↓
在这里插入图片描述
我们需要将web.xml放在你指定的目录下面,我放的就是我们项目真实运行目录下面,你也可以换别的工程下的目录,只要能识别到就行

将web.xml的文件放入到指定容器以后呢,就只剩下调用了
思路也很简单,之前myRequest对象有个uri属性还记得么?通过它,跟web-xml中的url-pattern属性对比,如果相同的话,就获取其servlet-name属性值,用该属性值跟另外一个容器中的servlet-name匹配获取对应的全类名,然后通过它使用反射获取生成对象实例,如果不相同,就说明没有配置这个servlet,报404就行了
在这里插入图片描述
还有一件事就是:
可能有点同学不知道为什么调用的是service方法,而不是具体的doget()或dopost()
因为我们没有重写service(),所有当我们调用这个方法的时候,他就会找到父类httpServlet中的service方法
在这里插入图片描述
在这里插入图片描述
因为我们是子类调用的嘛,所有我们就会绑定子类中的方法,什么意思呢?
就是service()不是会调用doget()么,由于我们绑定了子类中的方法,jvm就会优先去子类中查找是否有个名为doget的方法,如果有,他就会直接调用,如果没有,他才会去父类中查找,这就是面向对象中的动态绑定机制

然后我们也很好的完成了
在这里插入图片描述

在这里插入图片描述
然后最后一个功能就是
区分动态静态资源了,这个也很简单,就是识别后缀,比如说一个静态页面不就是以 “.html”结尾的么?
那我们只需要判断是否以这个后缀结尾就ok了
读取方式,也是先获取其路径,然后通过读流的方式读取,然后再输出一遍就可以了
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值