靜態資源服務器是一種從服務器功能來分類的,顧名思義就是僅僅處理瀏覽器的靜態資源請求
這裏的靜態資源是指 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
所以服務器就須要以流的形式處理讀取文件和發送文件,簡單的說就是 讀一點文件,就發送一點,這是須要用到 stream。java
經過原生 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 靜態資源服務器。
歡迎訪問我的博客