手寫 WEB 服務(wù)器和 HTTP 協(xié)議
本節(jié)我們將借助 Socket 實(shí)現(xiàn)服務(wù)的端口監(jiān)聽并根據(jù) Http 協(xié)議的請求和響應(yīng)結(jié)構(gòu),實(shí)現(xiàn)一個簡單的 Web 服務(wù)器,加深體驗(yàn) Web 服務(wù)和 Http 協(xié)議的原理。
1. Http服務(wù)基本要素
1.1 監(jiān)聽連接
瀏覽器每發(fā)起一次請求都需要跟服務(wù)端建立連接,服務(wù)端要時刻監(jiān)聽有沒有客戶端連接。傳輸層協(xié)議有 TCP/UDP 兩種,實(shí)現(xiàn)起來并沒有強(qiáng)制說用哪一種,下面是官方文檔對 Http 連接的說明:
HTTP communication usually takes place over TCP/IP connections. The default port is TCP 80 .
文檔中指明了連接通常用的是 TCP, TCP 不用考慮數(shù)據(jù)包亂序,丟失這些問題,實(shí)現(xiàn)起來更簡單,高效。在代碼層我們可以用 Socket 來實(shí)現(xiàn)我們的 TCP 傳輸服務(wù)。
1.2 接收數(shù)據(jù)
Socket 監(jiān)聽連接,在沒有連接到來之前一直是阻塞在 serverSocket.accept();
有請求過來就可以運(yùn)行到下面的代碼,然后可以根據(jù)我們的輸入流讀取信息,根據(jù) Http 協(xié)議拆開獲取我們要的請求數(shù)據(jù)。
1.3 返回?cái)?shù)據(jù)
根據(jù)業(yè)務(wù)處理完獲得返回實(shí)體數(shù)據(jù),然后遵從 Http 協(xié)議格式構(gòu)造返回的消息報(bào)文。瀏覽器獲得到的數(shù)據(jù)也會根據(jù) Http 協(xié)議進(jìn)行渲染。
2. Http報(bào)文格式
Http 協(xié)議請求報(bào)文的本質(zhì)就是一堆字符串,只是這堆字符是有格式的,發(fā)送方跟接收方都需要按照這個格式來拼接和拆解內(nèi)容。我們要實(shí)現(xiàn)一個 Web 服務(wù),了解這個是最基本的要素。
以下截圖的報(bào)文是通過 tcpflow
(一款功能強(qiáng)大的、基于命令行的免費(fèi)開源工具)在 Linux 系統(tǒng)抓包獲取的。
sudo tcpflow -c port 8080
2.1 Request
圖中各種請求首部字段的具體含義/用途,會在下面章節(jié)中詳細(xì)講解到。
2.2 Response
一般情況下,服務(wù)器收到客戶端的請求后,就會有一個 Http 的響應(yīng)消息,Http 響應(yīng)也由 4 部分組成,分別是:狀態(tài)行、響應(yīng)頭、空行 和 響應(yīng)實(shí)體。
圖中的首部字段和返回內(nèi)容(響應(yīng)實(shí)體)中間是有一個空行的。
3. 實(shí)現(xiàn)
3.1 效果
- Web 服務(wù)端監(jiān)聽 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 服務(wù)端,并監(jiān)聽 8090 端口
ServerSocket serverSocket = new ServerSocket(8090);
do {
//阻塞,直到有客戶端連接上,才會執(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)碼,表示請求成功
.append("HTTP/1.1 200 \r\n")
//告訴請求的客戶端,返回的內(nèi)容是 text/html 格式的
.append("Content-Type: text/html\r\n")
//首部字段和消息實(shí)體中間的空行
.append("\r\n")
//內(nèi)容部分
.append("hello tomcat");
//獲取客戶端通道的輸出流
OutputStream outputStream = socket.getOutputStream();
//往輸出流通道寫消息
outputStream.write(responseBuilder.toString().getBytes());
//流是有緩存機(jī)制的,寫消息的時候不一定立馬發(fā)出去,刷一下才能保證數(shù)據(jù)發(fā)送出去
outputStream.flush();
//關(guān)閉輸出流通道
outputStream.close();
}
}
上面的代碼初學(xué)者可以自己模仿著寫一個,相信對 Http 會有很深刻的體驗(yàn)。代碼中主要是監(jiān)聽連接,客戶端連接后,根據(jù) Http 協(xié)議進(jìn)行字符串的拼接返回給客戶端,客戶端瀏覽器接收到是標(biāo)準(zhǔn)的 Http 格式就會進(jìn)行渲染。
4. 小結(jié)
這邊的代碼雖然很簡單,但是最核心的 Http 服務(wù)雛形已經(jīng)展示出來了,成熟的 Http 服務(wù)可以在這基礎(chǔ)上對以下模塊進(jìn)行優(yōu)化:
- 針對請求事件的 線程 / IO 優(yōu)化;
- Servlet 協(xié)議支持;
- 配置獨(dú)立管理;
- Http協(xié)議內(nèi)容完善(比如緩存機(jī)制);
- 支持虛擬主機(jī)配置;
- 支持代理;
- rewrite 機(jī)制;
- 安全認(rèn)證。