重要声明:本文章仅仅代表了作者个人对此观点的理解和表述。读者请查阅时持自己的意见进行讨论。
为了完成文件浏览功能,我们必须把代码写在一个js文件里。新建文件,命名为file_server.js
,随便保存在那里,只要你找得到。废话不多说,直接开始上代码加讲解。
一、梳理思路
既然要进行文件浏览,所以fs
模块想必是必不可少的了。而其次我们是要作为一个http服务,所以http
模块也跑不了了,再然后,为了让我们在服务端拿到不同的请求路径与请求参数,我们需要加入一个url
模块,它可以帮我们解析url链接上的东西。最后,文件浏览必然涉及到有文件路径相关处理,因此引入path
模块对文件路径进行操作。path
模块可以方便的处理各个文件路径之间的关系。
现在让我们来看看有哪些模块了:
- fs:用于文件处理
- http:用于创建服务
- url:用于处理请求连接
- path:用于处理文件路径
再构建一套不错的程序实现方案,就可以开始撸代码了。不用你想,下面将使用下面的逻辑来构建这个程序:
- 首先浏览器打开首页,显示某个目录下的所有文件。
- 点击某个文件,就跳转到另一个链接,这个链接就进行文件打开,如果这个文件是目录,就列出所有文件。
逻辑是这样,但如何把这个逻辑转化成代码呢?第一,根据上面的描述,可得,这个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");
});