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

缘起

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

照例附上github链接

项目结构

项目结构如下:
《实现简单的Tomcat | Tomcat原理学习(1)》

实现细节

创建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 OK\r\n")
        .append("Content-type:text/html\r\n")
        .append("\r\n")
        .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)》

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

    原文作者:Shimmer
    原文地址: https://segmentfault.com/a/1190000018452179
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞