跟我一起動手實現Tomcat(一):實現靜態Web伺服器

Geoffrey.Yip?發表於2017-12-30

前言

最近筆者讀了《深入剖析tomcat》這本書(原作:《how tomcat works》),發現該書簡單易讀,每個
章節循序漸進的講解了tomcat的原理,在接下來的章節中,tomcat都是基於上一章新增功能並完善,
到最後形成一個簡易版tomcat的完成品。所以有興趣的同學請按順序閱讀,本文為記錄第一章的知識點
以及原始碼實現(造輪子)。
複製程式碼

如何實現

HTTP協議就是我們們web伺服器與瀏覽器互動的協議,具體的知識點以及背景本文就不再累述。那麼舉一個簡單的例子就好:

  • 在瀏覽器輸入http://www.baidu.com按下Enter鍵。
  • 瀏覽器大概傳送了以下的http請求到百度的伺服器中:
GET /index.html HTTP/1.1
Host: www.baidu.com
...
複製程式碼
  • 百度web伺服器在接收到我們的請求的時候,找到對應的伺服器資源並相應:
HTTP/1.1 200 OK
...

<html>
<head>
<title>百度一下你就知道</title>
</head>
<body>
....
</body>
</html>
複製程式碼

那麼其實通過上面的例子我們可以發現,靜態(這裡指的是html/圖片/css等)web伺服器的實現也是比較簡單的:

程式碼實現

在這裡使用java socket api 實現簡單的靜態web伺服器。
複製程式碼
  • 新建一個main方法,核心程式碼如下:
//開啟socket server 8080埠監聽.
ServerSocket server = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));

try (Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();
OutputStream outputStream = accept.getOutputStream()) {
//解析使用者的請求
Request request = new Request();
request.setRequestStream(inputStream);
request.parseRequest();
//生成響應物件並響應靜態資源
Response resp = new Response(outputStream, request);
resp.accessStaticResources();
} catch (IOException e) {
LOGGER.warn("catch from user request.",e);
}
//關閉伺服器
serverSocket.close();
複製程式碼
  • Request 物件

    主要功能:將使用者請求(socket的inputStream流)解析為字串,提取請求中的URI
    複製程式碼

解析字串程式碼如下:

StringBuilder requestStr = new StringBuilder();
int i;
//new 一個 byte緩衝陣列
byte[] buffer = ArrayUtil.generatorCache();
try {
i = inputStream.read(buffer);
} catch (IOException e) {
e.printStackTrace();
i = -1;
}
//將讀取到的byte轉為String
for (int j = 0; j < i; j++) {
requestStr.append((char) buffer[j]);
}
//解析請求的字串,提取請求的URI
this.parseURI(requestStr.toString());
複製程式碼

那麼請求的資訊被我們解析成字串了,我們怎麼知道他想請求什麼靜態資源呢?

那我們把解析字串列印一下:

    System.out.println(requestStr.toString());
複製程式碼

GET /index.html HTTP/1.1

Host: 127.0.0.1:8080
Connection: keep-alive

Cache-Control: max-age=0

User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36

Upgrade-Insecure-Requests: 1

Accept-Encoding: gzip, deflate, br

Accept-Language: zh-CN,zh;q=0.9

可以很明顯的看到,加粗的地方就是我們要提取的URI,那麼怎麼提取呢?細心的我們發現了,/index.html 這段字串前後都有一個空格!行,那我們可以直接用String的indexOf方法解析,參考程式碼如下:

// 獲取/index.html 前面的那個空格索引
int oneSpace = requestStr.indexOf(" ");
//獲取/index.html 後面的那個空格索引
int twoSpace = requestStr.indexOf(" ", oneSpace + 1);
//擷取獲得使用者請求URI
uri = requestStr.substring(oneSpace + 1, twoSpace);
複製程式碼
  • Response 物件

    上面Request物件已經把使用者想請求的資源解析出來了,那麼Response的功能就是找到這個檔案,
    使用Socket的outputStream把檔案作為位元組流輸出給瀏覽器,就可以將我們的HTML顯示給使用者啦~
    複製程式碼

    那麼這個專案我們的靜態檔案放在那裡呢?來看看我們的專案結構:

    -main  
-java java程式碼
-resources
-webroot 存放我們靜態資源的資料夾
複製程式碼

因為是隻使用MAVEN構建專案,我們也沒使用Spring等框架,如何定位到webroot這個資料夾呢?參考了網上的程式碼:

String WEB_PROJECT_ROOT = HttpServer.class.getClassLoader().getResource("webroot").getFile().substring(1);
複製程式碼

前面的疑惑都解決了,接下來我們就直接把對應的檔案找到給寫回去就完事了~
偽碼如下:

//根據請求URI找到使用者對應請求的資原始檔
File staticResource = new File(HttpServer.WEB_PROJECT_ROOT + request.getUri());
//資源存在
if (staticResource.exists() && staticResource.isFile()) {
outputStream.write(this.responseToByte(200,"OK"));
write(staticResource);
//資源不存在,使用預設的404返回
} else {
staticResource = new File(HttpServer.WEB_PROJECT_ROOT + "/404.html");
outputStream.write(this.responseToByte(404,"file not found"));
write(staticResource);
}
複製程式碼

其中,responseToByte()這個方法只負責將響應行輸出:

    HTTP/1.1 200 OK
複製程式碼

資源不存在時我們們就輸出:

    HTTP/1.1 404 file not found
複製程式碼

write()方法也很簡單,將傳入的file物件轉成流並使用socket的outputStream輸出

try (FileInputStream fis = new FileInputStream(file)) {
byte[] cache = new byte[1024];
int read;
while ((read = fis.read(cache, 0, 1024)) != -1) {
outputStream.write(cache, 0, read);
}
}
複製程式碼

看看效果

執行main方法,開啟我們的瀏覽器輸出127.0.0.1/index.html按下回車,可以看到結果如圖:


試試隨便輸入一個不存在的資源:

按下F12看看Http請求和響應分別是怎樣的:

請求:
GET /abc.html HTTP/1.1
Host: 127.0.0.1:8080
其他請求頭忽略...

響應:
HTTP/1.1 404 file not found

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404 not found!</title>
</head>
<body>
<h1>請求頁面不存在!</h1>
</body>
</html>
複製程式碼

到這裡,我們們的Tomcat 1.0 web伺服器就已經開發完成啦(滑稽臉),已經可以實現簡單的html和css、圖片等資源的訪問等功能,下一章我們們來實現以下簡單的Servlet容器功能開發:

跟我一起動手實現Tomcat(二):實現簡單的Servlet容器

PS:本章原始碼已上傳github SimpleTomcat

相關文章