手動搭建Node服務器

Node 靜態資源服務器

靜態資源服務器是一種從服務器功能來分類的,顧名思義就是僅僅處理瀏覽器的靜態資源請求
這裏的靜態資源是指 html、js、css、圖片、音視頻。。。等文件

建立服務器

  經過原生的 http 模塊,啓動一個服務,處理文件請求。javascript

const http = require('http')
const fs = require('fs')
const url = require('url')

const server = http.createServer((req, res) => {
  const { pathname } = url.parse(req.url, true)
  fs.readFile(`static${pathname}`, (err, data) => { // 假設靜態資源存放在 static 的目錄下
    if (err) {
      res.writeHeader(404)
      res.write('Not Found')
    } else {
      res.write(data)
    }
    res.end()
  })
})

server.listen(8080)

  其實經過 fs.readFile 的方式讀取文件再發送給瀏覽器實際上是一個很是耗時的過程,由於當文件很大的時候,只有當文件所有讀到內存後纔會開始發送給瀏覽器。css

  這樣瀏覽器的這個請求就有很大一部分時間浪費在了等待服務器讀取文件,這樣就限制服務器處理能力,沒法應對高併發以及大文件的請求。 html

  所以服務器就須要以流的形式處理讀取文件和發送文件,簡單的說就是 讀一點文件,就發送一點,這是須要用到 streamjava

流形式的靜態資源服務器

  經過原生 fs 模塊提供的 createStream 方法,使用流的形式處理靜態資源的讀取與發送,以提升服務器的性能。node

const http = require('http')
const fs = require('fs')
const url = require('url')

const server = http.createServer((req, res) => {
  const { pathname } = url.parse(req.url, true)
  const stream = fs.createReadStream(`static${pathname}`)
  stream.pipe(res)

  stream.on('error', error => {
    res.writeHeader(404)
    res.write('Not Found')
    res.end()
  })
})

server.listen(8080)

  可是這種方式有個很大的問題,那就是傳輸的文件沒有被壓縮,這樣線上的服務器會浪費很大的帶寬,那就是在浪費成本。git

壓縮形式的靜態資源服務器

  經過原生的 zlib 模塊,將讀取的流文件壓縮後在發送給瀏覽器。github

const http = require('http')
const fs = require('fs')
const url = require('url')
const zlib = require('zlib')

const server = http.createServer((req, res) => {
  const gzip = zlib.createGzip()
  const { pathname } = url.parse(req.url, true)
  const stream = fs.createReadStream(`static${pathname}`)

  res.setHeader('content-encoding', 'gzip')
  stream.pipe(gzip).pipe(res)

  stream.on('error', error => {
    res.writeHeader(404)
    res.write('Not Found')
    res.end()
  })
})

server.listen(8080)

  所以能夠查看下面兩張圖片的對比,更加明確先後的差距瀏覽器

文件大小比較

文件大小比較

響應頭比較

響應頭比較

  能夠粗略的計算下,壓縮先後的節約的多少的空間:緩存

(262 - 77.8)/262 = 0.703
所以,能夠看出壓縮的這個功能購節約大約 70% 的帶寬成本,真的是沒想到 !!!

帶緩存策略的靜態資源服務器

  上面的靜態資源服務器可以很好的解決首次拿到資源以及資源發生更新時的狀況,可是當靜態資源文件沒有發生變化時,這個時候其實能夠直接使用緩存文件,可以讓用戶有更快的體驗,以及減少服務器的壓力。服務器

服務器緩存策略

  爲了解決上述的未更新的靜態資源從新獲取的問題,能夠經過設置不一樣的緩存策略來解決,一般有 強緩存協商緩存

那麼協商緩存是什麼?
  一、第一次請求資源時,服務器會在響應頭(大小寫不敏感)中加上 Last-Modified 字段,表示資源最後一次更新的時間;
  二、之後瀏覽器再次請求該資源時,會在請求頭中自動設置 If-Modified-Since 字段,值爲上次服務器返回的 Last-Modified 的值;
  三、當服務器在請求頭中拿到了  If-Modified-Since ,就會判斷該資源的最後更新時間是否相同:
      若是相同,則返回 304 瀏覽器直接使用緩存;
      若是不相同,則會重新設置 Last-Modified 的值;

這裏以 協商緩存 爲例,看一下 node 服務器是實現緩存策略的。

const http = require('http')
const fs = require('fs')
const url = require('url')
const zlib = require('zlib')

const server = http.createServer((req, res) => {
  const { pathname } = url.parse(req.url, true)
  console.log(pathname)

  fs.stat(`static${pathname}`, (err, stat) => { // fs.stat 可以獲取到文件的基本信息
    if (err) {
      send404(res)
    } else {
      const time = stat.mtime.toUTCString()
      const if_modified_since = req.headers['if-modified-since'] // headers 中的頭必須爲小寫

      if (if_modified_since) { // 比較下最後更新時間
        const client_time = Math.floor(new Date(if_modified_since).getTime()/1000)
        const server_time = Math.floor(new Date(time).getTime()/1000)
        if (server_time <= client_time) {
          send304(res)
        } else {
          sendData(pathname, time, res)
        }
      } else {
        sendData(pathname, time, res)
      }
    }
  })
})

server.listen(8080)

const send404 = (res) => {
  res.writeHeader(404)
  res.write('Not Found')
  res.end()
}

const send304 = (res) => {
  res.writeHeader(304)
  res.write('Not Modified')
  res.end()
}

const sendData = (pathname, time, res) => {
  const stream = fs.createReadStream(`static${pathname}`)
  res.on('error', error => {
    send404(response)
  })

  res.setHeader('Last-Modified', time)
  stream.pipe(res)
}
帶有緩存策略的先後請求頭

帶有緩存策略的先後請求頭

多進程處理請求的靜態資源服務器

  當純手工搭建的服務器通過這一系列的優化以後,已經擁有了不錯的性能了,可是還有個問題,那就是這個服務不夠健壯,好比碰見未知錯誤時,會直接掛掉致使整個服務不可用。對於一臺線上的服務器,這樣確定是不行的,那又該如何解決呢?
  答案就是 使用多進程的方式處理請求
  NodeJS 的 cluster 模塊 能夠建立共享服務器端口的子進程。咱們都知道 NodeJS 實例是運行在單個線程中,咱們能夠充分的利用多核系統,去啓用一組 NodeJS 進程去處理負載任務。

const http = require('http')
const os = require('os')
const cluster = require('cluster')

if (cluster.isMaster) {
  const cpu_counts = os.cpus().length
  for(let i=0; i<cpu_counts; i++) {
    cluster.fork() // 父進程 建立 子進程
  }
} else { // 子進程專門用來處理請求
  const server = http.createServer((req, res) => {
    // 處理請求。。。
  })
  server.listen(8080) // 父子進程間,能夠監聽相同的端口
}

  綜上,咱們就能夠本身手動搭建出一個,節約帶寬和流量的前提下,性能還算能夠的,也不容易宕機的 Web Node 靜態資源服務器

歡迎訪問我的博客

相關文章
相關標籤/搜索