type
status
date
slug
summary
tags
category
icon
password
Java手写Tomcat和Servlet
本文将介绍如何实现一个简易版的 Tomcat 服务器和 Servlet 容器。我们将从 Tomcat 的基本工作机制开始,逐步构建出一个基本的 Web 服务器框架。
因为是模拟tomcat嘛,所以先介绍一下真正的tomcat机制
我们自己配置Tomcat,在web.xml 文件里面肯定是要配置<servlet>和<servlet-mapping>
在一次http请求打过来的时候,tomcat会读取uri,然后判断容器里面是不是有这个servlet实例,它会用这个uri去一个tomcat维护的一个Map<name,servlet实例>里面找,如果找不到name,就会通过配置的name找到servlet-class全类名,通过反射创建实例,然后调用init()初始化方法。如果不是第一次http请求,那么在map里面肯定能找到这个name,可以获取servlet实例,调用它的service()方法。
接下来开始写自己的 Tomcat
打通数据通道
在服务端监听指定端口,如果浏览器/客户端连接该端口,则建立连接,返回socket对象
socket表示服务端和客户端/浏览器间的连接,通过Socket可以得到InputStream和OutputStream流对象,而通过流可以进行数据的接收和发送。消息的接收没有什么问题,主要是消息的返回需要设置响应头和响应体,http响应体与响应头直接需要用两个换行来区分
使用多线程来打通数据通道
获取到Socket后将socket组为参数传给,一个线程专门为其服务,所以我写了一个ResquetHander类实现runable接口,在它的run方法里面将输入流转换为字符流,方便按行读取,然后开始循环读取浏览器发送的数据bufferedReader.readLine()。需要返回数据就获取输出流,构建响应头,响应体和响应体要有两个换行,最后关闭socket。
引入Servlet,实现业务功能
模仿原生Servlet的规范,自己写一个简单的Servlet,我写了一个Servlet接口,一个HttpServlet抽象类实现Servlet接口,一个实现类(实现计算器功能)继承HttpServlet。因为原生的Servlet的service方法不管是doPost,还是doGet,都有两个对象HttpServlet request;HttpServlet response,所以我也模仿创建了Request类和Response类。
Response类比较简单,就是一个写死的响应头,只返回text文件类型,接收ResquetHander给它的outputStream,作为一个属性。
Requset类的作用就是存储httpRequest的信息,里面的属性有method,uri,,存储parms的HashMap<String,String> ,我在它的构造函数里面传入一个inputStream,这个inputSream由ResquetHander提供,就是ResquetHander调用Request的构造函数,把它从socket里get到的输入流传给这个构造函数,然后在Requset的构造函数里面还写了一个init()初始化方法,在初始化方法里,我用了一个转换流InputStreamReader将inputSream转换为bufferedReader,因为这个转换流可以设置utf-8编码,并且buffferedReader支持readLine(),一行一行的读取数据,通过这个方法获取整个url,然后根据字符串分割获取uri和parms,将uri赋给类属性,将parms put进这个类声明的map里去。这些属性就可以在Servlet的Service方法中被使用。Servlet我是这么构建的,servelt接口里面有下面的三个方法
在HttpServlet抽象类里面实现这三个方法,实际上只写了service方法,只不过我用抽象模板模式把service里的doGet,doPost方法抽象出来让它的子类实现CalServlet来实现具体的业务。
emm,其实在这一步我还没用到反射加容器get对应URI的Servlet,是手动new一个Servlet出来。
容器的构建
使用dom4j来读取xml配置文件里面的信息,构建三个 ConcurrentHashMap 分别存储<name,servlet全类名>键值对,和<uri,name>键值对,<name,servlet实例>键值对,那么前面的手动new一个Servlet,就可以改成从map里获取servlet实例。
接收HTTP request和response
我定义了两个类,分别是response和request,requset里面的属性有method,uri,存储parms的HashMap<String,String>,这个类的构造函数需要传一个inputStream属性进来。 这里我用了一个转换流InputStreamReader将inputSream转换为bufferedReader,因为这个转换流可以设置utf-8编码,并且buffferedReader支持readLine(),一行一行的读取数据,通过这个方法获取整个url,然后根据字符串分割获取uri和parms,将uri赋给类属性,将parms put进这个类声明的map里去
main方法里面起一个ServerSocket监听8080端口,每来一个请求,就getSocket并将它作为参数传给一个分发器类,启动这个线程,这个分发器类实现了runable接口,分发器顾名思义负责分发 它是每来一个请求,就起一个socket.getInputStream和一个socket.getOutputStream,然后将这两个流作为参数传给这两个类,类名叫request和response,分别存储http请求和响应,属性除了http协议里的method,uri等之外,还各有一个InputStream或者OutputStream,resquest类负责解析读取的http请求,获取到method,uri,response的响应头是写死的
得到uri后看uri是否对应到一个servlet,如果对应到就去调用servlet实例
总结:对于servlet的模拟实现,因为真正的servlet是有两个hashMap,维护了<uri,servletname>键值对和<servletname,servlet实例>,它工作的时候,肯定是先根据uri找servletName,再通过servletName找servlet实例。
- 作者:流风回雪
- 链接:https://zone.lfhx.me/learning/tomcat
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章