Node.js:深刻淺出 http 與 stream

原文:Node.js:深刻淺出 http 與 streamhtml

前言

stream(流)是Node.js提供的又一個僅在服務區端可用的模塊,流是一種抽象的數據結構。Stream 是一個抽象接口,Node 中有不少對象實現了這個接口。例如,對http 服務器發起請求的request 對象就是一個 Stream,還有stdout(標準輸出流)。git

經過本文,你會知道 stream 是什麼,以及 strem 在 http 服務中發揮着什麼做用。github

1、stream(流)是什麼

1.1 舉個栗子

假設樓上有一桶水,想倒往樓下的水桶。直接往下倒,確定會灑出來,那麼在兩個水桶間加一根管子(pipe),就可讓樓上的水,逐漸地流到樓下的水桶內:web

561794-20190506200834583-356545808.png

1.2 爲何要使用 stream

看了前面稍微瞭解 Node.js 的同窗可能就要問了,流的做用不就是傳遞數據麼,也就是把一個地方數據拷貝到另外一個地方,不用流也能夠這樣實現:api

var water = fs.readFileSync('a.txt', {encoding: 'utf8'});
fs.writeFileSync('b.txt', water);複製代碼

但這樣作有個致命問題:瀏覽器

處理數據量較大的文件時不能分塊處理,致使速度慢,內存容易爆滿。bash

const rs = fs.createReadStream('a.mp4')
const ws = fs.createWriteStream('b.mp4')
rs.pipe(ws) // pipe自動調用了 data, end 等事件複製代碼

1.3 小結

其實 stream 不只能夠用來處理文件,它能夠處理任何一種數據提供源。下面來看看 stream 在 http 服務中,扮演着什麼樣的角色。服務器

2、深刻 http 模塊

先來幾個靈魂拷問:數據結構

  • 瀏覽器發起 http 請求,它屬於流嗎?
  • Node.js 的 Koa 框架,是基於流的嗎?
  • 接口轉發時,能用流來處理嗎?

下面一步步來深刻了解 Node.js 的 http 服務。app

2.1 Nodejs 中的 http 模塊

Node 能夠不須要 Apache、Nginx、IIS,自身就能夠搭建可靠的 http 服務。

建立一個簡單的 http 服務:

const http = require('http');

const server = http.createServer(function (req, res) {
  res.writeHead(200, {
    "Content-Type": "text/html;charset=UTF-8"
  })
  res.end("歡迎來到推啊!!!")
})

server.listen(3000, function () {
  console.log('listening port 3000')
})複製代碼

2.2 http.createServer 發生了什麼?

Node 是如何監聽 http 請求的,內部的處理機制是什麼。

http.createServer() 方法返回的是 http 模塊封裝的一個基於事件的 http 服務器。

http.createServer() 封裝了 http.Server()

const http = require('http');
const server = new http.Server();

server.on('request', function (req, res) {
  res.writeHead(200, {
    "Content-Type": "text/html;charset=UTF-8"
  })
  res.end("歡迎來到推啊!!!")
})

server.listen(3000, function () {
  console.log('listening port 3000');
});複製代碼

Koa 框架的本質是在 http 模塊的上層,封裝了本身的 ctx 對象,以及實現了洋蔥模型的中間件體系。

image.png

2.3 進一步瞭解 http 模塊

Node.js 中的 HTTP 接口旨在支持傳統上難以使用的協議的許多特性。 特別是,大塊的、可能塊編碼的消息。 接口永遠不會緩衝整個請求或響應,用戶可以流式傳輸數據。

爲了支持全部可能的 HTTP 應用程序,Node.js 的 HTTP API 都很是底層。 它僅進行流處理和消息解析。

http.Server() 是基於事件的,主要事件有:

  • request:最經常使用的事件,當客戶端請求到來時,該事件被觸發,提供req和res參數,表示請求和響應信息。
  • connection:當Tcp鏈接創建時,該事件被觸發,提供一個socket參數,是 net.Socket 的實例。
  • close

那麼,http 模塊是如何監聽獲取瀏覽器發來的請求呢?

這就須要進一步看看 Node.js 中,http 模塊是如何實現的了。

2.4 http 的下層:net 模塊

http.Server 繼承自: <net.Server>

net 模塊用於建立基於流的 TCP 或 IPC 的服務器(net.createServer())與客戶端(net.createConnection())

建立 TCP 服務:

const net = require('net');
const server = net.createServer((c) => {
  // 'connection' 監聽器。
  console.log('客戶端已鏈接');
  c.on('end', () => {
    console.log('客戶端已斷開鏈接');
  });
  c.write('你好\r\n');
  c.pipe(c);
});
server.on('error', (err) => {
  throw err;
});
server.listen(8124, () => {
  console.log('服務器已啓動');
});複製代碼

telnet 進行測試:

image.png

須要注意的是,net 模塊建立出來的是一個 TCP 服務,而它監聽接受到的數據,是一個 stream(流)

net 模塊接收到的內容是無法直接打印的,須要經過 strema 的方式來處理。

下面代碼接受並打印瀏覽器請求:

const net = require('net');
const fs = require('fs');

const server = net.createServer((c) => {
  let stream = fs.createWriteStream('test.txt');
  c.pipe(stream).on('finish', () => {
    console.log('Done');
  });
  c.on('error', (err) => {
    console.log(err);
  });
})

server.listen('4000', () => {
  console.log('服務器已啓動');
});複製代碼

在瀏覽器輸入 localhost:4000 後,在服務端目錄下生成 test.txt 文件:

GET / HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7,ko;q=0.6
Cookie: UM_distinctid=16e3ab5c769669-0aa68fde01b9b3-37647e05-13c680-16e3ab5c76a1ec; _9755xjdesxxd_=32; gdxidpyhxdE=6EK5fbKotSpp2Uiam1adlQW2uSUV%2FlvMMzX0Lo2iqcBIdSnV4Gf2CIYG2LZevagi2DhNAVlVmPjg4WyCs0EXamAO%2B3tQHgmnyrEj14hrmaV6Ev2MHAdWnIR9giTvXoIlRicy1MhUZ007j%5C%5C84xvU4PmLl0sEbHlkQ4Tvefuj9Ri%2B6D%2FZ%3A1574856606158; YD00636840014594%3AWM_NI=dKC1nbSYkvmP3bFoHMIDoTTp82bhdKlE9PKhaaJmK%2FO5hJLvRckcK9ONax49iBl7ML0AK8sRz90Qd%2Bexn2ABVSxcFOebaImloWcpuWVLQ8eRrrbgDEaIMdym983xRZqnV1c%3D; YD00636840014594%3AWM_NIKE=9ca17ae2e6ffcda170e2e6eeafd83a8aea9dd7ed5ca9b88ba3d44a828e8faaf23d8ab3ff9bec7c86b7a7d0b12af0fea7c3b92aa8958dd2ed6a9187a597f05cf1be8a90cb478b9ca1b5d77cfceeae89e741b09bf7b1ca64a3bf868eca258deba8abcf7e92bda584ca338d92af99b733ab9ea4d8d048f49f85d9f25abae700b2e721a7a8a2d3c44a968db884c545a7beafaaf068ad89ad91f85d96949fd9e85ab2aeae88c74fa3978385fc67fbe8fd99d152b3b29ad2e237e2a3; YD00636840014594%3AWM_TID=PZ3AtyPUGXFABQFEABZp7I3hmitUyn7z; CNZZDATA1278176396=591860064-1572943020-%7C1574943751; _yapi_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjI4LCJpYXQiOjE1NzY0NjQzMjIsImV4cCI6MTU3NzA2OTEyMn0.EEdBvJMRnnu2-_qli_Jqc2D6CtxocEzZjEz_hWv4qEk; _yapi_uid=28複製代碼

2.5 http 服務的完整鏈路

梳理一下 http 服務的整個調用鏈路:

  1. Nodejs 建立 http 服務,內部實際上是繼承了 net.Server 模塊。
  2. net 模塊內部其實是使用了 TCP 和 Stream 模塊,綁定 TCP 端口後,將數據以流的形式,經過 connection 事件回調給 http 模塊。
  3. http 模塊接收到 stream 後,調用 httpParser 解析,回調給 request 事件。

image.png

  • net 模塊主要作的事情,就是啓動 TCP 服務,將監聽到的請求信息,經過 connection 回調給 http 模塊。
  • http 模塊主要作的就是在 connection 回調中解析報文數據,而後觸發業務中的 request 回調,提供給開發者使用。

3、應用中的 stream

在深刻了解 http 模塊內部的基本原理後,能夠想一想咱們在應用場景中,能夠利用 stream(流)作到哪些事情

能夠嘗試本身實現一下平時接觸到的一些應用,如:

  • http-proxy-middleware轉發中間件
  • 大文件流式上傳服務
  • 流媒體播放服務
  • ......


有問題歡迎留言...

相關文章
相關標籤/搜索