緣起
用了那麼久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();
}
}
測試結果