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,
/** * 简单的静态服务器,单文件无依赖 * @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 += `
'; html += ` `; html += '' response.writeHead(200, { "content-type": "text/html" }); response.end(html); }); } else { log(request, response, 500); response.writeHead(500, { "content-type": contentType }); response.end("- ..
`; //读取该路径下文件 fs.readdir(filePath, (err, files) => { if (err) { console.log("读取路径失败!", filePath); } else { for (var file of files) { html += `- ${file}
`; } } html += '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}`); }); }