手寫(xiě) WEB 服務(wù)器和 HTTP 協(xié)議
本節(jié)我們將借助 Socket 實(shí)現(xiàn)服務(wù)的端口監(jiān)聽(tīng)并根據(jù) Http 協(xié)議的請(qǐng)求和響應(yīng)結(jié)構(gòu),實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 Web 服務(wù)器,加深體驗(yàn) Web 服務(wù)和 Http 協(xié)議的原理。
1. Http服務(wù)基本要素
1.1 監(jiān)聽(tīng)連接
瀏覽器每發(fā)起一次請(qǐng)求都需要跟服務(wù)端建立連接,服務(wù)端要時(shí)刻監(jiān)聽(tīng)有沒(méi)有客戶端連接。傳輸層協(xié)議有 TCP/UDP 兩種,實(shí)現(xiàn)起來(lái)并沒(méi)有強(qiáng)制說(shuō)用哪一種,下面是官方文檔對(duì) Http 連接的說(shuō)明:
HTTP communication usually takes place over TCP/IP connections. The default port is TCP 80 .
文檔中指明了連接通常用的是 TCP, TCP 不用考慮數(shù)據(jù)包亂序,丟失這些問(wèn)題,實(shí)現(xiàn)起來(lái)更簡(jiǎn)單,高效。在代碼層我們可以用 Socket 來(lái)實(shí)現(xiàn)我們的 TCP 傳輸服務(wù)。
1.2 接收數(shù)據(jù)
Socket 監(jiān)聽(tīng)連接,在沒(méi)有連接到來(lái)之前一直是阻塞在 serverSocket.accept(); 有請(qǐng)求過(guò)來(lái)就可以運(yùn)行到下面的代碼,然后可以根據(jù)我們的輸入流讀取信息,根據(jù) Http 協(xié)議拆開(kāi)獲取我們要的請(qǐng)求數(shù)據(jù)。
1.3 返回?cái)?shù)據(jù)
根據(jù)業(yè)務(wù)處理完獲得返回實(shí)體數(shù)據(jù),然后遵從 Http 協(xié)議格式構(gòu)造返回的消息報(bào)文。瀏覽器獲得到的數(shù)據(jù)也會(huì)根據(jù) Http 協(xié)議進(jìn)行渲染。
2. Http報(bào)文格式
Http 協(xié)議請(qǐng)求報(bào)文的本質(zhì)就是一堆字符串,只是這堆字符是有格式的,發(fā)送方跟接收方都需要按照這個(gè)格式來(lái)拼接和拆解內(nèi)容。我們要實(shí)現(xiàn)一個(gè) Web 服務(wù),了解這個(gè)是最基本的要素。
以下截圖的報(bào)文是通過(guò) tcpflow(一款功能強(qiáng)大的、基于命令行的免費(fèi)開(kāi)源工具)在 Linux 系統(tǒng)抓包獲取的。
sudo tcpflow -c port 8080
2.1 Request


圖中各種請(qǐng)求首部字段的具體含義/用途,會(huì)在下面章節(jié)中詳細(xì)講解到。
2.2 Response
一般情況下,服務(wù)器收到客戶端的請(qǐng)求后,就會(huì)有一個(gè) Http 的響應(yīng)消息,Http 響應(yīng)也由 4 部分組成,分別是:狀態(tài)行、響應(yīng)頭、空行 和 響應(yīng)實(shí)體。

圖中的首部字段和返回內(nèi)容(響應(yīng)實(shí)體)中間是有一個(gè)空行的。
3. 實(shí)現(xiàn)
3.1 效果

- Web 服務(wù)端監(jiān)聽(tīng) 8090 端口;
- 本地瀏覽器訪問(wèn) 8090 頁(yè)面顯示
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 {
//開(kāi)啟一個(gè) Socket 服務(wù)端,并監(jiān)聽(tīng) 8090 端口
ServerSocket serverSocket = new ServerSocket(8090);
do {
//阻塞,直到有客戶端連接上,才會(huì)執(zhí)行后面的邏輯
Socket socket = serverSocket.accept();
//處理數(shù)據(jù)
hander(socket);
} while (true);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* http response
* 第一行 協(xié)議 返回狀態(tài)
* 第二行 媒體類型 josn/html
* 第三行 空
* 內(nèi)容
* @param socket
*/
private void hander(Socket socket) throws IOException {
//拼接返回的 request 報(bào)文
StringBuilder responseBuilder = new StringBuilder();
responseBuilder
//返回 200 狀態(tài)碼,表示請(qǐng)求成功
.append("HTTP/1.1 200 \r\n")
//告訴請(qǐng)求的客戶端,返回的內(nèi)容是 text/html 格式的
.append("Content-Type: text/html\r\n")
//首部字段和消息實(shí)體中間的空行
.append("\r\n")
//內(nèi)容部分
.append("hello tomcat");
//獲取客戶端通道的輸出流
OutputStream outputStream = socket.getOutputStream();
//往輸出流通道寫(xiě)消息
outputStream.write(responseBuilder.toString().getBytes());
//流是有緩存機(jī)制的,寫(xiě)消息的時(shí)候不一定立馬發(fā)出去,刷一下才能保證數(shù)據(jù)發(fā)送出去
outputStream.flush();
//關(guān)閉輸出流通道
outputStream.close();
}
}
上面的代碼初學(xué)者可以自己模仿著寫(xiě)一個(gè),相信對(duì) Http 會(huì)有很深刻的體驗(yàn)。代碼中主要是監(jiān)聽(tīng)連接,客戶端連接后,根據(jù) Http 協(xié)議進(jìn)行字符串的拼接返回給客戶端,客戶端瀏覽器接收到是標(biāo)準(zhǔn)的 Http 格式就會(huì)進(jìn)行渲染。
4. 小結(jié)
這邊的代碼雖然很簡(jiǎn)單,但是最核心的 Http 服務(wù)雛形已經(jīng)展示出來(lái)了,成熟的 Http 服務(wù)可以在這基礎(chǔ)上對(duì)以下模塊進(jìn)行優(yōu)化:
- 針對(duì)請(qǐng)求事件的 線程 / IO 優(yōu)化;
- Servlet 協(xié)議支持;
- 配置獨(dú)立管理;
- Http協(xié)議內(nèi)容完善(比如緩存機(jī)制);
- 支持虛擬主機(jī)配置;
- 支持代理;
- rewrite 機(jī)制;
- 安全認(rèn)證。
zhourj ·
2025 imooc.com All Rights Reserved |