【nodejs】用纯node打造一个简易版文件浏览器。

by img Microanswer 创建时间:Nov 12, 2019 1:38:20 PM 

标签: 文件浏览器 node url path fs fileserver



重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。

为了完成文件浏览功能,我们必须把代码写在一个js文件里。新建文件,命名为file_server.js,随便保存在那里,只要你找得到。废话不多说,直接开始上代码加讲解。

一、梳理思路

既然要进行文件浏览,所以fs模块想必是必不可少的了。而其次我们是要作为一个http服务,所以http模块也跑不了了,再然后,为了让我们在服务端拿到不同的请求路径与请求参数,我们需要加入一个url模块,它可以帮我们解析url链接上的东西。最后,文件浏览必然涉及到有文件路径相关处理,因此引入path模块对文件路径进行操作。path模块可以方便的处理各个文件路径之间的关系。

现在让我们来看看有哪些模块了:

  • fs:用于文件处理
  • http:用于创建服务
  • url:用于处理请求连接
  • path:用于处理文件路径

再构建一套不错的程序实现方案,就可以开始撸代码了。不用你想,下面将使用下面的逻辑来构建这个程序:

  1. 首先浏览器打开首页,显示某个目录下的所有文件。
  2. 点击某个文件,就跳转到另一个链接,这个链接就进行文件打开,如果这个文件是目录,就列出所有文件。

逻辑是这样,但如何把这个逻辑转化成代码呢?第一,根据上面的描述,可得,这个http服务要有2个链接可以访问,一个是首页,另一个是打开文件页。因此,我们要设计到支持多个链接。第二,如果使用if语句判断不同的链接做不同的事情,代码量一多起来,岂不是要写很多if。所以使用对象映射的方式。第三,通过url模块对请求链接的解析,拿到对应请求路径,直接从映射对象里取出进行不同业务功能处理。

二、代码示列

现在有了上面的思路,就可以开始撸代码了。详细注释也有,看代码:

/**
 * 文件服务器示列代码。
 * created by microanswer.cn
 * 2019年11月12日08:57:50。
 *
 * 使用命令: node file_server.js 即可运行。
 */

let http = require("http");
let fs   = require("fs");
let url  = require("url");
let path = require("path");

// 这个目录用于这个服务要浏览的目录。你可以修改为你自己喜欢的目录。
let dir = "C:/";

// 访问路径与对应业务处理对象。
let server = {

    // 处理首页,渲染文件列表。
    "/": function (req, res) {
        res.setHeader("Content-type", "text/html;charset=utf-8");

        // 读取文件列表。
        let dirs = fs.readdirSync(dir);

        // 构建文件列表html结构
        let fileUl;
        if (dirs && dirs.length > 0) {
            fileUl = "<ul>";
            dirs.forEach((fileName, index) => {
                fileUl += "<li>" +
                          "    <a href='/file?path="+ encodeURIComponent(fileName) +"'>" + fileName + "</a>" +
                          "</li>"
            });
            fileUl += "</ul>";
        } else {
            fileUl =
                "<div>此目录没有文件</div>" +
                "<div>" +
                    "<button onclick='history.back()'>返回</button>" +
                "</div>"
        }


        // 构建网页字符串。
        let html =
              "<!DOCTYPE html>" +
              "<html lang=\"zh-CN\">" +
              "    <head>" +
              "        <title>文件浏览器Demo</title>" +
              "    </head>" +
              "    <body>" +
              "        <div>" +fileUl+ "</div>" +
              "    </body>" +
              "</html>";

        // 渲染
        res.end(html);

    },

    // 处理具体要查看的文件
    "/file": function (req, res) {
        res.setHeader("Content-type", "text/html;charset=utf-8");

        // 获取传递来的要查看的文件路径:
        let mPath = req.query.path;

        // 再根据配置拿到全路径
        let fullPath = path.join(dir, mPath);

        let fileHtml =
            "<div>" +
            "    <button onclick='history.back()'>返回</button> | 查看文件:" + fullPath + "" +
            "</div>" +
            "<hr>" +
            "<small>此示列仅能打开UTF8格式文本文件,请勿打开其他类型文件,如果你打开的文件较大可能比较卡。</small>" +
            "<hr>";

        // 文件存在,则打开文件。
        if(fs.existsSync(fullPath)) {
            try {
                let stat = fs.statSync(fullPath);
                // 如果是目录,则按目录打开。
                if (stat.isDirectory()) {
                    let thisDirs = fs.readdirSync(fullPath);
                    if (thisDirs && thisDirs.length > 0) {
                        thisDirs.forEach((fileName, index) => {
                            fileHtml +=
                            "<li>" +
                             "<a href='/file?path="+encodeURIComponent(path.join(mPath,fileName))+"'>"+fileName+"</a>"+
                            "</li>";
                        });
                    } else {
                        fileHtml += "<div>此目录没有文件</div>";
                    }
                } else {
                    // 如果是文件,则打开文件。
                    let str = fs.readFileSync(fullPath);
                    fileHtml += "<pre>" + str + "</pre>";
                }
            }catch (e) {
                // 出错可能是因为没权限。
                fileHtml += "出错了:" + e.message + "。 可能是没权限读取此文件或目录,也可能文件目前被占用。";
            }

        } else {
        // 文件不存在,则返回文件不存在。
            fileHtml +=
                "<div>文件已不存在或没有权限读取。</div>"
        }


        let html =
            "<!DOCTYPE html>" +
            "<html lang=\"zh-CN\">" +
            "    <head>" +
            "        <title>文件:"+ mPath +"</title>" +
            "    </head>" +
            "    <body>" +
            "        <div>" +fileHtml+ "</div>" +
            "    </body>" +
            "</html>";

        res.end(html);
    },

    // google浏览器默认会一此路径作为网站图标,这里兼容住!
    "/favicon.ico": function (req, res) {
        res.setHeader("Content-Type", "image/jpeg");

        // 不要害怕。这一堆只是 [滑稽] 表情的 base64 字符串数据。
        let imgBase64 =
            "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0" +
            "d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9i" +
            "ZS54bXAAAAAAADw/ eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2V" +
            "oaUh6cmVTek5UY3prYzlkIj8+ IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYm" +
            "U6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExID" +
            "Y2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZj" +
            "pSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLX" +
            "JkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9Ii" +
            "IgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bW" +
            "xuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG" +
            "1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS" +
            "9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG" +
            "9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NU" +
            "Q4RDU3OUUwNEU5MTFFQUI1NUNFNkI3QkU3MkUxRTEiIHhtcE1NOkRvY3VtZW" +
            "50SUQ9InhtcC5kaWQ6NUQ4RDU3OUYwNEU5MTFFQUI1NUNFNkI3QkU3MkUxRT" +
            "EiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLm" +
            "lpZDo1RDhENTc5QzA0RTkxMUVBQjU1Q0U2QjdCRTcyRTFFMSIgc3RSZWY6ZG" +
            "9jdW1lbnRJRD0ieG1wLmRpZDo1RDhENTc5RDA0RTkxMUVBQjU1Q0U2QjdCRT" +
            "cyRTFFMSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG" +
            "1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpylktkAAALsSURBVHjapFNNaB" +
            "NBFP5mN8kmmya0saaNbSy1Ug/VYlVURC8FPYvowYMiSPFQW0XBQ1FsDx48iI" +
            "KgogcVxF+KJ8GDgr8oaiktan+xmrRpG9okm2R3s7uzO05SlKq9+eAxw5v3vv" +
            "nme28IYwz/Y66lgjalYWXq8zGaHpEEpsP2VKEs0vxRDtU8JoC5OJcsZsB3kc" +
            "z4iyPa0M0TspQNuIOVIKIbVM/ByikwfBs+hTYdvegLVt5dCiAy13/rqTh9p7" +
            "miaQcQiHIq9gKsIBR5wYx9QDqeQdn2S93+ULSnWCT8qlYmXnawsavNUm0L1K" +
            "wJK/UdcLIA5W6lYCRjoFIUvqAA5fXJbsvUG34DOI4VTr660pH3tCJvt17W6L" +
            "aj8zPVs9l4wmE0h8RgEnOpNe/zxpZOs/zAJ23OQGqwt6tUX3xCamLg3MCVNu" +
            "d8+z42M53YUow5jHmnXlweyb5tZ/fOtLE7F3qyPF7BneRiwweHbx9XKbUaS1" +
            "2gc0M+v50hu1rWQn927VGiwqO6qxuoaMfqBL+KlvW1cAQEJh+e7hP9KzXCDF" +
            "NUYz5aUIQSAHEVnOqIglXCNOCxoyRqwMAAPHUyP3OhMfijpCVJuetpnOe4DQ" +
            "jhAphtOSUA213ltYMKdDYFb8QPEiCQmJsX8ZZTk4MUb+G+wgE1VAhEg5X38S" +
            "kKlvqDYM3GN9Rk0GfiyI5y9V0WV17lyBpXWFtYbZ27ivm+ UZh6Ah7/mlFJl" +
            "lMlAG/58ge6b2e/XKVA/TYPfSwBSAVA1BfcxfeChuTzcchVgGHYcNXvv8hJJ" +
            "X8PkpHP7Mk839sb8I4hPUjgqZQh1/LniIQToMiNZCAt4/GwhkKoK758a8eqo" +
            "v5/ jHI+NXk2/67zlEw+yE7agZkhYFx+0W1DqiawBBk03DEe2ty52yXgyz9/" +
            "oWim5axWvtw/ RWefHBbYpCDyfpisHKK36aun4dD18pXrbnDq2pKfabFZDhp" +
            "sNS2CcUFdAUiyb5YXKn/n/RRgAABEb3ih3CK9AAAAAElFTkSuQmCC";
        // 将图片传给客户端。
        res.end(Buffer.from(imgBase64, "base64"));
    }
};


// 创建服务。
let app = http.createServer(function (req, res) {

    let urlObj = url.parse(req.url); // 获取到请求的url,并进行解析。服务端方便根据客户的url进行不同的业务处理。

    // 然后将不同的url分发到不同的处理方法中。
    // server对象作为一个集中处理对象,里面有各种方法处理了各种path。
    let fun = server[urlObj.pathname];

    if (fun) {
        // server 里找到里此路径的对应处理方法,那就执行住。

        // 执行前,先拿到请求参数
        let query = {};
        if (urlObj.query) {
            let qs = urlObj.query.split("&");
            for (let i = 0; i < qs.length; i++) {
                let kv = qs[i].split("=");
                query[kv[0]] = decodeURIComponent(kv[1]);
            }
        }

        // 这样方便在 req 上直接获取链接上的请求参数。
        req.query = query;
        fun(req, res);
    } else {
        // server 里没有此路径的业务处理方法,就从定向到首页.
        res.statusCode = 301;
        res.statusMessage = "removed";
        res.setHeader("Location", "/");
        res.end();
    }
});

app.listen(8080, function () {
    console.log("服务已经开启:http://127.0.0.1:8080");
});

三、效果展示

全文完, 转载请注明出处。 对你有帮助?不如赞一个吧:
发表评论(发表评论需要登录,你现在还没有登录。)
你需要先登录才可以评论。

评论列表 (0条)