DIY破产版tomcat

编程入门 行业动态 更新时间:2024-10-11 13:21:29

<a href=https://www.elefans.com/category/jswz/34/1762082.html style=DIY破产版tomcat"/>

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了
读取方式,也是先获取其路径,然后通过读流的方式读取,然后再输出一遍就可以了


更多推荐

DIY破产版tomcat

本文发布于:2024-03-14 14:44:11,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1736701.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:DIY   tomcat

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!