[自己造輪子] 動手寫一個 Tomcat 容器
阿新 • • 發佈:2018-12-12
一、前言
IT 行業變化多端,流行的技術層出不窮, 而萬變不離其宗,學習其設計思想,才是最為重要。 Tomcat,基於 HTTP 協議,根據請求連結,返回相應的資源。
二、程式碼
程式碼參考了網上,做了一定的修改,使之符合 Maven 的專案規範
1、Tomcat 啟動檔案
package testTomcat;
import myTomcat.Servlet;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
public class TestServer {
//定義一個變數,存放服務端WebContent目錄的絕對路徑
public static String WEB_ROOT=System.getProperty("user.dir")+"\\src\\main\\webapp";
//定義靜態變數,用於存放本次請求的靜態頁面名稱
private static String url="";
//定義一個靜態型別MAP,儲存服務端conf.properties中的配置資訊
private static Map<String,String> map=new HashMap<String,String>();
static {
//伺服器啟動之前將配置引數中的資訊載入到MAP中
//建立一個Properties物件
Properties prop=new Properties();
try {
//載入WebContent目錄下的conf.properties檔案
prop.load(new FileInputStream(System.getProperty("user.dir")+"\\src\\main\\resources\\conf.properties"));
//將配置檔案中的資料讀取到MAP中
Set set=prop.keySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()) {
String key=(String)iterator.next();
String value = prop.getProperty(key);
map.put(key, value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
System.out.println(map);
ServerSocket serverSocket=null;
Socket socket=null;
InputStream is=null;
OutputStream ops=null;
try {
//建立ServerSocket,監聽本機器的80埠,等待來自客戶端的請求
serverSocket=new ServerSocket(8987);
while(true) {
//獲取到客戶端對應的socket
socket= serverSocket.accept();
//獲取到輸入流物件
is = socket.getInputStream();
//獲取到輸出流物件
ops = socket.getOutputStream();
//獲取HTTP協議的請求部分,擷取客戶端要訪問的資源名稱,將這個資源名稱賦值給url demo01.html aa
parse(is);
//判斷本次請求的是靜態demo.html頁面還是執行在服務端一段JAVA小程式
if(null!=url) {
if(url.indexOf(".")!=-1) {
//傳送靜態資原始檔
sendStaticResource(ops);
}else {
//傳送動態資源
sendDynamicResource(is,ops);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
//釋放資源
if(null!=is) {
is.close();
is=null;
}
if(null!=ops) {
ops.close();
ops=null;
}
if(null!=socket) {
socket.close();
socket=null;
}
}
}
private static void sendDynamicResource(InputStream is, OutputStream ops) throws Exception {
//將HTTP協議的響應行和響應頭髮送到客戶端
ops.write("HTTP/1.1 200 ok\n".getBytes());
ops.write("Server:Apache\n".getBytes());
ops.write("Content-type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
//判斷map中的是否存在一個key,這個key是否和本次待請求的資源路徑一致
if(map.containsKey(url)) {
//如果包含指定的key,獲取到MAP中key對應的value部分
String value=map.get(url);
//通過反射將對應的JAVA程式載入到記憶體
Class clazz=Class.forName(value);
Servlet servlet=(Servlet)clazz.newInstance();
//執行init方法
servlet.init();
//執行service方法
servlet.Service(is, ops);
}
}
//獲取HTTP協議的請求部分,擷取客戶端要訪問的資源名稱,將這個資源名稱賦值給url
private static void parse(InputStream is) throws IOException {
//定義一個變數,存放HTTP協議請求部分資料
StringBuffer content=new StringBuffer(2048);
//定義一個數組,存放HTTP協議請求部分資料
byte[] buffer=new byte[2048];
//定義一個變數i,代表讀取資料到陣列中之後,資料量的大小
int i=-1;
//讀取客戶端傳送過來的資料,將資料讀取到位元組陣列buffer中.i代表讀取資料量的大小
i=is.read(buffer);
//遍歷位元組陣列,將陣列中的資料追加到content變數中
for(int j=0;j<i;j++) {
content.append((char)buffer[j]);
}
//列印HTTP協議請求部分資料
System.out.println(content);
//擷取客戶端要請求的資源路徑 demo.html,賦值給url
parseUrl(content.toString());
}
//擷取客戶端要請求的資源路徑 demo.html,賦值給url
private static void parseUrl(String content) {
//定義2個變數,存放請求行的2個空格的位置
int index1,index2;
//獲取http請求部分第1個空格的位置
index1=content.indexOf(" ");
if(index1!=-1) {
index2=content.indexOf(" ",index1+1);
//獲取http請求部分第2個空格的位置
if(index2>index1) {
//擷取字串獲取到本次請求資源的名稱
url=content.substring(index1+2, index2);
}
}
//列印本次請求靜態資源名稱
System.out.println(url);
}
//傳送靜態資源
private static void sendStaticResource(OutputStream ops) throws IOException {
//定義一個位元組陣列,用於存放本次請求的靜態資源demo01.html的內容
byte[] bytes=new byte[2048];
//定義一個檔案輸入流,使用者獲取靜態資源demo01.html中的內容
FileInputStream fis=null;
try {
//建立檔案物件File,代表本次要請求的資源demo01.html
File file=new File(WEB_ROOT,url);
//如果檔案存在
if(file.exists()) {
//向客戶端輸出HTTP協議的響應行/響應頭
ops.write("HTTP/1.1 200 OK\n".getBytes());
ops.write("Server:apache-Coyote/1.1\n".getBytes());
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
//獲取到檔案輸入流物件
fis=new FileInputStream(file);
//讀取靜態資源demo01.html中的內容到陣列中
int ch=fis.read(bytes);
while(ch!=-1) {
//將讀取到陣列中的內容通過輸出流傳送到客戶端
ops.write(bytes, 0, ch);
ch=fis.read(bytes);
}
}else {
//如果檔案不存在
//向客戶端響應檔案不存在訊息
ops.write("HTTP/1.1 404 not found\n".getBytes());
ops.write("Server:apache-Coyote/1.1\n".getBytes());
ops.write("Content-Type:text/html;charset=utf-8\n".getBytes());
ops.write("\n".getBytes());
String errorMessage="file not found";
ops.write(errorMessage.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//釋放檔案輸入流物件
if(null!=fis) {
fis.close();
fis=null;
}
}
}
}
2、servlet
介面
package myTomcat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
//所有服務端的JAVA小程式要實現的介面
public interface Servlet {
//初始化
public void init();
//服務
public void Service(InputStream is, OutputStream ops) throws IOException;
//銷燬
public void destroy();
}
實現
package myTomcat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class helloServlet implements Servlet {
public void init() {
}
public void Service(InputStream is, OutputStream ops) throws IOException {
ops.write("Hello world".getBytes());
ops.flush(); // 立馬輸出
}
public void destroy() {
}
}
3、配置
conf.properties
aa=myTomcat.helloServlet
4、測試頁
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>cun</title>
</head>
<body>
cun
</body>
</html>
三、效果
測試 servlet
測試靜態資源
四、其他
真正做到和 Tomcat 功能完備,還需有很長的路要走,畢竟前輩們多年的努力不是白費的。