资讯专栏INFORMATION COLUMN

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

Corwien / 1645人阅读

摘要:项目结构项目结构如下实现细节创建对象首先创建自定义的请求类,其中定义与两个属性,表示请求的以及请求的方式。其构造函数需要传入一个输入流,该输入流通过客户端的套接字对象得到。创建服务端套接字,并绑定某个端口。

缘起

用了那么久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("
")[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
")
        .append("Content-type:text/html
")
        .append("
")
        .append("Hello World")
        .append(content)
        .append("");
        
        //转换成字节数组 并进行写出
        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 ListservletMappings=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 MapurlServletMap=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();
    }
    
}



测试结果

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/73638.html

相关文章

  • Tomcat】IDEA下跟踪查看源码

    摘要:算是心血来潮吧,想看看的源码,顺便学习一下。这里就简单的做点记录如何用跟踪查看源码。当然这里也提供书籍的源码,可以下载下来对照书看。附上一张运行成功的结果图   算是心血来潮吧,想看看 Tomcat 的源码,顺便学习一下。  这里就简单的做点记录--如何用 IDEA 跟踪查看 Tomcat 源码。  同时,这里也推荐下 how-tomcat-works 这本书,虽然书中讲的是 tomc...

    edgardeng 评论0 收藏0
  • tomcat与nginx反向代理,https过程分析

    摘要:接下来我们要配置这个的端口,这样他们才能运行时端口号不冲突。问题指明不同的端口号访问也太蠢了吧的确很蠢,所以我们要慢慢过渡学习。接下来我们学习用来进行反向代理。阿里云的部分有一些配置的具体过程。 一、在linux上部署运行多个tomcat 1、以前的我们 虽然说是在linux上,但是windows上也是同样的道理,只不过我们服务器都是选用linux罢了。 原先,自己有多个项目需要部署在...

    aikin 评论0 收藏0

发表评论

0条评论

Corwien

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<