1 Star 0 Fork 0

染青/icedog.flex-layout

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
web-server.js 18.91 KB
一键复制 编辑 原始数据 按行查看 历史
染青 提交于 2022-03-28 10:41 . add
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
/**
* web server.js node js 运行的一个web 服务器
* 特点:
* 1. 运行时指定端口 : node ./web-server.js 5001
* 2. 开启目录浏览,带参数?!dir: localhost:5001/pic/?!dir
*/
let http = require("http");
let url = require("url");
let fs = require("fs");
let path = require("path");
let config = {
hostname: "127.0.0.1", //主机ip地址 127.0.0.1
port: 5000, //端口值 80
home: ".", //根目录地址,默认是当前web-server.js目录。默认值 '.',
//指定目录默认访问页
defaultPages: ["index.html", "index.asp", "index.jsp", "index.cshtml"],
/**指定无法识别的格式的元类型 text/plain application/octet-stream(默认)*/
unmatchFileAs: "text/plain",
/** 目录映射 */
dirMapping: {
tool: "",
},
};
// 0 node 1 file
if (process.argv.length > 2) {
config.port = process.argv[2];
}
if (config.hostname === "") {
config.hostname = "0.0.0.0";
}
/**
* 通过扩展名获取 mine type
* @param {*} extName 扩展名 e.g. .txt .html
* @returns
*/
const getContentType = (extName) => {
//from nginx mime types
let mimeTypes = {
".html": "text/html",
".htm": "text/html",
".shtml": "text/html",
".css": "text/css",
".xml": "text/xml",
".gif": "image/gif",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "application/javascript",
".atom": "application/atom+xml",
".rss": "application/rss+xml",
".mml": "text/mathml",
".txt": "text/plain",
".gitignore": "text/plain",
".jad": "text/vnd.sun.j2me.app-descriptor",
".wml": "text/vnd.wap.wml",
".htc": "text/x-component",
".png": "image/png",
".svg": "image/svg+xml",
".svgz": "image/svg+xml",
".tif": "image/tiff",
".tiff": "image/tiff",
".wbmp": "image/vnd.wap.wbmp",
".webp": "image/webp",
".ico": "image/x-icon",
".jng": "image/x-jng",
".bmp": "image/x-ms-bmp",
".woff": "application/font-woff",
".jar": "application/java-archive",
".war": "application/java-archive",
".ear": "application/java-archive",
".json": "application/json",
".hqx": "application/mac-binhex40",
".doc": "application/msword",
".pdf": "application/pdf",
".ps": "application/postscript",
".eps": "application/postscript",
".ai": "application/postscript",
".rtf": "application/rtf",
".m3u8": "application/vnd.apple.mpegurl",
".kml": "application/vnd.google-earth.kml+xml",
".kmz": "application/vnd.google-earth.kmz",
".xls": "application/vnd.ms-excel",
".eot": "application/vnd.ms-fontobject",
".ppt": "application/vnd.ms-powerpoint",
".odg": "application/vnd.oasis.opendocument.graphics",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".pptx":
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
".xlsx":
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".docx":
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".wmlc": "application/vnd.wap.wmlc",
".7z": "application/x-7z-compressed",
".cco": "application/x-cocoa",
".jardiff": "application/x-java-archive-diff",
".jnlp": "application/x-java-jnlp-file",
".run": "application/x-makeself",
".pl": "application/x-perl",
".pm": "application/x-perl",
".prc": "application/x-pilot",
".pdb": "application/x-pilot",
".rar": "application/x-rar-compressed",
".rpm": "application/x-redhat-package-manager",
".sea": "application/x-sea",
".swf": "application/x-shockwave-flash",
".sit": "application/x-stuffit",
".tcltk": "application/x-tcl",
".der": "application/x-x509-ca-cert",
".pem": "application/x-x509-ca-cert",
".crt": "application/x-x509-ca-cert",
".xpi": "application/x-xpinstall",
".xhtml": "application/xhtml+xml",
".xspf": "application/xspf+xml",
".zip": "application/zip",
".msi": "application/octet-stream",
".msp": "application/octet-stream",
".msm": "application/octet-stream",
".mid": "audio/midi",
".midi": "audio/midi",
".kar": "audio/midi",
".mp3": "audio/mpeg",
".ogg": "audio/ogg",
".m4a": "audio/x-m4a",
".ra": "audio/x-realaudio",
".3gp": "video/3gpp",
".3gpp": "video/3gpp",
".ts": "video/mp2t",
".mp4": "video/mp4",
".mpeg": "video/mpeg",
".mpg": "video/mpeg",
".mov": "video/quicktime",
".webm": "video/webm",
".flv": "video/x-flv",
".m4v": "video/x-m4v",
".mng": "video/x-mng",
".asx": "video/x-ms-asf",
".asf": "video/x-ms-asf",
".wmv": "video/x-ms-wmv",
".avi": "video/x-msvideo",
".gitignore": "text/plain",
".gitattributes": "text/plain",
license: "text/plain",
};
let contentType = mimeTypes[extName];
if (contentType === null || contentType === undefined) {
contentType = config.unmatchFileAs;
}
return contentType;
};
/**
* 获取请求路径
* @param {IncomingMessage} req
*/
const getUrlObj = (req) => {
let reqUrl = req.url;
reqUrl = decodeURI(reqUrl);
let urlObj = url.parse(reqUrl);
return urlObj;
};
/**
* 格式化字节单位
* @param {*} bytes
* @param {*} decimals 小数点位数
* @returns
*/
const formatBytes = (bytes, decimals = 2) => {
if (bytes === 0) return "0 Bytes";
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
};
/**
* 检测路径是否存在,同时判断是否是目录
* @param {*} filePath 文件路径
* @returns
*/
const checkPathIsDirectory = (filePath) => {
return fs.existsSync(filePath) && fs.statSync(filePath).isDirectory();
};
/**
* 检测路径是否存在,同时判断是否是文件
* @param {*} filePath 文件路径
* @returns
*/
const checkPathIsFile = (filePath) => {
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
};
/**
* 填充文本到预期长度
* @param {*} text
* @param {*} expectLength
* @param {*} fillUnit
* @returns
*/
const fillToExpectLength = (text, expectLength, fillUnit = "&nbsp;&nbsp;") => {
let need = expectLength - text.length;
if (need <= 0) {
return text;
}
let append = "";
for (let i = 0; i < need; i++) {
append += fillUnit;
}
return text + append;
};
/**
*
* @param {*} filePath
* @param {*} pathname 请求url上的路径名
* @param {*} cb
*/
const buildDirectoryViewPage = (filePath, pathname, cb) => {
let baseHtml =
'<!DOCTYPE html><html><head><meta charset="UTF-8" />' +
'<title>文件浏览</title><meta name="viewport" content="width=device-width, initial-scale=1.0" />' +
"<style> * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }" +
" .container { padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } " +
"@media (min-width: 768px) { .container { width: 750px; } } @media (min-width: 992px) { .container " +
"{ width: 970px; } } @media (min-width: 1200px) { .container { width: 1170px; } } .h1, h1 { " +
"font-size: 36px; margin-top: 20px; margin-bottom: 10px; font-family: inherit; font-weight: 500; " +
"line-height: 1.1; color: inherit; margin: 0.67em 0; } hr { margin-top: 20px; margin-bottom: 20px;" +
" border: 0; border-top: 1px solid #eee; height: 0; -webkit-box-sizing: content-box; -moz-box-sizing:" +
" content-box; box-sizing: content-box; } a {text-overflow: ellipsis;overflow:hidden;" +
"display:inline-block;color: #337ab7; text-decoration: none; background-color:" +
" transparent; }a:hover{text-decoration: underline;cursor:pointer;color:green;} " +
".well { display: block; padding: 9.5px; margin: 0 0 10px; font-size: 13px; line-height:" +
" 1.42857143; color: #333; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border:" +
" 1px solid #ccc; border-radius: 4px; } #fork { position: fixed; top: 0; right: 0; _position: absolute;" +
" z-index: 10000; } .bottom { margin: 20px auto; width: 100%; text-align: center; } .container { min-width:" +
" 800px; margin: 50px auto; } .well a{width:300px;} .date, .size { display: inline-block; min-width: 100px; margin-left: 100px;" +
' } .title span,.title a{vertical-align: middle;}</style></head><body class="container">' +
'<h1 class="title">[title]</h1><hr /><div class="well">[content]</div></body></html>';
fs.stat(filePath, (err, stats) => {
if (!stats.isDirectory()) {
cb(`${filePath} is not a directory`);
return;
}
let content = `<div><a href="/?!dir">根目录 /</a></div>`;
if (pathname == "/") {
content += `<div><a href="/?!dir">返回上一级 ..</a></div>`;
} else {
let newPathname = pathname.substring(0, pathname.length - 1);
let idx = newPathname.lastIndexOf("/");
newPathname = newPathname.substring(0, idx + 1);
content += `<div><a href="${newPathname}?!dir">返回上一级 ..</a></div>`;
}
fs.readdir(filePath, (err, files) => {
let sHideDirs = [];
let sDirs = [];
let sHideFiles = [];
let sFiles = [];
files.forEach((fileName) => {
let stats = fs.statSync(path.join(filePath, fileName));
if (stats.isDirectory()) {
if (fileName.indexOf(".") == 0) {
sHideDirs.push({
name: fileName,
mtime: fillToExpectLength(
stats.mtime.toLocaleString(),
19
),
size: "-",
});
} else {
sDirs.push({
name: fileName,
mtime: fillToExpectLength(
stats.mtime.toLocaleString(),
19
),
size: "-",
});
}
}
if (stats.isFile()) {
if (fileName.indexOf(".") == 0) {
sHideFiles.push({
name: fileName,
mtime: fillToExpectLength(
stats.mtime.toLocaleString(),
19
),
size: formatBytes(stats.size),
});
} else {
sFiles.push({
name: fileName,
mtime: fillToExpectLength(
stats.mtime.toLocaleString(),
19
),
size: formatBytes(stats.size),
});
}
}
});
sHideDirs.sort();
sDirs.sort();
sHideDirs = sHideDirs.concat(sDirs);
sHideFiles.sort();
sFiles.sort();
sHideFiles = sHideFiles.concat(sFiles);
for (let i = 0; i < sHideDirs.length; i++) {
let f = sHideDirs[i];
content += `<div><a href="${pathname + f.name}?!dir" title="${
f.name
}">[D]${f.name}</a ><span class="date">${
f.mtime
}</span><span class="size">-</span></div>`;
}
for (let i = 0; i < sHideFiles.length; i++) {
let f = sHideFiles[i];
content += `<div><a href="${pathname + f.name}" title="${
f.name
}">${f.name}</a ><span class="date">${
f.mtime
}</span><span class="size">${f.size}</span></div>`;
}
let arr = ["<span>Index Of </span>"];
let pathArr = pathname.split("/");
let basePath = "/";
for (let i = 0; i < pathArr.length; i++) {
const p = pathArr[i];
if (p.length === 0) {
continue;
}
basePath += p + "/";
arr.push(`<a href="${basePath}?!dir">${p}</a>`);
}
let html = baseHtml.replace("[title]", arr.join("<span>/</span>"));
html = html.replace("[content]", content);
cb(html);
});
});
};
/**
* 获取默认页
* @param {*} filePath
*/
const getDefaultPage = (filePath) => {
if (!checkPathIsDirectory(filePath)) {
return;
}
//如果当前文件夹是目录,就访问默认页
let defaultPages = config.defaultPages;
if (defaultPages.length === 0) {
defaultPages = ["index.html"];
}
for (let i = 0; i < defaultPages.length; i++) {
const defaultPage = defaultPages[i];
let newFilePath = path.join(filePath, defaultPage);
if (checkPathIsFile(newFilePath)) {
return newFilePath;
}
}
return filePath;
};
/**
* 获取扩展名
* @param {String} filePath
*/
const getExtname = (filePath) => {
filePath = filePath.toLowerCase();
if (!checkPathIsFile(filePath)) {
return;
}
let pathObj = path.parse(filePath);
if (pathObj.ext === "") {
return pathObj.base;
}
return pathObj.ext;
};
/**
* 处理301永久跳转
* @param {IncomingMessage} req
* @param {ServerResponse} res
*/
const process301Redirect = (req, res) => {
let urlObj = getUrlObj(req);
let pathname = urlObj.pathname;
let search = urlObj.search;
if (search === null) {
search = "";
}
let filePath = path.join(config.home, pathname);
/**
* 跳转情况:
* 1.请求路径为空
* 2.判断请求路径是不是【文件夹】,如果是文件夹,
* 同时请求末尾不是 / 结尾,跳转到有 / 的路径
*/
let cond1 = pathname.length === 0;
let cond2 =
checkPathIsDirectory(filePath) &&
pathname.lastIndexOf("/") !== pathname.length - 1;
if (cond1 || cond2) {
let newPathname = pathname + "/" + search;
console.log(
`[info]:redirect url:from ${pathname + search} to ${newPathname}`
);
newPathname = encodeURI(newPathname);
res.writeHead(301, { Location: newPathname });
res.end();
return true;
}
return false;
};
/**
* 处理目录浏览 在路径末尾加 #!dir 即可进行目录浏览
* @param {IncomingMessage} req
* @param {ServerResponse} res
*/
const processDirectoryView = (req, res) => {
let urlObj = getUrlObj(req);
let pathname = urlObj.pathname;
let filePath = path.join(config.home, pathname);
if (checkPathIsDirectory(filePath) && urlObj.query === "!dir") {
console.log(`[info]:open directory:${filePath}`);
res.writeHead(200, { "Content-Type": "text/html" });
buildDirectoryViewPage(filePath, pathname, (html) => res.end(html));
return true;
}
return false;
};
/**
* 处理MarkDown文件,请求url带有 !skip 跳过,如 /a.md?!skip
* @param {IncomingMessage} req
* @param {ServerResponse} res
*/
const processMarkDownFile = (req, res) => {
let urlObj = getUrlObj(req);
let pathname = urlObj.pathname;
let filePath = path.join(config.home, pathname);
if (!checkPathIsFile(filePath) || urlObj.query === "!skip") {
return false;
}
let pathObj = path.parse(filePath);
if (!(pathObj.ext === ".md" || pathObj.ext === ".markdown")) {
return false;
}
pathname = encodeURI(pathname);
res.writeHead(301, { Location: `/index.html?p=${pathname}` });
res.end();
return true;
};
/**
* 处理文件定位
* @param {IncomingMessage} req
* @param {ServerResponse} res
*/
const processFileLocate = (req, res) => {
let urlObj = getUrlObj(req);
let pathname = urlObj.pathname;
pathname = decodeURI(pathname);
let filePath = path.join(config.home, pathname);
//判断是否是个目录
if (checkPathIsDirectory(filePath)) {
filePath = getDefaultPage(filePath);
}
if (!checkPathIsFile(filePath)) {
res.writeHead(404, { "Content-Type": "text/html" });
res.end(
`<!DOCTYPE html><html>
<head><title>404 Page</title><meta charset="UTF-8"/></head><body>
<p><h1>404 Page Not Found</h1><h2 style="color:#73D661;" >path:${filePath}</h2>
<h2>try view current <a style="color:#F9AD39;" href="?!dir">directory</a> ? or go
<a style="color:#F9AD39;" href="/?!dir">home</a>?</h2></p></body>`
);
return false;
}
let extname = getExtname(filePath);
//在返回头中写入内容类型
res.writeHead(200, {
"Content-Type": getContentType(extname) + ";charset=utf-8",
});
//创建只读流用于返回
let stream = fs.createReadStream(filePath, {
flags: "r",
encoding: null,
highWaterMark:64 * 1024
});
stream.on("error", () => {
res.writeHead(404, { "Content-Type": "text/html" });
res.end(`<h1>404 Read File Error</h1><h2>path:${filePath}</h2`);
});
//连接文件流和http返回流的管道,用于返回实际Web内容
stream.pipe(res);
return true;
};
/**
* 启动 web service
* @param {*} successCallback 启动成功的回调函数
*/
const startWebService = (successCallback) => {
const server = http.createServer((req, res) => {
let reqUrl = decodeURI(req.url);
console.log(`[info]: request url: ${reqUrl}`);
if (process301Redirect(req, res)) {
return;
}
if (processDirectoryView(req, res)) {
return;
}
if (processMarkDownFile(req, res)) {
return;
}
processFileLocate(req, res);
});
server.on("error", (error) => {
console.log(`[error]: ${error}`);
});
server.listen(config.port, config.hostname, successCallback);
};
startWebService(() => {
console.log("/******************** web server ********************/\n");
console.log(`Server running at http://${config.hostname}:${config.port}/`);
console.log("press [Ctrl+C] to stop server.");
console.log("\n/****************** web server **********************/");
});
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
HTML/CSS
1
https://gitee.com/dhclly/icedog.flex-layout.git
git@gitee.com:dhclly/icedog.flex-layout.git
dhclly
icedog.flex-layout
icedog.flex-layout
master

搜索帮助