手寫 WEB 服務器和 HTTP 協議
本節我們將借助 Socket 實現服務的端口監聽并根據 Http 協議的請求和響應結構,實現一個簡單的 Web 服務器,加深體驗 Web 服務和 Http 協議的原理。
1. Http服務基本要素
1.1 監聽連接
瀏覽器每發起一次請求都需要跟服務端建立連接,服務端要時刻監聽有沒有客戶端連接。傳輸層協議有 TCP/UDP 兩種,實現起來并沒有強制說用哪一種,下面是官方文檔對 Http 連接的說明:
HTTP communication usually takes place over TCP/IP connections. The default port is TCP 80 .
文檔中指明了連接通常用的是 TCP, TCP 不用考慮數據包亂序,丟失這些問題,實現起來更簡單,高效。在代碼層我們可以用 Socket 來實現我們的 TCP 傳輸服務。
1.2 接收數據
Socket 監聽連接,在沒有連接到來之前一直是阻塞在 serverSocket.accept();
有請求過來就可以運行到下面的代碼,然后可以根據我們的輸入流讀取信息,根據 Http 協議拆開獲取我們要的請求數據。
1.3 返回數據
根據業務處理完獲得返回實體數據,然后遵從 Http 協議格式構造返回的消息報文。瀏覽器獲得到的數據也會根據 Http 協議進行渲染。
2. Http報文格式
Http 協議請求報文的本質就是一堆字符串,只是這堆字符是有格式的,發送方跟接收方都需要按照這個格式來拼接和拆解內容。我們要實現一個 Web 服務,了解這個是最基本的要素。
以下截圖的報文是通過 tcpflow
(一款功能強大的、基于命令行的免費開源工具)在 Linux 系統抓包獲取的。
sudo tcpflow -c port 8080
2.1 Request
圖中各種請求首部字段的具體含義/用途,會在下面章節中詳細講解到。
2.2 Response
一般情況下,服務器收到客戶端的請求后,就會有一個 Http 的響應消息,Http 響應也由 4 部分組成,分別是:狀態行、響應頭、空行 和 響應實體。
圖中的首部字段和返回內容(響應實體)中間是有一個空行的。
3. 實現
3.1 效果
- Web 服務端監聽 8090 端口;
- 本地瀏覽器訪問 8090 頁面顯示
hello tomcat
。
3.2 代碼
package com.imooc.mytomcat.tomcat;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* Mytomcat
*
* @author zhourj
* description
*/
public class Mytomcat {
public static void main(String[] args) {
Mytomcat server = new Mytomcat();
server.start();
}
private void start(){
try {
//開啟一個 Socket 服務端,并監聽 8090 端口
ServerSocket serverSocket = new ServerSocket(8090);
do {
//阻塞,直到有客戶端連接上,才會執行后面的邏輯
Socket socket = serverSocket.accept();
//處理數據
hander(socket);
} while (true);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* http response
* 第一行 協議 返回狀態
* 第二行 媒體類型 josn/html
* 第三行 空
* 內容
* @param socket
*/
private void hander(Socket socket) throws IOException {
//拼接返回的 request 報文
StringBuilder responseBuilder = new StringBuilder();
responseBuilder
//返回 200 狀態碼,表示請求成功
.append("HTTP/1.1 200 \r\n")
//告訴請求的客戶端,返回的內容是 text/html 格式的
.append("Content-Type: text/html\r\n")
//首部字段和消息實體中間的空行
.append("\r\n")
//內容部分
.append("hello tomcat");
//獲取客戶端通道的輸出流
OutputStream outputStream = socket.getOutputStream();
//往輸出流通道寫消息
outputStream.write(responseBuilder.toString().getBytes());
//流是有緩存機制的,寫消息的時候不一定立馬發出去,刷一下才能保證數據發送出去
outputStream.flush();
//關閉輸出流通道
outputStream.close();
}
}
上面的代碼初學者可以自己模仿著寫一個,相信對 Http 會有很深刻的體驗。代碼中主要是監聽連接,客戶端連接后,根據 Http 協議進行字符串的拼接返回給客戶端,客戶端瀏覽器接收到是標準的 Http 格式就會進行渲染。
4. 小結
這邊的代碼雖然很簡單,但是最核心的 Http 服務雛形已經展示出來了,成熟的 Http 服務可以在這基礎上對以下模塊進行優化:
- 針對請求事件的 線程 / IO 優化;
- Servlet 協議支持;
- 配置獨立管理;
- Http協議內容完善(比如緩存機制);
- 支持虛擬主機配置;
- 支持代理;
- rewrite 機制;
- 安全認證。