当前位置 : 主页 > 网络编程 > JavaScript >

nodejs 简单的静态服务器,单文件无依赖

来源:互联网 收集:自由互联 发布时间:2021-06-28
web.js /** * 简单的静态服务器,单文件无依赖 * @author xBei */var http = require('http');var fs = require('fs');var url = require('url');var path = require('path');const { exec } = require('child_process');const VERSION = [0,
web.js
/**
 * 简单的静态服务器,单文件无依赖
 * @author xBei 
 
  
 */

var http = require('http');
var fs = require('fs');
var url = require('url');
var path = require('path');
const { exec } = require('child_process');

const VERSION = [0, 1, 3];
const minetype = {
    "css": "text/css",
    "txt": "text/plain",
    "gitignore": "text/plain",
    "htm": "text/html",
    "html": "text/html",
    "xml": "text/xml",
    "js": "text/javascript",
    "ico": "image/x-icon",
    "gif": "image/gif",
    "jpeg": "image/jpeg",
    "jpg": "image/jpeg",
    "png": "image/png",
    "svg": "image/svg+xml",
    "tif": "image/tiff",
    "tiff": "image/tiff",
    "json": "application/json",
    "pdf": "application/pdf",
    "swf": "application/x-shockwave-flash",
    "woff": "application/x-font-woff",
    "woff2": "application/x-font-woff",
    "eof": "application/vnd.ms-fontobjec",
    "ttf": "application/font-sfn",
    "mp3": "audio/mpeg",
    "wav": "audio/x-wav",
    "wma": "audio/x-ms-wma",
    "aiv": "video/x-msvideo",
    "mov": "video/quicktime",
    "wmv": "video/x-ms-wmv",
    "unknown": "application/octet-stream",
    "zip": "application/zip"
};
const defaultDocuments = [
    'index.html',
    'index.htm',
    'default.html',
    'default.htm',
];

const styles = {
    'bold': ['\x1B[1m', '\x1B[22m'],
    'italic': ['\x1B[3m', '\x1B[23m'],
    'underline': ['\x1B[4m', '\x1B[24m'],
    'inverse': ['\x1B[7m', '\x1B[27m'],
    'strikethrough': ['\x1B[9m', '\x1B[29m'],
    'white': ['\x1B[37m', '\x1B[39m'],
    'grey': ['\x1B[90m', '\x1B[39m'],
    'black': ['\x1B[30m', '\x1B[39m'],
    'blue': ['\x1B[34m', '\x1B[39m'],
    'cyan': ['\x1B[36m', '\x1B[39m'],
    'green': ['\x1B[32m', '\x1B[39m'],
    'magenta': ['\x1B[35m', '\x1B[39m'],
    'red': ['\x1B[31m', '\x1B[39m'],
    'yellow': ['\x1B[33m', '\x1B[39m'],
    'whiteBG': ['\x1B[47m', '\x1B[49m'],
    'greyBG': ['\x1B[49;5;8m', '\x1B[49m'],
    'blackBG': ['\x1B[40m', '\x1B[49m'],
    'blueBG': ['\x1B[44m', '\x1B[49m'],
    'cyanBG': ['\x1B[46m', '\x1B[49m'],
    'greenBG': ['\x1B[42m', '\x1B[49m'],
    'magentaBG': ['\x1B[45m', '\x1B[49m'],
    'redBG': ['\x1B[41m', '\x1B[49m'],
    'yellowBG': ['\x1B[43m', '\x1B[49m']
};

function isFile(file) {
    if (!fs.existsSync(file))
        return false;
    return fs.statSync(file).isFile();
}
/**
 *
 * 
 * @param {string} ext 
 * @returns {string}
 */
function getMimetype(ext) {
    var contentType = minetype[ext] || "application/octet-stream";
    return contentType;
}

function getExtname(pathName) {
    var ext = path.extname(pathName);
    return ext ? ext.slice(1) : 'unknown';
}

function outputFile(request, response, filePath) {
    var contentType = getMimetype(getExtname(filePath));
    var stream = fs.createReadStream(filePath);
    //错误处理
    stream.on('error', function () {
        log(request, response, 500);
        response.writeHead(500, {
            "content-type": contentType
        });
        response.end("
  

500 Server Error

"); }); response.writeHead(200, { "content-type": contentType }); log(request, response, 200); //读取文件 stream.pipe(response); } /** * * 检查地址是否存在,如果存在那么是文件还是目录 * @param {URL} url * @returns {number} 0-不存在,1-文件,2-目录 */ function checkUrl(url) { let pathName = decodeURI(url.pathname); let filePath = path.resolve(__dirname + pathName); if (!fs.existsSync(filePath)) return false; let st = fs.statSync(filePath); return st.isFile() ? 1 : 2; } /** * 输出404错误 * * @param {Request} request * @param {Response} response * @returns {Response} */ function getFileNotFound(request, response) { log(request, response, 404); response.writeHead(404, { "content-type": "text/html" }); response.end(' 404

404 - File Not Found.

'); return response; } /** * * * @param {Request} request * @param {Response} response * @param {number} [code=200] */ function log(request, response, code = 200) { console.log(`${setColor('yellow',request.method)} ${request.url}`, code == 200 ? `${setColor('green',code)}` : `${setColor('red',code)}`); } /** * * * @param {Request} request * @param {Response} response * @returns */ function processRequest(request, response) { //request里面切出标识符字符串 var requestUrl = request.url; //url模块的parse方法 接受一个字符串,返回一个url对象,切出来路径 let requestUri = url.parse(requestUrl); let fileStat = checkUrl(requestUri); if (!fileStat) { return getFileNotFound(request, response); } //对路径解码,防止中文乱码 var pathName = decodeURI(requestUri.pathname); //获取资源文件的绝对路径 var filePath = path.resolve(__dirname + pathName); if (fileStat == 1) { outputFile(request, response, filePath); } else if (fileStat == 2) { //解决301重定向问题,如果pathname没以/结尾,并且没有扩展名 if (!pathName.endsWith('/') //&& path.extname(pathName) === '' ) { pathName += '/'; requestUri.pathname = pathName; var redirect = url.format(requestUri); //"http://" + request.headers.host + pathName; response.writeHead(301, { location: redirect }); //response.end方法用来回应完成后关闭本次对话,也可以写入HTTP回应的具体内容。 response.end(); return; }; //处理默认文档 if (!defaultDocuments.every((value) => { var defaultFile = path.join(filePath, value); if (isFile(defaultFile)) { outputFile(request, response, defaultFile); return false; } return true; })) { return false; } //没有找到默认文档 //显示当前目录下所有文件 var html = ` ${pathName}`; html += `

${pathName}

    `; html += `
  • ..
  • `; //读取该路径下文件 fs.readdir(filePath, (err, files) => { if (err) { console.log("读取路径失败!", filePath); } else { for (var file of files) { html += `
  • ${file}
  • `; } } html += '
'; html += `
${new Date()}
`; html += '' response.writeHead(200, { "content-type": "text/html" }); response.end(html); }); } else { log(request, response, 500); response.writeHead(500, { "content-type": contentType }); response.end("

500 Server Error

"); } }; const defaultOptions = { port: 8080, help: false, version: false, update: false, }; function handlerArgs() { let args = process.argv; let argsCount = args.length; if (argsCount < 3) return Object.assign({}, defaultOptions, {}); args = args.slice(2); let options = {}; while (args.filter((v) => { return v.startsWith('-') }).length > 0) { let count = args.length; args.every((v, index) => { if (v.startsWith('-')) { if (index + 1 < count) { let vv = args[index + 1]; if (!vv.startsWith('-')) { options[v] = vv; args.splice(index, 2); } else { options[v] = true; args.splice(index, 1); } } else { options[v] = true; args.splice(index, 1); } return false; } return true; }); } for (let key in options) { let element = options[key]; switch (key) { case '-p': case '--port': options['port'] = element; break; case '-H': case '--help': options['help'] = element; break; default: options[key.replace(/^-{1,}/, '')] = element; break; } delete options[key]; } //console.log(options); return Object.assign({}, defaultOptions, options); } function showHelp() { let me = __filename.split(path.sep).pop(); console.log(`把当前目录(${me}所在目录)当作“主目录”创建一个简单的静态服务器`); console.log(`用法:node ${me} [选项]`); console.log(); console.log('版本:', VERSION.join('.')); console.log('选项:'); console.log(' -P, --port ', "\t 监听的端口号(大于1024)。默认:8080"); console.log(' -U, --update', "\t\t 在线更新"); console.log(' -V, --version', "\t\t 显示版本"); console.log(' -H, --help', "\t\t 显示帮助"); } let options = handlerArgs(); if (options.help === true) { //console.log(process.argv) showHelp(); process.exit(0); } else if (options.version == true) { console.log('版本:', VERSION.join('.')); }else if (options.update === true) { doUpdate(); } else { let port = options.port; if (!port || isNaN(port + '')) { showHelp(); process.exit(1); } else { if (port < 1024 || port > 65535) { console.error("port must be > 1024 AND < 65535"); process.exit(1); } } var httpServer = http.createServer((req, res) => { processRequest(req, res); }); httpServer.on('clientError', (err, socket) => { console.error(err); socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); }); httpServer.on('error', (err, socket) => { console.error(`[${setColor('red', 'ERROR')}]`, `CAN NOT listen at ${err.port}`); }); //指定一个监听的接口 httpServer.listen(port, function () { console.log(`app is running at port:${setColor('red', port)}`); let os = process.platform switch (os) { case 'darwin': exec(`open 'http://localhost:${port}/'`); break; case 'freebsd': case 'linux': case 'sunos': exec(`x-www-browser 'http://localhost:${port}/'`); break; case 'win32': exec(`start http://localhost:${port}/`); break; default: break; } }); } function setColor(color, text) { let a = styles[color]; if (!a) return text; return `${a[0]}${text}${a[1]}`; } //console.log('\x1B[36m%s\x1B[0m2222', 123); //cyan //console.log('\x1B[33m%s\x1b[0m:', path); //yellow //https://gitee.com/xbei/codes/cy07qm6idzvst52lp38eh46/raw?blob_name=web.js function downFile(urlToDownload, toSave) { return new Promise((resolve, reject) => { var https = require('https'); console.log('get', urlToDownload); let uri = new url.URL(urlToDownload); const options = { method: 'GET', hostname: uri.host, path: uri.search ? `${uri.pathname}${uri.search}` : uri.pathname, headers: { 'Content-Type': 'text/plain' } }; //console.log('get',options); let s = ''; let f2 = fs.openSync(toSave, 'w'); let r = https.request(options, (res) => { res.setEncoding('utf8'); res.on('data', (chunk) => { s += chunk; fs.writeSync(f2, chunk); }); res.on('end', () => { fs.closeSync(f2); resolve(s); }); }); r.on('error', (e) => { reject(e); }); r.end(); }); } function doUpdate() { console.log('在线更新'); // 一、校验本地文件读写权限 // 自己 let selfFile = __filename; try { fs.accessSync(selfFile, fs.constants.F_OK | fs.constants.W_OK); } catch (err) { console.error(`[${setColor('red', '错误')}]`, '没有权限更新', selfFile); return; } // 临时文件 let tmpFile = selfFile + '.tmp'; try { let f2 = fs.openSync(tmpFile, 'w'); fs.closeSync(f2); } catch (err) { console.error(`[${setColor('red', '错误')}]`, '无法创建', tmpFile); return; } console.log('当前版本:', VERSION.join('.')); // 二、下载文件 downFile('https://gitee.com/xbei/codes/cy07qm6idzvst52lp38eh46/raw?blob_name=web.js', tmpFile) .then((content) => { //判断是否可更新 //let match = /^ \* @version (\d+).(\d+).(\d+)/ig.exec(content); //const VERSION = [0, 1, 1]; let match = /const VERSION = \[(\d+), (\d+), (\d+)\];/ig.exec(content); if (match) { if (VERSION[0] < match[1] || VERSION[1] < match[2] || VERSION[2] < match[3]) { console.log(`最新版本: ${match[1]}.${match[2]}.${match[3]}`); return true; } } return false; }) .then(canUpdate => { if (canUpdate) { fs.copyFile(tmpFile, selfFile, (err) => { if (err) throw err; console.log(setColor('green','更新成功')); fs.unlink(tmpFile, err => { }); }); } else { console.log('已经是最新版了,不需要更新'); } }) .catch(e => { console.error(`[${setColor('red', '错误')}]: ${e.message}`); }); }
网友评论