您好,登錄后才能下訂單哦!
靜態文件服務器實現
nodejs不僅僅可以用來寫服務端接口,用來做靜態文件服務器替代nginx的功能, 也是分分鐘可以搞定的。 話不多說,先上代碼:
var server=http.createServer(function (req,res){ fs.createReadStream(Path.resolve(__dirname,"."+req.url)).pipe(res); })
在項目根目錄建一個hello.html文件測試一下 hello.html內容如下:
<h2>hello,world</h2>
node app.js運行,打開瀏覽器訪問一下: http://localhost/hello.html
我們再回頭審視一下代碼,的確就只有這么簡單,這要歸功于node Stream類 pipe方法的強大,fs.createReadStream讀取本地文件創建一個可讀流(ReadStream類的實例),再使用pipe導流到res響應流,res是一個http.ServerResponse類的實例,是一個可寫流,繼承自 Stream類
http.ServerResponse類的繼承關系如下:
安全性考慮
上述代碼實現靜態文件服務器后,意味著項目根目錄下所有的文件(遞歸)都可以通過瀏覽器直接訪問和下載了,這樣會帶來一些安全性的問題,想想看,你的服務器端代碼和配置文件都能通過瀏覽器直接下載了,因此需要在代碼里加一些限制,例如只能訪問特定的目錄下的文件和特定擴展名的文件,這樣還不夠,參考OWasp Top 10安全風險(第4條-不安全的對象直接引用),攻擊者仍然可以通過../../目錄回溯的方法訪問到其它目錄,對于訪問路徑中包含..的也要全部過濾掉。
實現mine type
mime type是指http 響應頭中的content-type字段,它決定了瀏覽器如何解析文件,是直接當做純文件顯示(text/plain),還是做為html文件渲染(text/html),或者當做二進制文件下載,沒有輸出正確的mine type,可能導致圖片文件無法顯示,字體文件無效,視頻文件無法播放的問題。要實現起來也十分簡單,只需要做一個映射表,不同文件擴展名,在響應頭的content-type字段中輸出對應的mine type就行了。
完整代碼如下:
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(相對時間)兩個用于控制緩存過期時間的參數,瀏覽器下次請求該文件時,分為以下幾種情況:
邏輯分支較多,但都是日期比對,搞清楚緩存協商過程比較容易寫出來,有興趣的同學可以自行實現
高性能靜態文件服務器優化
如果要做一個高性能的靜態文件服務器僅實現gzip和緩存協商是不夠的,涉及到本地文件的頻繁讀取,高并發下I/O必定成為瓶頸,考慮到服務器上的文件是很少更新的, 可以用Buffer把文件流緩存到內存中,每次請求時先在內存中查找匹配項,如果命中了直接從內存中返回,避免了讀取磁盤,gzip也不用壓縮了,直接用壓縮好的文件流返回,可以成倍的大幅提升性能。當然如果文件太多了,內存也會飆升,需要考慮淘汰算法,只緩存訪問次數高的文件,剔除低訪問量的文件。
采用fs.watch監控目錄文件的變化,如果文件有更新,則刪掉緩存。
小結
Node.js 內置的pipe方法可以非常簡便的實現將服務器本地文件輸出到http 響應流中,gzip壓縮也同樣可以通過pipe實現,再配合輸出mine type 實現的靜態服務器已經可以滿足一般業務的使用。如果要實現高性能的靜態文件服務器,還需要實現客戶端緩存、服務端緩存功能(本文提供了思路,按圖索驥也非難事)。
最后,推薦一下個人的開源項目, node.js web開發框架,已包含本文靜態文件服務器的功能 webcontext: https://github.com/windyfancy/webcontext
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。