用了那麼久tomcat,忽然以爲本身對它或許並無想象中的那麼熟悉,因此趁着放假我研究了一下這隻小貓咪,實現了本身的小tomcat,寫出這篇文章同你們一塊兒分享!html
照例附上github連接。
java
項目結構以下:
git
首先建立自定義的請求類,其中定義url與method兩個屬性,表示請求的url以及請求的方式。github
其構造函數須要傳入一個輸入流,該輸入流經過客戶端的套接字對象獲得。web
輸入流中的內容爲瀏覽器傳入的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; } }
建立自定義的響應類,構造函數須要傳入由客戶端的套接字獲取的輸出流。tomcat
定義其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(); } }
因爲咱們本身寫一個Servlet的時候須要繼承HttpServlet,所以在這裏首先定義了一個抽象類——MyServlet對象。app
在其中定義了兩個須要子類實現的抽象方法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"); } } }
這裏我建立了兩個業務相關類: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(); } } }
該結構實現的是請求的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; } }
配置類中定義了一個列表,裏面存儲着項目中的映射關係。
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中主要作了以下幾件事情:
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(); } }