Node.js一行代碼實現靜態文件服務器

靜態文件服務器實現

nodejs不只僅能夠用來寫服務端接口,用來作靜態文件服務器替代nginx的功能, 也是分分鐘能夠搞定的。 話很少說,先上代碼:css

var server=http.createServer(function (req,res){
    fs.createReadStream(Path.resolve(__dirname,"."+req.url)).pipe(res);
})
複製代碼

在項目根目錄建一個hello.html文件測試一下 hello.html內容以下:html

<h1>hello,world</h1>
複製代碼

node app.js運行,打開瀏覽器訪問一下:http://localhost/hello.htmlnode

咱們再回頭審視一下代碼,的確就只有這麼簡單,這要歸功於node Stream類 pipe方法的強大,fs.createReadStream讀取本地文件建立一個可讀流(ReadStream類的實例),再使用pipe導流到res響應流,res是一個http.ServerResponse類的實例,是一個可寫流,繼承自 Stream類nginx

http.ServerResponse類的繼承關係以下:git

安全性考慮

上述代碼實現靜態文件服務器後,意味着項目根目錄下全部的文件(遞歸)均可以經過瀏覽器直接訪問和下載了,這樣會帶來一些安全性的問題,想一想看,你的服務器端代碼和配置文件都能經過瀏覽器直接下載了,所以須要在代碼里加一些限制,例如只能訪問特定的目錄下的文件和特定擴展名的文件,這樣還不夠,參考OWasp Top 10安全風險(第4條-不安全的對象直接引用),攻擊者仍然能夠經過../../目錄回溯的方法訪問到其它目錄,對於訪問路徑中包含..的也要所有過濾掉。github

實現mine type

mime type是指http 響應頭中的content-type字段,它決定了瀏覽器如何解析文件,是直接當作純文件顯示(text/plain),仍是作爲html文件渲染(text/html),或者當作二進制文件下載,沒有輸出正確的mine type,可能致使圖片文件沒法顯示,字體文件無效,視頻文件沒法播放的問題。要實現起來也十分簡單,只須要作一個映射表,不一樣文件擴展名,在響應頭的content-type字段中輸出對應的mine type就好了。web

完整代碼以下:算法

const http=require("http");
const Path=require("path");
const fs=require("fs");

var server=http.createServer(function (req,res){
    const fileName=Path.resolve(__dirname,"."+req.url);
    const extName=Path.extname(fileName).substr(1);

    if (fs.existsSync(fileName)) { //判斷本地文件是否存在
        var mineTypeMap={
            html:'text/html;charset=utf-8',
            htm:'text/html;charset=utf-8',
            xml:"text/xml;charset=utf-8",
            png:"image/png",
            jpg:"image/jpeg",
            jpeg:"image/jpeg",
            gif:"image/gif",
            css:"text/css;charset=utf-8",
            txt:"text/plain;charset=utf-8",
            mp3:"audio/mpeg",
            mp4:"video/mp4",
            ico:"image/x-icon",
            tif:"image/tiff",
            svg:"image/svg+xml",
            zip:"application/zip",
            ttf:"font/ttf",
            woff:"font/woff",
            woff2:"font/woff2",

        }
        if (mineTypeMap[extName]) {
            res.setHeader('Content-Type', mineTypeMap[extName]);
        }
        var stream=fs.createReadStream(fileName);
        stream.pipe(res);
    }

    
})
server.listen(80);
複製代碼

實現gzip

對於文本類型的文件,如html,js,css,採用gzip壓縮能夠大幅減小傳輸量,提高服務器傳輸性能,固然這會損耗一點服務器的cpu性能作爲代價,若是客戶端瀏覽器支持gzip壓縮,則會在請求頭的accept-encoding中攜帶gzip關鍵字,用node自帶的zlib類就能夠實現gzip壓縮了,只要在stream.pip實多加一層,先導流到gzip流,再導出到res流,固然,還要在響應頭中添加Content-Encoding爲gzip,這樣瀏覽器才能正確識別到http body是採用gzip算法壓縮的,並進行自動解壓縮。瀏覽器

代碼以下:緩存

const zlib = require('zlib');

if (req.headers["accept-encoding"].indexOf("gzip")>=0 && (extName=="js" || extName=="css" || extName=="html"))) {
     res.setHeader('Content-Encoding', "gzip");
     const gzip = zlib.createGzip();
     stream.pipe(gzip).pipe(res);
 }
複製代碼

客戶端緩存

http協議的緩存協商流程比較長,最終在響應頭中生成expire(絕對時間)和cache-control(相對時間)兩個用於控制緩存過時時間的參數,瀏覽器下次請求該文件時,分爲如下幾種狀況:

  1. 若是沒到過時時間,瀏覽器不會請求文件直接讀緩存
  2. 若是已到過時時間,則會在請求頭中last-modified字段攜帶文件的最後修改日期,若是對比時間戳與服務器文件一致,則HTTP 返回 304: Not Modified
  3. 若是按下f5刷新,會在請求頭中if-modified-since字段中攜帶緩存的過時時間,若是對比時間戳與服務器文件一致,則HTTP 返回 304: Not Modified
  4. ctrl+f5刷新,請求頭中攜帶 cache-control: no-cache,強制禁用緩存。從新下載文件

邏輯分支較多,但都是日期比對,搞清楚緩存協商過程比較容易寫出來,有興趣的同窗能夠自行實現

高性能靜態文件服務器優化

若是要作一個高性能的靜態文件服務器僅實現gzip和緩存協商是不夠的,涉及到本地文件的頻繁讀取,高併發下I/O一定成爲瓶頸,考慮到服務器上的文件是不多更新的, 能夠用Buffer把文件流緩存到內存中,每次請求時先在內存中查找匹配項,若是命中了直接從內存中返回,避免了讀取磁盤,gzip也不用壓縮了,直接用壓縮好的文件流返回,能夠成倍的大幅提高性能。固然若是文件太多了,內存也會飆升,須要考慮淘汰算法,只緩存訪問次數高的文件,剔除低訪問量的文件。

採用fs.watch監控目錄文件的變化,若是文件有更新,則刪掉緩存。

小結

Node.js 內置的pipe方法能夠很是簡便的實現將服務器本地文件輸出到http 響應流中,gzip壓縮也一樣能夠經過pipe實現,再配合輸出mine type 實現的靜態服務器已經能夠知足通常業務的使用。若是要實現高性能的靜態文件服務器,還須要實現客戶端緩存、服務端緩存功能(本文提供了思路,按圖索驥也非難事)。

最後,推薦一下我的的開源項目, node.js web開發框架,已包含本文靜態文件服務器的功能 webcontext: github.com/windyfancy/…

相關文章
相關標籤/搜索