实现简单的Tomcat | Tomcat原理学习(1)

缘起

用了那么久tomcat,突然觉得自己对它或许并没有想象中的那么熟悉,所以趁着放假我研究了一下这只小猫咪,实现了自己的小tomcat,写出这篇文章同大家一起分享!

照例附上github链接

项目结构

项目结构如下:
图片描述

实现细节

创建MyRequest对象

首先创建自定义的请求类,其中定义url与method两个属性,表示请求的url以及请求的方式。

其构造函数需要传入一个输入流,该输入流通过客户端的套接字对象得到。

输入流中的内容为浏览器传入的http请求头,格式如下:

GET /student HTTP/1.1 Host: localhost:8080 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: Hm_lvt_eaa22075ffedfde4dc734cdbc709273d=1549006558; _ga=GA1.1.777543965.1549006558 

通过对以上内容的分割与截取,我们可以得到该请求的url以及请求的方式。

package tomcat.dome;  import java.io.IOException; import java.io.InputStream;  //实现自己的请求类 public class MyRequest {     //请求的url     private String url;     //请求的方法类型     private String method;          //构造函数 传入一个输入流     public MyRequest(InputStream inputStream) throws IOException {         //用于存放http请求内容的容器         StringBuilder httpRequest=new StringBuilder();         //用于从输入流中读取数据的字节数组         byte[]httpRequestByte=new byte[1024];         int length=0;         //将输入流中的内容读到字节数组中,并且对长度进行判断         if((length=inputStream.read(httpRequestByte))>0) {             //证明输入流中有内容,则将字节数组添加到容器中             httpRequest.append(new String(httpRequestByte,0,length));         }         //将容器中的内容打印出来         System.out.println("httpRequest = [ "+httpRequest+" ]");                           //从httpRequest中获取url,method存储到myRequest中         String httpHead=httpRequest.toString().split("n")[0];         url=httpHead.split("\s")[1];         method=httpHead.split("\s")[0];         System.out.println("MyRequests = [ "+this+" ]");     }      public String getUrl() {         return url;     }      public void setUrl(String url) {         this.url = url;     }      public String getMethod() {         return method;     }      public void setMethod(String method) {         this.method = method;     }      } 

创建MyResponse对象

创建自定义的响应类,构造函数需要传入由客户端的套接字获取的输出流。

定义其write方法,像浏览器写出一个响应头,并且包含我们要写出的内容content。

package tomcat.dome;  import java.io.IOException; import java.io.OutputStream;  //实现自己的响应类 public class MyResponse {     //定义输出流     private OutputStream outputStream;          //构造函数 传入输出流     public MyResponse(OutputStream outputStream) {         this.outputStream=outputStream;     }          //创建写出方法     public void write(String content)throws IOException{         //用来存放要写出数据的容器         StringBuffer stringBuffer=new StringBuffer();         stringBuffer.append("HTTP/1.1 200 OKrn")         .append("Content-type:text/htmlrn")         .append("rn")         .append("<html><head><title>Hello World</title></head><body>")         .append(content)         .append("</body><html>");                  //转换成字节数组 并进行写出         outputStream.write(stringBuffer.toString().getBytes());         //System.out.println("sss");         outputStream.close();     }      } 

创建MyServlet对象

由于我们自己写一个Servlet的时候需要继承HttpServlet,因此在这里首先定义了一个抽象类——MyServlet对象。

在其中定义了两个需要子类实现的抽象方法doGet和doSet。

并且创建了一个service方法,通过对传入的request对象的请求方式进行判断,确定调用的是doGet方法或是doPost方法。

package tomcat.dome;  //写一个抽象类作为servlet的父类 public abstract class MyServlet {     //需要子类实现的抽象方法     protected abstract void doGet(MyRequest request,MyResponse response);     protected abstract void doPost(MyRequest request,MyResponse response);          //父类自己的方法     //父类的service方法对传入的request以及response     //的方法类型进行判断,由此调用doGet或doPost方法     public void service(MyRequest request,MyResponse response) throws NoSuchMethodException {         if(request.getMethod().equalsIgnoreCase("POST")) {             doPost(request, response);         }else if(request.getMethod().equalsIgnoreCase("GET")) {             doGet(request, response);         }else {             throw new NoSuchMethodException("not support");         }     } } 

创建业务相关的Servlet

这里我创建了两个业务相关类:StudentServlet和TeacherServlet。

package tomcat.dome;  import java.io.IOException;  //实现自己业务相关的Servlet public class StudentServlet extends MyServlet{      @Override     protected void doGet(MyRequest request, MyResponse response) {         //利用response中的输出流 写出内容         try {             //System.out.println("!!!!!!!!!!!!!!!!!!");             response.write("I am a student.");             //System.out.println("9999999999999999");         }catch(IOException e) {             e.printStackTrace();         }     }      @Override     protected void doPost(MyRequest request, MyResponse response) {         //利用response中的输出流 写出内容         try {             response.write("I am a student.");         }catch(IOException e) {             e.printStackTrace();         }         }  } 
package tomcat.dome;  import java.io.IOException;  //实现自己业务相关的Servlet public class TeacherServlet extends MyServlet{      @Override     protected void doGet(MyRequest request, MyResponse response) {         //利用response中的输出流 写出内容         try {             response.write("I am a teacher.");         }catch(IOException e) {             e.printStackTrace();         }     }      @Override     protected void doPost(MyRequest request, MyResponse response) {         //利用response中的输出流 写出内容         try {             response.write("I am a teacher.");         }catch(IOException e) {             e.printStackTrace();         }         }  } 

创建映射关系结构ServletMapping

该结构实现的是请求的url与具体的Servlet之间的关系映射。

package tomcat.dome;  //请求url与项目中的servlet的映射关系 public class ServletMapping {     //servlet的名字     private String servletName;     //请求的url     private String url;     //servlet类     private String clazz;     public String getServletName() {         return servletName;     }     public void setServletName(String servletName) {         this.servletName = servletName;     }     public String getUrl() {         return url;     }     public void setUrl(String url) {         this.url = url;     }     public String getClazz() {         return clazz;     }     public void setClazz(String clazz) {         this.clazz = clazz;     }     public ServletMapping(String servletName, String url, String clazz) {         super();         this.servletName = servletName;         this.url = url;         this.clazz = clazz;     } } 

映射关系配置对象ServletMappingConfig

配置类中定义了一个列表,里面存储着项目中的映射关系。

package tomcat.dome;  import java.util.ArrayList; import java.util.List;  //创建一个存储有请求路径与servlet的对应关系的 映射关系配置类 public class ServletMappingConfig {     //使用一个list类型 里面存储的是映射关系类Mapping     public static List<ServletMapping>servletMappings=new ArrayList<>(16);          //向其中添加映射关系     static {         servletMappings.add(new ServletMapping("student","/student", "tomcat.dome.StudentServlet"));         servletMappings.add(new ServletMapping("teacher","/teacher", "tomcat.dome.TeacherServlet"));     } } 

主角登场 MyTomcat!

在服务端MyTomcat中主要做了如下几件事情:

1)初始化请求的映射关系。

2)创建服务端套接字,并绑定某个端口。

3)进入循环,用户接受客户端的链接。

4)通过客户端套接字创建request与response对象。

5)根据request对象的请求方式调用相应的方法。

6)启动MyTomcat!

package tomcat.dome;  import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map;   //Tomcat服务器类 编写对请求做分发处理的相关逻辑 public class MyTomcat {     //端口号     private int port=8080;     //用于存放请求路径与对应的servlet类的请求映射关系的map     //相应的信息从配置类中获取     private Map<String, String>urlServletMap=new HashMap<>(16);     //构造方法     public MyTomcat(int port) {         this.port=port;     }          //tomcat服务器的启动方法     public void start() {         //初始化请求映射关系         initServletMapping();         //服务端的套接字         ServerSocket serverSocket=null;         try {             //创建绑定到某个端口的服务端套接字             serverSocket=new ServerSocket(port);             System.out.println("MyTomcat begin start...");             //循环 用于接收客户端             while(true) {                 //接收到的客户端的套接字                 Socket socket=serverSocket.accept();                 //获取客户端的输入输出流                 InputStream inputStream=socket.getInputStream();                 OutputStream outputStream=socket.getOutputStream();                 //通过输入输出流创建请求与响应对象                 MyRequest request=new MyRequest(inputStream);                 MyResponse response=new MyResponse(outputStream);                                  //根据请求对象的method分发请求 调用相应的方法                 dispatch(request, response);                 //关闭客户端套接字                 socket.close();             }         } catch (IOException e) {             e.printStackTrace();         }     }          //初始化请求映射关系,相关信息从配置类中获取     private void initServletMapping() {         for(ServletMapping servletMapping:ServletMappingConfig.servletMappings) {             urlServletMap.put(servletMapping.getUrl(), servletMapping.getClazz());         }     }          //通过当前的request以及response对象分发请求     private void dispatch(MyRequest request,MyResponse response) {         //根据请求的url获取对应的servlet类的string         String clazz=urlServletMap.get(request.getUrl());         //System.out.println("====="+clazz);         try {             //通过类的string将其转化为对象             Class servletClass=Class.forName("tomcat.dome.StudentServlet");             //实例化一个对象             MyServlet myServlet=(MyServlet)servletClass.newInstance();                          //调用父类方法,根据request的method对调用方法进行判断             //完成对myServlet中doGet与doPost方法的调用             myServlet.service(request, response);         } catch (ClassNotFoundException e) {             e.printStackTrace();         } catch (IllegalAccessException e) {             e.printStackTrace();         } catch (InstantiationException e) {             e.printStackTrace();         } catch (NoSuchMethodException e) {             e.printStackTrace();         }     }          //main方法  直接启动tomcat服务器     public static void main(String[] args) {         new MyTomcat(8080).start();     }      } 

测试结果

图片描述

图片描述

脚本宝典为你提供优质服务
脚本宝典 » 实现简单的Tomcat | Tomcat原理学习(1)

发表评论

提供最优质的资源集合

立即查看 了解详情