Node.js開發(fā)靜態(tài)資源服務(wù)器
正文
在09年Node.js出來后,讓前端開發(fā)人員的開發(fā)路線變的不再那么單調(diào),經(jīng)過這么多年的發(fā)展,我們的開發(fā)基本已經(jīng)離不開Node.js,不管是用作于工具類的開發(fā),還是做完服務(wù)端的中間層,Node.js都占據(jù)了非常重要的地位,今天我們就一起通過原生的js+Node來實(shí)現(xiàn)一個簡單的靜態(tài)資源服務(wù),如果你還不了解這方面的知識,那就跟我一起來學(xué)習(xí)吧!
靜態(tài)資源服務(wù)器
Node.js經(jīng)過這么多年的發(fā)展,已經(jīng)有了很多很優(yōu)秀的基礎(chǔ)框架或類庫,像express.js、Koa.js、egg.js等,它們都是基于原生的Node.js來實(shí)現(xiàn)的,而我們之所以不選擇用這些框架,其實(shí)就是希望大家能夠了解前面這幾種框架是如何實(shí)現(xiàn)一個基礎(chǔ)的靜態(tài)資源服務(wù)的,只有當(dāng)我們了解了這其中的知識點(diǎn),再使用這些框架時才會更加得心應(yīng)手,下面我們一起看一下這個基礎(chǔ)的靜態(tài)資源服務(wù)該如何開發(fā)吧!
首先,我們要了解的是,既然是要開發(fā)靜態(tài)資源服務(wù),那么什么是靜態(tài)資源服務(wù)呢?簡單來說就是可以靜態(tài)訪問的一個資源服務(wù)器,而這些靜態(tài)資源包括但不限于類似html、css、js以及一些圖片資源,音視頻等等。我們能通過網(wǎng)絡(luò)直接訪問這些內(nèi)容,就是因?yàn)樗鼈兺ㄟ^靜態(tài)資源服務(wù)器將這些內(nèi)容掛載在網(wǎng)上。
我們首先要做的就是創(chuàng)建一個服務(wù),在Node中我們通過http模塊來創(chuàng)建一個服務(wù),http是Node中的一個基礎(chǔ)的API,相關(guān)的內(nèi)容可以查閱官方文檔,然后我們需要讀取本地的資源,那么就需要用到另外一個模塊fs。fs模塊能夠操作本地的資源文件,具體的內(nèi)容也可以通過官網(wǎng)的文檔進(jìn)行查看,下面我們一起來看一下相關(guān)的代碼,代碼如下:
const fs = require('fs');
const http = require('http');
http.createServer((req, res) => {
fs.readFile(__dirname + req.url, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('404: File not found');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(data);
}
});
}).listen(8000);
在上述的代碼中,我們首先通過http.createServer創(chuàng)建了一個服務(wù)器,并且通過listen(8000)來監(jiān)聽了8000端口的服務(wù),這樣我們就可以直接在瀏覽器中通過訪問localhost:8000來進(jìn)行訪問;在這個服務(wù)的內(nèi)部,我們通過fs.readFile方法來讀取文件,因?yàn)槲覀儧]有指定讀取的具體內(nèi)容,而是通過獲取請求信息來判斷我們要展示給用戶看到的內(nèi)容,所以最終會在頁面中展示404,如下圖所示:

上面的代碼已經(jīng)簡單了實(shí)現(xiàn)了一個靜態(tài)資源服務(wù)了,但是代碼看起來就比較零散,下面我們一起來對這個代碼進(jìn)行改造,通過模塊化的思想對代碼進(jìn)行升級,這樣不至于讓我們的代碼看起來像面條代碼。
模塊化
首先,我們讀取的文件地址可能跟我們的這個文件服務(wù)不在一個目錄中,為了解決問題問題,我們需要修改我們的文件目錄,而關(guān)于目錄的相關(guān)信息,就不得不用到Node中另外一個很重要的模塊path了。通過path模塊,我們能夠解析不同目錄中的內(nèi)容,一起來看一下修改后的代碼吧,如下:
const fs = require('fs');
const path = require('path');
const directoryName = './public';
const requestUrl = 'index.html';
const filePath = path.join(directoryName, requestUrl);
fs.readFile(filePath, (err, data) => {
// ...
});
我們通過path將靜態(tài)資源的根目錄拼接在一起,這樣當(dāng)我們使用fs.readFile來讀取這個文件時,不至于因?yàn)槁窂藉e誤而讀取不到正確的內(nèi)容。
接下來我們需要考慮的就是安全性的問題了,因?yàn)槲覀儾幌M脩裟軌蛟谖词跈?quán)的情況隨意訪問我們服務(wù)器中的任意資源,目前并不是不能訪問除了指定的目錄外的文件,這就是一個安全性的問題。為了解決這個問題,我們可以通過path模塊來檢測用戶請求的文件是否是可以訪問的,下面一起看看我們對上述代碼的改造,如下:
const path = require('path');
const directoryName = './public';
const root = path.normalize(path.resolve(directoryName));
const requestUrl = 'index.html';
const filePath = path.join(root, fileName);
const isPathUnderRoot = path
.normalize(path.resolve(filePath))
.startsWith(root);
上述代碼中,我們通過path.normalize來檢測這個文件地址是否包含在根路徑中,這樣就能確保用戶只能訪問到我們允許訪問的地址。同樣的,我們還可以通過檢查文件的類型來確保用戶無法訪問到一些敏感的文件。
為此,我們需要指定能夠訪問的文件類型的數(shù)組或?qū)ο螅挥挟?dāng)用戶訪問的文件在這個數(shù)組或者對象中,才能展示給用戶看到,因此我們還需要用到path模塊來檢查文件的后綴名,修改后代碼如下:
const path = require('path');
const types = ['html', 'css', 'js', 'json'];
const requestUrl = 'index.html';
const extension = path.extname(requestUrl).slice(1);
const isTypeSupported = types.includes(extension);
我們定義了一個文件類型的數(shù)組,其中就包含了用戶可以訪問的資源類型,然后我們通過path.extname來檢測用戶請求的文件的后綴,只有在這個類型文件的數(shù)組包含的類型,才會展示給用戶進(jìn)行查看。
當(dāng)然,當(dāng)我們訪問html的時候,我們一般都不會也不需要在瀏覽器中輸入xxx.html這樣的后綴,因此我們還需要對html這樣的后綴做省略,讓用戶可以直接通過網(wǎng)址就能訪問正確的頁碼,而不需要添加.html。下面我們一起看一下改造后的代碼,如下:
const fs = require('fs');
const path = require('path');
const directoryName = './public';
const root = path.normalize(path.resolve(directoryName));
const extension = path.extname(req.url).slice(1);
let fileName = requestUrl;
if (requestUrl === '/') {
fileName = 'index.html';
} else if (!extension) {
try {
fs.accessSync(path.join(root, requestUrl + '.html'), fs.constants.F_OK);
fileName = requestUrl + '.html';
} catch (e) {
fileName = path.join(requestUrl, 'index.html');
}
}
上述的代碼中,我們通過判斷用戶訪問的文件類型是否包含在前面的數(shù)組中,當(dāng)用戶訪問的類型不包含時,我們通過fs.accessSync來測試用戶訪問的文件是否是允許訪問的,如果允許訪問則直接返回一個.html的文件,如果用戶訪問的地址是不允許訪問的,則直接返回index.html。
最后,當(dāng)我們將前面所有的內(nèi)容都完成后,我們可以將這些內(nèi)容都整合在一起,下面我們一起來看一下最終完成的這個靜態(tài)資源服務(wù)的完整代碼吧,如下:
const fs = require('fs');
const http = require('http');
const path = require('path');
// 靜態(tài)資源服務(wù)器地址
const port = 8000;
// 靜態(tài)資源文件夾
const directoryName = './public';
// 允許訪問的文件類型
const types = {
html: 'text/html',
css: 'text/css',
js: 'application/javascript',
png: 'image/png',
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
gif: 'image/gif',
json: 'application/json',
xml: 'application/xml',
};
// 靜態(tài)資源文件根路徑
const root = path.normalize(path.resolve(directoryName));
// 創(chuàng)建靜態(tài)資源服務(wù)器
const server = http.createServer((req, res) => {
// 獲取訪問的文件類型
const extension = path.extname(req.url).slice(1);
// 文件類型后綴
const type = extension ? types[extension] : types.html;
// 是否支持的文件類型
const supportedExtension = Boolean(type);
// 如果這個文件類型不允許訪問,則直接返回404
if (!supportedExtension) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('404: File not found');
return;
}
// 通過url獲取訪問的文件名稱
let fileName = req.url;
// 如果訪問的路徑是 /
if (req.url === '/') {
// 則文件名是 index.html
fileName = 'index.html';
} else if (!extension) {
try {
// 檢測文件是否允許訪問
fs.accessSync(path.join(root, req.url + '.html'), fs.constants.F_OK);
// 當(dāng)允許訪問時,則返回對應(yīng)的頁面
fileName = req.url + '.html';
} catch (e) {
// 否則直接返回 index.html
fileName = path.join(req.url, 'index.html');
}
}
const filePath = path.join(root, fileName);
const isPathUnderRoot = path.normalize(path.resolve(filePath)).startsWith(root);
if (!isPathUnderRoot) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('404: File not found');
return;
}
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('404: File not found');
} else {
res.writeHead(200, { 'Content-Type': type });
res.end(data);
}
});
});
server.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
最終我們通過不到100行的代碼就實(shí)現(xiàn)了這個靜態(tài)資源服務(wù),我們可以看一下具體的運(yùn)行效果。

當(dāng)我們訪問首頁時,就直接展示默認(rèn)的index.html中的文件,如果我們訪問的內(nèi)容不允許訪問,則直接顯示404,如下所示:

最后
我們只通過Node.js中一些簡單的API就開發(fā)了一個基礎(chǔ)的靜態(tài)資源服務(wù)器,也讓大家了解了一些Node.js相關(guān)的基礎(chǔ)操作,了解這些基礎(chǔ)的操作不僅有利于提高我們自身的知識儲備,也更有利于我們在實(shí)際開發(fā)中少踩一些坑。
以上就是Node.js開發(fā)靜態(tài)資源服務(wù)器的詳細(xì)內(nèi)容,更多關(guān)于Node.js靜態(tài)資源服務(wù)器的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Node.js之HTTP服務(wù)端和客戶端實(shí)現(xiàn)方式
這篇文章主要介紹了Node.js之HTTP服務(wù)端和客戶端實(shí)現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-09-09
node.js配置Token驗(yàn)證的2種方式總結(jié)
token驗(yàn)證,在設(shè)計登錄注冊和一些權(quán)限接口時發(fā)揮作用,下面這篇文章主要給大家介紹了關(guān)于node.js配置Token驗(yàn)證的2種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
npm報錯"A?complete?log?of?this?run?can?be?found?
這篇文章主要給大家介紹了關(guān)于npm報錯"A?complete?log?of?this?run?can?be?found?in:"的解決辦法,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-04-04
xtemplate node.js 的使用方法實(shí)例解析
這篇文章主要介紹了xtemplate node.js 的使用方法實(shí)例說明,非常不錯,介紹的非常詳細(xì),具有參考借鑒價值,需要的朋友可以參考下2016-08-08
node.js使用net模塊創(chuàng)建服務(wù)器和客戶端示例【基于TCP協(xié)議】
這篇文章主要介紹了node.js使用net模塊創(chuàng)建服務(wù)器和客戶端,結(jié)合實(shí)例形式分析了node.js使用net模塊實(shí)現(xiàn)TCP客戶端與服務(wù)器端通信的相關(guān)操作技巧,需要的朋友可以參考下2020-02-02

