最近筆者讀了《深刻剖析tomcat》這本書(原做:《how tomcat works》),發現該書簡單易讀,每一個
章節按部就班的講解了tomcat的原理,在接下來的章節中,tomcat都是基於上一章新增功能並完善,
到最後造成一個簡易版tomcat的完成品。因此有興趣的同窗請按順序閱讀,本文爲記錄第一章的知識點
以及源碼實現(造輪子)。
複製代碼
HTTP協議就是我們web服務器與瀏覽器交互的協議,具體的知識點以及背景本文就再也不累述。那麼舉一個簡單的例子就好:css
GET /index.html HTTP/1.1
Host: www.baidu.com
...
複製代碼
HTTP/1.1 200 OK
...
<html>
<head>
<title>百度一下你就知道</title>
</head>
<body>
....
</body>
</html>
複製代碼
那麼其實經過上面的例子咱們能夠發現,靜態(這裏指的是html/圖片/css等)web服務器的實現也是比較簡單的:html
在這裏使用java socket api 實現簡單的靜態web服務器。
複製代碼
//開啓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