手把手教你用 Node 實(shí)現(xiàn) HTTP 協(xié)議(二)

手把手教你用 Node 實(shí)現(xiàn) HTTP 協(xié)議(二)

這一章我們重點(diǎn)講解如何解析 HTTP 請求報(bào)文,HTTP 報(bào)文主要分為三個(gè)部分:起始行、首部字段、內(nèi)容主體。

這里我使用 postman 發(fā)起下圖的 POST 請求,然后看看請求報(bào)文的格式是什么樣的

POST 請求

收到的請求報(bào)文格式是這樣的:

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" },而中間的內(nèi)容就是 HTTP 報(bào)文的請求首部。

我們已經(jīng)把一個(gè)復(fù)雜的 HTTP 報(bào)文分解成了多個(gè)簡單的部分,那我們希望能得到一個(gè)可用的 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}"
}

我們新建一個(gè) src/HttpParser.ts 文件來進(jìn)行解析(如果你沒有配置 Node TS 運(yùn)行環(huán)境,那么你可以基于這份已完成的框架進(jìn)行重新開發(fā)),我們先定義我們最后解析的格式為 HttpMessage

export type Headers = { [key: string]: string };

export type HttpMessage = {
  method: string;
  url: string;
  version: string;
  headers: Headers;
  body: string;
}

我們的 HttpParser 類應(yīng)該有兩個(gè)屬性,一個(gè)用于接收報(bào)文流的 message,一個(gè)承載解析后的報(bào)文 httpMessage,然后應(yīng)該還有一個(gè)解析的函數(shù) parse,所以整體結(jié)構(gòu)看起來應(yīng)該是像這樣的:

class HttpParser {
  private message: string;
  public httpMessage: HttpMessage = null;

  constructor(message: string) {
    this.message = message;
    this.parse();
  }

  private parse(): void {
    // ...
  }
}

export default HttpParser;

從上面可以看出,其實(shí)我們的關(guān)鍵性函數(shù)就是 parse,那我們怎么去解析這個(gè)報(bào)文呢?從第一章的知識(shí)可以得知,起步行和首部就是由行分隔的 ASCII 文本。每行都以一個(gè) 由兩個(gè)字符組成的行終止序列作為結(jié)束,其中包括一個(gè)回車符(ASCII 碼 13)和一個(gè)換行符(ASCII 碼 10)。這個(gè)行終止序列可以寫作 CRLF。這個(gè) CRLF 在代碼中的表示就是 \r\n,由此可知,我們只需要用 String.prototype.split 函數(shù)傳入 \r\n 就可以得到各個(gè)部分,再利用三個(gè)函數(shù)分別處理起始行、首部和主體字段即可,這里的實(shí)現(xià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;
  }
}

最后通過調(diào)用 new HttpParser(message).httpMessage 就可以從 HTTP 報(bào)文中得到序列化后的請求報(bào)文了。

對請求報(bào)文我們做了序列化,對響應(yīng)報(bào)文我們也應(yīng)該做一個(gè)反序列化,最后輸出的響應(yīng)報(bào)文格式應(yīng)該是這樣的(根據(jù)我們第一章的需求):

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}"}

這個(gè)反序列化的實(shí)現(xiàn)交由讀者去自行實(shí)現(xiàn)作為練習(xí),我們在最后一章的時(shí)候會(huì)講解如何完成一個(gè)客戶-服務(wù)器模式中的服務(wù)器應(yīng)用,接收來自客戶端的請求,并響應(yīng)處理結(jié)果。

原文地址,歡迎 Star

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容