1. 程式人生 > >我手寫的簡易tomcat

我手寫的簡易tomcat

相關配置 初始 style port oid 服務 tst bubuko init

前述

  自己手寫的簡易的tomcat,實現了tomcat的基本響應功能,項目代碼已經上傳到我的Github,剛剛開始學習這裏,當前還存在很多問題

項目簡述及代碼

  當我們的Web運行的時候,從瀏覽器發出的請求,必然首先到達tomcat中,之後由tomcat進行處理,由此要考慮tomcat要進行哪些處理,首先便是提供Socket服務,之後對於請求進行分發,把請求和產生的響應封裝成request和response

  (1)提供Socket服務

  (2)封裝請求/響應對象

  (3)將不同的請求映射到具體的Servlet處理

處理請求

  我們首先考慮的,是客戶端發送來請求時,我們應該怎麽去識別它,這裏涉及到的就是HTTP請求協議的部分,我直接那Github頁面的HTTP請求協議做例子來說,如下圖

  我們可以看到,在Request頭的首行,由 GET /jyroy HTTP/1.1 三部分構成,而這三部分分別的含義是 請求方法 請求路徑 請求協議及其對應版本號

技術分享圖片

  我們在拿到Resquest請求之後根據上面的分析,拿到相應的信息就可以進行後續的處理了。

 1 package myTomcat;
 2 
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 
 6 /**
 7  * @author jyroy
 8  *
 9  */
10 public class MyRequest {
11 12 //請求路徑 13 private String url; 14 //請求方法 15 private String method; 16 17 //讀取輸入字節流,封裝成字符串格式的請求內容 18 public MyRequest(InputStream inputStream) throws IOException{ 19 String httpRequest = ""; 20 21 byte[] httpRequestBytes = new byte[1024];
22 23 int length = 0; 24 25 if((length = inputStream.read(httpRequestBytes)) > 0) { 26 httpRequest = new String(httpRequestBytes, 0, length); 27 } 28 //HTTP請求協議:首行的內容依次為:請求方法、請求路徑以及請求協議及其對應版本號 29 // GET /index HTTP/1.1 30 String httpHead = httpRequest.split("\n")[0]; //取出HTTP請求協議的首行 31 System.out.println(httpHead); 32 method = httpHead.split("\\s")[0]; //按照空格進行分割,第一個是請求的方法 33 url = httpHead.split("\\s")[1]; //按照空格進行分割,第二個是請求的路徑 34 System.out.println(this.toString()); 35 } 36 37 public String getUrl() { 38 return url; 39 } 40 41 public void setUrl(String url) { 42 this.url = url; 43 } 44 45 public String getMethod() { 46 return method; 47 } 48 49 public void setMethod(String method) { 50 this.method = method; 51 } 52 53 @Override 54 public String toString() { 55 return "MyRequest [url=" + url + ", method=" + method + "]"; 56 } 57 58 59 }

處理響應

  考慮完接受請求之後,我們再來考慮一下怎麽來做出我們的響應,瀏覽器才能識別,這裏要涉及到的就是HTTP響應報文的內容,我的思路是,利用字符串拼接出Response報文,再將String轉換為字節流就可以了。

  我們也是來看一下Github的Response報文的格式,如下圖

技術分享圖片

  這麽多的響應頭,其實不是全部需要的,我們只需要寫入一些基本的必須響應頭信息,例如 請求協議及其對應版本號 響應號 響應狀態 和 Cotent-type 等,如下

技術分享圖片

  最後只要轉化字節流就可以

  技術分享圖片

 1 package myTomcat;
 2 
 3 import java.io.IOException;
 4 import java.io.OutputStream;
 5 
 6 public class MyResponse {
 7     private OutputStream outputStream;
 8     
 9     public MyResponse(OutputStream outputStream) {
10         this.outputStream = outputStream;
11     }
12     
13     //將文本轉換為字節流
14     public void write(String content) throws IOException{
15         StringBuffer httpResponse = new StringBuffer();
16         httpResponse.append("HTTP/1.1 200 OK\n")      //按照HTTP響應報文的格式寫入
17                     .append("Content-Type:text/html\n")
18                     .append("\r\n")
19                     .append("<html><head><link rel=\"icon\" href=\"data:;base64,=\"></head><body>")
20                     .append(content)          //將頁面內容寫入
21                     .append("</body></html>");
22         outputStream.write(httpResponse.toString().getBytes());      //將文本轉為字節流
23         outputStream.close();
24     }
25     
26 }

Servlet請求處理基類

  當我們的請求和響應都已經準備好之後,接下來考慮servlet請求處理的部分,tomcat本身是一種滿足servlet規範的容器,我們需要識別接收到的請求之後並做出響應,就涉及到了 doGet doPost service 三個方法

技術分享圖片

 1 package myTomcat;
 2 
 3 /**
 4  * @author jyroy
 5  * 提供API:doGet doPost service 方法
 6  */
 7 public abstract class MyServlet {
 8     
 9     public void service(MyRequest myRequest, MyResponse myResponse) {
10         if(myRequest.getMethod().equalsIgnoreCase("POST")) {
11             doPost(myRequest, myResponse);
12         }else if(myRequest.getMethod().equalsIgnoreCase("GET")) {
13             doGet(myRequest, myResponse);
14         }
15     }
16     
17     public void doGet(MyRequest myRequest, MyResponse myResponse) {
18         
19     }
20     
21     public void doPost(MyRequest myRequest, MyResponse myResponse) {
22         
23     }
24     
25 }

Servlet配置

  考慮完上述問題之後,下一步需要的是分配url給哪一個servlet來處理,首先需要的就是一個反應映射關系的類

  技術分享圖片

 1 package myTomcat;
 2 
 3 public class ServletMapping {
 4     private String servletName;
 5     private String url;
 6     private String clazz;
 7     
 8     public ServletMapping(String servletName, String url, String clazz) {
 9         super();
10         this.servletName = servletName;
11         this.url = url;
12         this.clazz = clazz;
13     }
14 
15     public String getServletName() {
16         return servletName;
17     }
18 
19     public void setServeletName(String servletName) {
20         this.servletName = servletName;
21     }
22 
23     public String getUrl() {
24         return url;
25     }
26 
27     public void setUrl(String url) {
28         this.url = url;
29     }
30 
31     public String getClazz() {
32         return clazz;
33     }
34 
35     public void setClazz(String clazz) {
36         this.clazz = clazz;
37     }
38 }

  以及相關配置文件

技術分享圖片

 1 package myTomcat;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 /**
 7  * @author jyroy
 8  * 
 9  */
10 public class ServletMappingConfig {
11     public static List<ServletMapping> servletMappingList = new ArrayList<>();
12     
13      static {
14         servletMappingList.add(new ServletMapping("index", "/index", "myTomcat.test.IndexServlet"));
15         servletMappingList.add(new ServletMapping("myblog", "/myblog", "myTomcat.test.MyBlog"));
16      }
17 }

核心類

  最終,我們準備好基類後,需要的就是實現開始提到的整個處理流程

  (1)提供Socket服務

  (2)封裝請求/響應對象

  (3)將不同的請求映射到具體的Servlet處理

  這裏重點說的是,要利用 ServerSocket 通過服務器上的端口通信 以及 accpt方法一直等待客戶端的請求

  具體邏輯在代碼中註釋

 1 package myTomcat;
 2 
 3 import java.io.InputStream;
 4 import java.io.OutputStream;
 5 import java.net.ServerSocket;
 6 import java.util.HashMap;
 7 import java.util.Map;
 8 import java.net.Socket;
 9 
10 /**
11  * @author jyroy
12  * Tomcat的處理流程:把URL對應處理的Servlet關系形成,解析HTTP協議,封裝請求/響應對象,
13  * 利用反射實例化具體的Servlet進行處理即可。
14  */
15 public class MyTomcat {
16     private Integer port = 8080;     //定義8080端口
17     
18     private Map<String, String> urlServletMapping = new HashMap<>();    //存儲url和對應的類
19 
20     public MyTomcat(Integer port) {
21         super();
22         this.port = port;
23     }
24     
25     @SuppressWarnings("resource")
26     public void start() {
27         initServletMapping();
28         
29             try {
30                 ServerSocket serverSocket = null;     //實例化一個 ServerSocket 對象,表示通過服務器上的端口通信
31                 serverSocket = new ServerSocket(port);   
32                 System.out.println("MyTomcat is starting...");
33                 while(true) {
34                     Socket socket = serverSocket.accept();     //服務器調用 ServerSocket 類的 accept() 方法,該方法將一直等待,直到客戶端連接到服務器上給定的端口 
35                     InputStream inputStream = socket.getInputStream();
36                     OutputStream outputStream = socket.getOutputStream();
37                     
38                     MyRequest myRequest = new MyRequest(inputStream);
39                     MyResponse myResponse = new MyResponse (outputStream);
40                     
41                     dispatch(myRequest, myResponse);
42             
43                     socket.close();                
44                 }
45             }catch(Exception e) {
46                 e.printStackTrace();
47             }
48         
49 //        }finally {
50 //            if(serverSocket != null) {
51 //                try {
52 //                    serverSocket.close();
53 //                }catch(Exception e){
54 //                    e.printStackTrace();
55 //                }
56 //            }
57 //        }
58     }
59     
60     //初始化映射
61     public void initServletMapping() {
62         for(ServletMapping servletMapping : ServletMappingConfig.servletMappingList) {
63             urlServletMapping.put(servletMapping.getUrl(), servletMapping.getClazz());
64         }
65     }
66     
67     //分發請求
68     @SuppressWarnings("unchecked")
69     public void dispatch(MyRequest myRequest, MyResponse myResponse) {
70         String clazz = urlServletMapping.get(myRequest.getUrl());
71         
72         try {
73             Class<MyServlet> myServletClass = (Class<MyServlet>)Class.forName(clazz); 
74             MyServlet myservlet = myServletClass.newInstance();
75             myservlet.service(myRequest, myResponse);
76         }catch(ClassNotFoundException e) {
77             e.printStackTrace();
78         }catch(InstantiationException e) {
79             e.printStackTrace();
80         }catch(IllegalAccessException e) {
81             e.printStackTrace();
82         }
83     }
84     
85     public static void main(String[] args) {
86         MyTomcat myTomcat = new MyTomcat(8080);
87         myTomcat.start();
88     }
89     
90 }

測試類

 1 package myTomcat.test;
 2 
 3 import java.io.IOException;
 4 
 5 import myTomcat.MyRequest;
 6 import myTomcat.MyResponse;
 7 import myTomcat.MyServlet;
 8 
 9 public class IndexServlet extends MyServlet {
10     @Override
11     public void doGet(MyRequest myRequest, MyResponse myResponse) {
12         try {
13             myResponse.write("Hello, myTomcat");
14         } catch (IOException e) {
15             e.printStackTrace();
16         }
17     }
18     
19     @Override
20     public void doPost(MyRequest myRequest, MyResponse myResponse) {
21         try {
22             myResponse.write("Hello, myTomcat");
23         } catch (IOException e) {
24             e.printStackTrace();
25         }
26     }
27 }
 1 package myTomcat.test;
 2 
 3 import java.io.IOException;
 4 
 5 import myTomcat.MyRequest;
 6 import myTomcat.MyResponse;
 7 import myTomcat.MyServlet;
 8 
 9 public class MyBlog extends MyServlet {
10     @Override
11     public void doGet(MyRequest myRequest, MyResponse myResponse) {
12         try {
13             myResponse.write("Hello, this is my blog");
14         } catch (IOException e) {
15             e.printStackTrace();
16         }
17     }
18     @Override
19     public void doPost(MyRequest myRequest, MyResponse myResponse) {
20         try {
21             myResponse.write("Hello, this is my blog");
22         } catch (IOException e) {
23             e.printStackTrace();
24         }
25     }
26 }

運行結果

技術分享圖片

技術分享圖片

我手寫的簡易tomcat