實現簡單的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 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();
    }
    
}



測試結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章