Node.js開發靜態資源服務器

正文

在09年Node.js出來後,讓前端開發人員的開發路線變的不再那麼單調,經過這麼多年的發展,我們的開發基本已經離不開Node.js,不管是用作於工具類的開發,還是做完服務端的中間層,Node.js都占據瞭非常重要的地位,今天我們就一起通過原生的js+Node來實現一個簡單的靜態資源服務,如果你還不瞭解這方面的知識,那就跟我一起來學習吧!

靜態資源服務器

Node.js經過這麼多年的發展,已經有瞭很多很優秀的基礎框架或類庫,像express.jsKoa.jsegg.js等,它們都是基於原生的Node.js來實現的,而我們之所以不選擇用這些框架,其實就是希望大傢能夠瞭解前面這幾種框架是如何實現一個基礎的靜態資源服務的,隻有當我們瞭解瞭這其中的知識點,再使用這些框架時才會更加得心應手,下面我們一起看一下這個基礎的靜態資源服務該如何開發吧!

首先,我們要瞭解的是,既然是要開發靜態資源服務,那麼什麼是靜態資源服務呢?簡單來說就是可以靜態訪問的一個資源服務器,而這些靜態資源包括但不限於類似htmlcssjs以及一些圖片資源,音視頻等等。我們能通過網絡直接訪問這些內容,就是因為它們通過靜態資源服務器將這些內容掛載在網上。

我們首先要做的就是創建一個服務,在Node中我們通過http模塊來創建一個服務,httpNode中的一個基礎的API,相關的內容可以查閱官方文檔,然後我們需要讀取本地的資源,那麼就需要用到另外一個模塊fsfs模塊能夠操作本地的資源文件,具體的內容也可以通過官網的文檔進行查看,下面我們一起來看一下相關的代碼,代碼如下:

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創建瞭一個服務器,並且通過listen(8000)來監聽瞭8000端口的服務,這樣我們就可以直接在瀏覽器中通過訪問localhost:8000來進行訪問;在這個服務的內部,我們通過fs.readFile方法來讀取文件,因為我們沒有指定讀取的具體內容,而是通過獲取請求信息來判斷我們要展示給用戶看到的內容,所以最終會在頁面中展示404,如下圖所示:

上面的代碼已經簡單瞭實現瞭一個靜態資源服務瞭,但是代碼看起來就比較零散,下面我們一起來對這個代碼進行改造,通過模塊化的思想對代碼進行升級,這樣不至於讓我們的代碼看起來像面條代碼。

模塊化

首先,我們讀取的文件地址可能跟我們的這個文件服務不在一個目錄中,為瞭解決問題問題,我們需要修改我們的文件目錄,而關於目錄的相關信息,就不得不用到Node中另外一個很重要的模塊path瞭。通過path模塊,我們能夠解析不同目錄中的內容,一起來看一下修改後的代碼吧,如下:

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將靜態資源的根目錄拼接在一起,這樣當我們使用fs.readFile來讀取這個文件時,不至於因為路徑錯誤而讀取不到正確的內容。

接下來我們需要考慮的就是安全性的問題瞭,因為我們不希望用戶能夠在未授權的情況隨意訪問我們服務器中的任意資源,目前並不是不能訪問除瞭指定的目錄外的文件,這就是一個安全性的問題。為瞭解決這個問題,我們可以通過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來檢測這個文件地址是否包含在根路徑中,這樣就能確保用戶隻能訪問到我們允許訪問的地址。同樣的,我們還可以通過檢查文件的類型來確保用戶無法訪問到一些敏感的文件。

為此,我們需要指定能夠訪問的文件類型的數組或對象,隻有當用戶訪問的文件在這個數組或者對象中,才能展示給用戶看到,因此我們還需要用到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);

我們定義瞭一個文件類型的數組,其中就包含瞭用戶可以訪問的資源類型,然後我們通過path.extname來檢測用戶請求的文件的後綴,隻有在這個類型文件的數組包含的類型,才會展示給用戶進行查看。

當然,當我們訪問html的時候,我們一般都不會也不需要在瀏覽器中輸入xxx.html這樣的後綴,因此我們還需要對html這樣的後綴做省略,讓用戶可以直接通過網址就能訪問正確的頁碼,而不需要添加.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');
    }
}

上述的代碼中,我們通過判斷用戶訪問的文件類型是否包含在前面的數組中,當用戶訪問的類型不包含時,我們通過fs.accessSync來測試用戶訪問的文件是否是允許訪問的,如果允許訪問則直接返回一個.html的文件,如果用戶訪問的地址是不允許訪問的,則直接返回index.html

最後,當我們將前面所有的內容都完成後,我們可以將這些內容都整合在一起,下面我們一起來看一下最終完成的這個靜態資源服務的完整代碼吧,如下:

const fs = require('fs');
const http = require('http');
const path = require('path');
// 靜態資源服務器地址
const port = 8000;
// 靜態資源文件夾
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',
};
// 靜態資源文件根路徑
const root = path.normalize(path.resolve(directoryName));
// 創建靜態資源服務器
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);
            // 當允許訪問時,則返回對應的頁面
            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行的代碼就實現瞭這個靜態資源服務,我們可以看一下具體的運行效果。

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

最後

我們隻通過Node.js中一些簡單的API就開發瞭一個基礎的靜態資源服務器,也讓大傢瞭解瞭一些Node.js相關的基礎操作,瞭解這些基礎的操作不僅有利於提高我們自身的知識儲備,也更有利於我們在實際開發中少踩一些坑。

以上就是Node.js開發靜態資源服務器的詳細內容,更多關於Node.js靜態資源服務器的資料請關註WalkonNet其它相關文章!

推薦閱讀: