1. 程式人生 > >[自己造輪子] 動手寫一個 Tomcat 容器

[自己造輪子] 動手寫一個 Tomcat 容器

一、前言

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 功能完備,還需有很長的路要走,畢竟前輩們多年的努力不是白費的。