使用Node.js搭建簡單靜態(tài)文件服務(wù)器

這半個(gè)多月一直在學(xué)Node.js,還是在入門階段,不過已經(jīng)對Node很感興趣了。這里介紹一個(gè)簡單的靜態(tài)文件服務(wù)器,總結(jié)一下心得體會。為什么說簡單呢,因?yàn)殡m然基本功能都有,但是沒加路由,還有沒考慮一些安全性的東西。后面我會繼續(xù)來總結(jié)完善。

本文參考了Node大神樸靈11年寫的一篇博文(強(qiáng)烈建議讀一讀,雖然有一些接口有些老,但是搭建服務(wù)器的思路,各方面都有涉及),還有這位大神前輩的博客,以及Node.js 6.x版本的文檔,stackoverflow一些答案。
如果哪里說得不對或者哪里有問題,請勞煩您指正,我會虛心接受。

正文如下:

一個(gè)Web服務(wù)器應(yīng)具備以下幾個(gè)功能:

1、能顯示以.html/.htm結(jié)尾的Web頁面

2、能直接打開以.js/.css/.json/.text結(jié)尾的文件內(nèi)容

3、顯示圖片資源

4、自動下載以.apk/.docx/.zip結(jié)尾的文件

5、形如http://xxx.com/a/b/ , 則查找b目錄下是否有index.html,如果有就顯示,如果沒有就列出該目錄下的所有文件及文件夾,并可以進(jìn)一步訪問。

6、形如http://xxx.com/a/b, 則作301重定向到http://xxx.com/a/b/ , 這樣可以解決內(nèi)部資源引用錯(cuò)位的問題。

總體的思路如下:

1.先加載需要用到的幾個(gè)模塊 url(解析request,截取路徑) path(解析路徑) fs(文件讀寫操作) http(起服務(wù)器)

2.切出來請求的url和路徑 (記得解碼,防止中文亂碼)

3.根據(jù)路徑有無擴(kuò)展名以及是否以“/”結(jié)尾作判斷,重定向

4.根據(jù)路徑來查找資源文件

OK,接下來上代碼,我有詳細(xì)的標(biāo)注注釋(咳,方便以后來查漏補(bǔ)缺)

前面有提到參考的那兩篇博文的年代有些久遠(yuǎn)(其實(shí)也就4,5年前..),以致一些接口6.x版本的Node.js已經(jīng)不支持了,或者一些方法近些年有了最佳實(shí)踐。

說一下重構(gòu)的地方:
1.判斷路徑類型,不使用fs.exist(),而是換成fs.stat方法

2.將回調(diào)換成了箭頭函數(shù)。

3.我一直沒查到getContentType這個(gè)方法.. 所以還是使用mime映射來傳入content-type 其實(shí)已經(jīng)專門有mime模塊來處理這個(gè)問題了。

4.做了一下模塊化處理

第一個(gè)模塊: app.js 啟動模塊

"use strict";
//加載所需要的模塊
var http = require('http');

var processRequest = require('./server');

//創(chuàng)建服務(wù),這里很機(jī)智的把對response和request的處理封裝成一個(gè)匿名函數(shù),傳入createServer中
//也可以直接在里面寫,但是看起來不是很整潔
var httpServer = http.createServer((req, res) => {
    processRequest(req, res);
});

var port = 8080;

//指定一個(gè)監(jiān)聽的接口
httpServer.listen(port, function() {

    console.log(`app is running at port:${port}`);
});

第二個(gè)模塊:mime.js 存放類型映射

module.exports = {
    "css": "text/css",
    "gif": "image/gif",
    "html": "text/html",
    "ico": "image/x-icon",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "js": "text/javascript",
    "json": "application/json",
    "pdf": "application/pdf",
    "png": "image/png",
    "svg": "image/svg+xml",
    "swf": "application/x-shockwave-flash",
    "tiff": "image/tiff",
    "txt": "text/plain",
    "wav": "audio/x-wav",
    "wma": "audio/x-ms-wma",
    "wmv": "video/x-ms-wmv",
    "xml": "text/xml"
};

第三個(gè)模塊,也是我們的核心模塊 server.js

var url = require('url');

var fs = require('fs');

var path = require('path');

var mime = require('./mime');

function processRequest(request, response) {


    //request里面切出標(biāo)識符字符串
    var requestUrl = request.url;
    //url模塊的parse方法 接受一個(gè)字符串,返回一個(gè)url對象,切出來路徑
    var pathName = url.parse(requestUrl).pathname;

    //對路徑解碼,防止中文亂碼
    var pathName = decodeURI(pathName);

    //解決301重定向問題,如果pathname沒以/結(jié)尾,并且沒有擴(kuò)展名
    if (!pathName.endsWith('/') && path.extname(pathName) === '') {

        pathName += '/';
        var redirect = "http://" + request.headers.host + pathName;
        response.writeHead(301, {
            location: redirect
        });
        //response.end方法用來回應(yīng)完成后關(guān)閉本次對話,也可以寫入HTTP回應(yīng)的具體內(nèi)容。
        response.end();
    };

    //獲取資源文件的絕對路徑
    var filePath = path.resolve(__dirname + pathName);
    console.log(filePath);
    //獲取對應(yīng)文件的文檔類型
    //我們通過path.extname來獲取文件的后綴名。由于extname返回值包含”.”,所以通過slice方法來剔除掉”.”,
    //對于沒有后綴名的文件,我們一律認(rèn)為是unknown。
    var ext = path.extname(pathName);
    ext = ext ? ext.slice(1) : 'unknown';

    //未知的類型一律用"text/plain"類型
    var contentType = mime[ext] || "text/plain";

    fs.stat(filePath, (err, stats) => {

        if (err) {
            response.writeHead(404, { "content-type": "text/html" });
            response.end("<h1>404 Not Found</h1>");
        };
        //沒出錯(cuò) 并且文件存在
        if (!err && stats.isFile()) {

            response.writeHead(200, { "content-type": contentType });
            //建立流對象,讀文件
            var stream = fs.createReadStream(filePath);
            //錯(cuò)誤處理
            stream.on('error', function() {

                response.writeHead(500, { "content-type": contentType });

                response.end("<h1>500 Server Error</h1>");

            });
            //讀取文件
            stream.pipe(response);
            //response.end();  這個(gè)地方有坑,加了會關(guān)閉對話,看不到內(nèi)容了
        };
        //如果路徑是目錄
        if (!err && stats.isDirectory()) {

            var html = " <head><meta charset = 'utf-8'/></head>";
            //讀取該路徑下文件
            fs.readdir(filePath, (err, files) => {
                if (err) {
                    console.log("讀取路徑失敗!");
                } else {

                    // files.foreach(function (file) {
                    // //做成一個(gè)鏈接表,方便用戶訪問
                    // html+=`<div><a href="${file}">${file}</a></div>`;
                    //  });

                    for (var file of files) {
                        if (file === "index.html") {

                            response.writeHead(200, { "content-type": "text/html" });
                            response.end(file);

                            break;
                        };
                        html += `<div><a href='${file}'>${file}</a></div>`;
                        console.log(html);

                    }
                    response.writeHead(200, { "content-type": "text/html" });
                    response.end(html);
                };

            });


        };

    });

};

module.exports = processRequest;
    這里有幾個(gè)細(xì)節(jié)的地方值得注意,一個(gè)是如何判斷并實(shí)現(xiàn)重定向的,第二個(gè)是如何來判斷content-type的。第三個(gè)要注意異步回調(diào)的執(zhí)行順序問題,有個(gè)坑就在傳html那里。
    再說一個(gè)小坑吧,response.end()這個(gè)方法我理解的不夠深,這個(gè)方法是用來關(guān)閉對話或者向response的body部分傳內(nèi)容,我把它放在了stream操作的下面,結(jié)果可想而知...  所以再涉及到文件讀寫時(shí),一定要注意這個(gè)地方。

下面是實(shí)現(xiàn)截圖:

1.這是我的目錄,不用管那個(gè)vscode,那是visual studio code文件配置目錄

![1.png](http://upload-images.jianshu.io/upload_images/3373032-ffc04d2fc246ad97.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2.服務(wù)器啟動在8080端口

![2.png](http://upload-images.jianshu.io/upload_images/3373032-9f44b9d0b7329c7a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
3.使用這個(gè)地址

![3.png](http://upload-images.jianshu.io/upload_images/3373032-18056b5020add779.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

4.打開調(diào)試工具,可以發(fā)現(xiàn)Head部分是301,也就是發(fā)生了重定向

![4.png](http://upload-images.jianshu.io/upload_images/3373032-8bdef90683b18238.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

5.來看看網(wǎng)頁內(nèi)容,可以看到,我們得到了該目錄下可以訪問的文件條目

![5.png](http://upload-images.jianshu.io/upload_images/3373032-cb9c9e1934c412ef.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

6.最后,控制臺信息

![6.png](http://upload-images.jianshu.io/upload_images/3373032-421376c5ef9f7914.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


OK  就是這樣。歡迎收看你的月亮我的心,好男人就是我~ 我們下周同一時(shí)間再會~
PS:如果本文有錯(cuò)誤的地方請您一定要指出來,我會虛心接受,萬分感謝。~~
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 個(gè)人入門學(xué)習(xí)用筆記、不過多作為參考依據(jù)。如有錯(cuò)誤歡迎斧正 目錄 簡書好像不支持錨點(diǎn)、復(fù)制搜索(反正也是寫給我自己看...
    kirito_song閱讀 2,654評論 1 37
  • Node.js是目前非常火熱的技術(shù),但是它的誕生經(jīng)歷卻很奇特。 眾所周知,在Netscape設(shè)計(jì)出JavaScri...
    w_zhuan閱讀 3,734評論 2 41
  • Node.js是目前非?;馃岬募夹g(shù),但是它的誕生經(jīng)歷卻很奇特。 眾所周知,在Netscape設(shè)計(jì)出JavaScri...
    Myselfyan閱讀 4,203評論 2 58
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,653評論 19 139
  • 文|愛讀分享者 自從這部韓劇襲來,顏值擔(dān)當(dāng)?shù)乃沃倩蛽軇恿巳f千追劇少女的心弦,成了諸多“韓粉”最新的“舔屏對象”。...
    愛讀分享閱讀 600評論 0 0

友情鏈接更多精彩內(nèi)容