我手寫的簡易tomcat
前述
自己手寫的簡易的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