跟我一塊兒動手實現Tomcat(一):實現靜態Web服務器

前言

最近筆者讀了《深刻剖析tomcat》這本書(原做:《how tomcat works》),發現該書簡單易讀,每一個
章節按部就班的講解了tomcat的原理,在接下來的章節中,tomcat都是基於上一章新增功能並完善,
到最後造成一個簡易版tomcat的完成品。因此有興趣的同窗請按順序閱讀,本文爲記錄第一章的知識點
以及源碼實現(造輪子)。
複製代碼

如何實現

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

  • 在瀏覽器輸入http://www.baidu.com按下回車鍵。
  • 瀏覽器大概發送瞭如下的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服務器的實現也是比較簡單的:html

代碼實現

在這裏使用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 對象java

    主要功能:將用戶請求(socket的inputStream流)解析爲字符串,提取請求中的URI
    複製代碼

解析字符串代碼以下:git

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());
複製代碼

那麼請求的信息被咱們解析成字符串了,咱們怎麼知道他想請求什麼靜態資源呢?github

那咱們把解析字符串打印一下:web

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
api

能夠很明顯的看到,加粗的地方就是咱們要提取的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顯示給用戶啦~
    複製代碼

    那麼這個項目咱們的靜態文件放在那裏呢?來看看咱們的項目結構:tomcat

-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

相關文章
相關標籤/搜索