這一章咱們重點講解如何解析 HTTP 請求報文,HTTP 報文主要分爲三個部分:起始行、首部字段、內容主體。git
這裏我使用 postman 發起下圖的 POST 請求,而後看看請求報文的格式是什麼樣的github
收到的請求報文格式是這樣的:json
POST / HTTP/1.1
Content-Type: application/json
User-Agent: PostmanRuntime/7.17.1
Accept: */*
Cache-Control: no-cache
Postman-Token: 5041de72-27c3-44c6-99e8-c04c306b11ef
Host: localhost:8888
Accept-Encoding: gzip, deflate
Content-Length: 19
Connection: keep-alive
{
"name": "jack"
}
複製代碼
咱們能夠先看第一行,包含的信息有請求的方法爲 POST
,請求的路徑爲 /
,HTTP 版本爲 1.1
;而後咱們看最後一行,最後一行包含了請求的主體 { "name": "jack" }
,而中間的內容就是 HTTP 報文的請求首部。bash
咱們已經把一個複雜的 HTTP 報文分解成了多個簡單的部分,那咱們但願能獲得一個可用的 JSON 格式,最終效果看起來是這樣的:服務器
{
"method": "POST",
"url": "/",
"version": "HTTP/1.1",
"headers": {
"content-type": "application/json",
"user-agent": "PostmanRuntime/7.17.1",
"accept": "*/*",
"cache-control": "no-cache",
"postman-token": "5041de72-27c3-44c6-99e8-c04c306b11ef",
"host": "localhost",
"accept-encoding": "gzip, deflate",
"content-length": "19",
"connection": "keep-alive"
},
"body": "{\n\t\"name\": \"jack\"\n}"
}
複製代碼
咱們新建一個 src/HttpParser.ts
文件來進行解析(若是你沒有配置 Node TS 運行環境,那麼你能夠基於這份已完成的框架進行從新開發),咱們先定義咱們最後解析的格式爲 HttpMessage
app
export type Headers = { [key: string]: string };
export type HttpMessage = {
method: string;
url: string;
version: string;
headers: Headers;
body: string;
}
複製代碼
咱們的 HttpParser
類應該有兩個屬性,一個用於接收報文流的 message
,一個承載解析後的報文 httpMessage
,而後應該還有一個解析的函數 parse
,因此總體結構看起來應該是像這樣的:框架
class HttpParser {
private message: string;
public httpMessage: HttpMessage = null;
constructor(message: string) {
this.message = message;
this.parse();
}
private parse(): void {
// ...
}
}
export default HttpParser;
複製代碼
從上面能夠看出,其實咱們的關鍵性函數就是 parse
,那咱們怎麼去解析這個報文呢?從第一章的知識能夠得知,起步行和首部就是由行分隔的 ASCII 文本。每行都以一個 由兩個字符組成的行終止序列做爲結束,其中包括一個回車符(ASCII 碼 13)和一個換行符(ASCII 碼 10)。這個行終止序列能夠寫做 CRLF。這個 CRLF 在代碼中的表示就是 \r\n
,由此可知,咱們只須要用 String.prototype.split
函數傳入 \r\n
就能夠獲得各個部分,再利用三個函數分別處理起始行、首部和主體字段便可,這裏的實現仍是比較簡單的,因此就直接貼代碼出來了函數
class HttpParser {
private message: string;
public httpMessage: HttpMessage = null;
constructor(message: string) {
this.message = message;
this.parse();
}
private parse(): void {
this.httpMessage = {} as HttpMessage;
const messages = this.message.split('\r\n');
const [head] = messages;
const headers = messages.slice(1, -2);
const [body] = messages.slice(-1);
this.parseHead(head);
this.parseHeaders(headers);
this.parseBody(body);
}
private parseHead(headStr: string) {
const [method, url, version] = headStr.split(' ');
this.httpMessage.method = method;
this.httpMessage.url = url;
this.httpMessage.version = version;
}
private parseHeaders(headerStrList: string[]) {
this.httpMessage.headers = {};
for (let i = 0; i < headerStrList.length; i++) {
const header = headerStrList[i];
let [key, value] = header.split(":");
key = key.toLocaleLowerCase();
value = value.trim();
this.httpMessage.headers[key] = value;
}
}
private parseBody(bodyStr: string) {
if (!bodyStr) return this.httpMessage.body = "";
this.httpMessage.body = bodyStr;
}
}
複製代碼
最後經過調用 new HttpParser(message).httpMessage
就能夠從 HTTP 報文中獲得序列化後的請求報文了。post
對請求報文咱們作了序列化,對響應報文咱們也應該作一個反序列化,最後輸出的響應報文格式應該是這樣的(根據咱們第一章的需求):ui
HTTP/1.1 200 ok
content-type: application/json
{"method":"POST","url":"/","version":"HTTP/1.1","headers":{"content-type":"application/json","user-agent":"PostmanRuntime/7.17.1","accept":"*/*","cache-control":"no-cache","postman-token":"5cd74556-35fe-488d-a363-b4754992da60","host":"localhost","accept-encoding":"gzip, deflate","content-length":"19","connection":"keep-alive"},"body":"{\n\t\"name\": \"jack\"\n}"}
複製代碼
這個反序列化的實現交由讀者去自行實現做爲練習,咱們在最後一章的時候會講解如何完成一個客戶-服務器模式中的服務器應用,接收來自客戶端的請求,並響應處理結果。