基于 Node.js 简易本地 http 服务

基于Node.js的简易http服务器

Python的 http 模块可以直接开启一个简单的http服务器,平时用来测试很方便

python -m http.server

但是如此开启的服务器不支持post请求,因此照着文档使用node.js实现了一个,在Windows10下测试基本满足需要

Node.js 环境配置

由于对于node.js只是一个小白,因此还存在一定的问题

  • 为了提高响应速度以及 readFile 存在的文件大小上限限制,读取文件时使用了流模式 createReadStream,但同时导致在读取大于一定尺寸的文件时多次响应或者响应不完全,暂时未找到解决方法

引入了一个模块 jschardet 用于判断文件的字符编码,判断错误几率还是存在的

npm i jschardet -g

判断文件类型使用了自己收集的 MIME 类型,可能有不正确的,但不影响使用。
也可以自行引入 mime 模块 在判断类型的部分修改使用

npm i mime -g

代码

'use strict'
// 端口
var PORT = 8080;
// 当前目录作为根目录
var DIR = '.';

var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');
var jschardet = require('jschardet');
var iconBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFCElEQVR4AaXUA6xkXRbF8f9e+9yqNsa2PRMnY9u2bdvh2LatYKxgbHte8/XLM6tu3XP2d5K26+tayW9dG+IC8+JvPPaeL/36k/7z3M899p+P/9BD71xxITTuii//waO94kXffMR1X/qtx/4pefqWGdfY1G+u3U/pO72m+W2v8WtW7Ni0OVWMQ+OuuH7Ac9fGx5qm+btMNwKCI9m0uVeixM2l9O9k/sGu5K5iHBpnJb/s+guay4yyZI81AKLCOJJecrkbpZTA9CSTtXJ7VsV5nXPh5Vdu31xhMCPpzWaI4IyJqt/vUXKxHIUIa8DeKdMBM7t1xdnoxIlypWmvyJedvTqXX/ypJ/3AjEsDwXniEsmdyIXDJ0EEXN5NP5H0Q4grV/R6jVccpVrsOTRvFft/288UvSc1/NfErYDC4RjnSwR13+QIiEIdWpSgRBRht0sp7UnJ31ZKzhUQViEIrnXly4T3475XuMWwNfH0E65YjJmoMCO5k3MQpZAjKBGqoBCGnmvShifdw0RUqBb7Z2e3x0hfalfUmAAwLiQB7qJEUEo5MoRcKyKsBCTzTX+amvry9//6+80//+ffTbWIbJt68m51Xw8smDSSnXAXSh1Sh4GAmaVFltbW7NKbd/UJM9XC3cOTgq5hMNdgzkQxMwpBKQFRThgP9szOIKmst4MAQgCNpyNvsbEx3YcCBJMloMuFUoIKMzG9uMhoNKIx4RgVqoUZSI67sOKsz/QwDyZNLpmcgxKZLnccWFzAlABDFZWglgx3q4Qn0c73KCNj0nQ505UCYcwsL5IjY2ZgIocRGKpFSkIy7MiJyJzhXA9NehcMBu2Qtsssra+SJDCgkguToVoEIHH44KoSdMsN3VCYTXR8uuiYX10CcyTHMXTCchkgceRsQILkYtC1/PMfc9RxJovRRqYv4SbkCSkdHpdXtbAAVTKskmB6ZZbF+ZbFxSGSMUmCYBSZxkVjhlcy4QjVglSQAiyQG/NrqwzaESkZe/6/hGQEMcE9MIbdCJnhMpKLfnKaJFSLolwV5EFYx4H5OZKEmbG0PGRubh2XmCQRwfqopfFEMsMAN1AtchQ6ddAU9s8tkEshOJyUxH/+u0BETPx33BgN6UrBlUiecEuoFq0GZBuxMthgdmHltKvd2Bhx4NAK7jbhScDKcJ3kTtt1SKISG3lgOY3s4KEldIZb3TRiau88OQcYE70Lo9xR74RdYfulSOaoFunSbTu/sNbfWMtn/O4joOsKe/Yv4K6JH8XSYK1ZGq128xtLqBZzv2mWFvbnx8o5a5KLqQPzDNsOM5v0JB59aGVhrS05VIvVlc7KUJ+86cv+J4zPnHjxnBiDf03NXNy7EByJzD5VGRGf7XJnFaqFNuWo+MMbryE6e6Q5NwD+XNmJO3GJ6bkV1gbtxTmwGfzJza/nlh7ddp1XlBJRoVoc1dHlilGb/9blfGPEfYGNE04El/GPqelxftFWDar7bmn6N9m9Zds/Kq6w81K54ijV4mwus2X3Ny6zbfcWjDec+BLNL68yv7SGYZwjb+il3uYdm7Z+o+JsVIvzudYlr/RqLjXqYXwNILnz9z0HkIwTLxeCiPjqcrfe27DhqyvOR+Os9O/VKWN/PzPQ/fG4Bcae9eGQ6YVFJAFQIvZUN3fZA3b6llwxDo2z0g12XSNucNUrF7Zmw/gdHleV2yP+c3B6VKK0uZSHm7hq0/PfB2F1WCrGcRGqhheqx+Gf1gAAAABJRU5ErkJggg==';

var server = http.createServer(function(request, response) {
    var pathname = decodeURI(url.parse(request.url).pathname);
    var realPath = path.join(DIR, pathname);
    var ext = path.extname(realPath);
    ext = ext ? ext.slice(1) : 'unknown';
    ext = ext.toLowerCase();
    fs.exists(realPath, function(exists) {
        if (exists) {
            // 获取文件信息
            var stat = fs.lstatSync(realPath);
            if (stat.isSymbolicLink()) {
                // 软连接 获取真实文件的信息
                stat = fs.lstatSync(fs.readlinkSync(realPath));
            }
            if (stat.isDirectory()) {
                let tmpPath = (pathname.lastIndexOf('/') === pathname.length - 1) ? pathname : (pathname + "/");
                let html = '<!DOCTYPE html>\n<html>\n<head>\n' +
                    //'<link rel="icon" sizes="32x32" type="image/png" href="' + iconBase64 + '">\n' +
                    //'<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n' +
                    //'<meta charset="UTF-8"/>\n' +
                    '<title>Directory listing for ' + tmpPath + '</title>\n' +
                    '</head>\n<body>\n<h1>Directory listing for ' + tmpPath + '</h1>\n<hr>\n<ul>\n';
                // 获取文件夹下所有文件
                let files = fs.readdirSync(realPath);
                files.forEach((val, index) => {
                    html += ('<li><a href="' + encodeURI(tmpPath + val) + '">' + val + '</a></li>\n');
                });
                html += '</ul>\n<hr>\n</body>\n</html>';
                response.writeHead(200, { 'Content-Type': "text/html" + "; charset=UTF-8" });
                response.write(html);
                response.end();
                printReqInfo(request, response);
            } else {
                let headerReturn = false;
                fs.createReadStream(realPath, {
                        highWaterMark: 1024**2 // 文件一次读多少字节,默认 64*1024
                        // flags: 'r', // 默认 'r'
                        // autoClose: true, // 默认读取完毕后自动关闭
                        // start: 0, // 读取文件开始位置
                        // end: stat.size, // 流是闭合区间 包含start也含end
                        // encoding: 'utf8' // 默认null
                    }).on("err", err => {
                        response.writeHead(500, { 'Content-Type': 'text/plain; charset=UTF-8' });
                        response.end(err.toString());
                        printReqInfo(request, response);
                        console.log(err);
                    }).on('data', chunk => {
                        if (!headerReturn) {
                            let contentType = MIME[ext] || 'application/octet-stream';
                            if ((!MIME_NO_JUDGE.contains(contentType)) && (contentType.startsWith('text') || (MIME_TEXT.contains(contentType)))) {
                                let charset = jschardet.detect(chunk).encoding;
                                if (charset) contentType += '; charset=' + charset;
                            }
                            response.writeHead(200, { 'Content-Type': contentType, 'Content-Length': stat.size });
                            headerReturn = true;
                        }
                        response.write(chunk);
                    }).on('end', () => {
                        response.end();
                        printReqInfo(request, response);
                    });
            }
        } else {
            response.writeHead(404, { 'Content-Type': 'text/plain; charset=UTF-8' });
            response.write('This request URL ' + pathname + ' was not found on this server.');
            response.end();
            printReqInfo(request, response);
        }
    });
});
server.listen(PORT);
console.log("Server runing at port: " + PORT + "...\n");

/**
 * 打印 请求/响应信息
 * */
function printReqInfo(request, response) {
    let pathname = decodeURI(url.parse(request.url).pathname);
    let method = request.method;
    let _headers = request.socket._httpMessage._header.split('\r\n');
    let reqVersion = _headers[0];
    let date = new Date(_headers[2].replace('Date: ', '')).toLocaleString();
    console.log('['+date+'] --', '"'+method, reqVersion+'"', pathname);
}

/**
 * 判断数组中是否包含某个元素
 * @param {*} obj 要判断的元素
 */
Array.prototype.contains = function(obj) {
    let _this = this;
    for (let i=0,len=_this.length; i<len; i++) {
        if (_this[i] === obj) return true;
    }
    return false;
}

/** MIME文件类型 */
const MIME = {
    'unknown': 'application/octet-stream',
    'evy': 'application/envoy',
    'fif': 'application/fractals',
    'spl': 'application/futuresplash',
    'hta': 'application/hta',
    'acx': 'application/internet-property-stream',
    'jar': 'application/java-archive',
    'json': 'application/json',
    'hqx': 'application/mac-binhex40',
    'doc': 'application/msword',
    'dot': 'application/msword',
    'exe': 'application/octet-stream',
    'msi': 'application/octet-stream',
    'bin': 'application/octet-stream',
    'class': 'application/octet-stream',
    'dms': 'application/octet-stream',
    'lha': 'application/octet-stream',
    'lzh': 'application/octet-stream',
    'oda': 'application/oda',
    'axs': 'application/olescript',
    'pdf': 'application/pdf',
    'prf': 'application/pics-rules',
    'p10': 'application/pkcs10',
    'crl': 'application/pkix-crl',
    'ai': 'application/postscript',
    'eps': 'application/postscript',
    'ps': 'application/postscript',
    'rtf': 'application/rtf',
    'setpay': 'application/set-payment-initiation',
    'setreg': 'application/set-registration-initiation',
    'xla': 'application/vnd.ms-excel',
    'xlc': 'application/vnd.ms-excel',
    'xlm': 'application/vnd.ms-excel',
    'xlt': 'application/vnd.ms-excel',
    'xlw': 'application/vnd.ms-excel',
    'xls': 'application/vnd.ms-excel',
    'xlsx': 'application/vnd.ms-excel',
    'sst': 'application/vnd.ms-pkicertstore',
    'cat': 'application/vnd.ms-pkiseccat',
    'stl': 'application/vnd.ms-pkistl',
    'ppt': 'application/vnd.ms-powerpoint',
    'pptx': 'application/vnd.ms-powerpoint',
    'pps': 'application/vnd.ms-powerpoint',
    'pot,': 'application/vnd.ms-powerpoint',
    'mpp': 'application/vnd.ms-project',
    'docx': 'application/vnd.ms-word',
    'wcm': 'application/vnd.ms-works',
    'wdb': 'application/vnd.ms-works',
    'wks': 'application/vnd.ms-works',
    'wps': 'application/vnd.ms-works',
    'odt': 'application/vnd.oasis.opendocument.text',
    'hlp': 'application/winhlp',
    '7z': 'application/x-7z-compressed',
    'bcpio': 'application/x-bcpio',
    'bz': 'application/x-bzip2',
    'bz2': 'application/x-bzip2',
    'tbz': 'application/x-bzip2',
    'cdf': 'application/x-cdf',
    'z': 'application/x-compress',
    'tgz': 'application/x-compressed',
    'cpio': 'application/x-cpio',
    'csh': 'application/x-csh',
    'dcr': 'application/x-director',
    'dir': 'application/x-director',
    'dxr': 'application/x-director',
    'dvi': 'application/x-dvi',
    'woff': 'application/x-font-woff',
    'gtar': 'application/x-gtar',
    'gz': 'application/x-gzip',
    'hdf': 'application/x-hdf',
    'ins': 'application/x-internet-signup',
    'isp': 'application/x-internet-signup',
    'iii': 'application/x-iphone',
    'js': 'application/x-javascript',
    'latex': 'application/x-latex',
    'mdb': 'application/x-msaccess',
    'crd': 'application/x-mscardfile',
    'clp': 'application/x-msclip',
    'dll': 'application/x-msdownload',
    'm13': 'application/x-msmediaview',
    'm14': 'application/x-msmediaview',
    'mvb': 'application/x-msmediaview',
    'wmf': 'application/x-msmetafile',
    'mny': 'application/x-msmoney',
    'pub': 'application/x-mspublisher',
    'scd': 'application/x-msschedule',
    'trm': 'application/x-msterminal',
    'wri': 'application/x-mswrite',
    'pma': 'application/x-perfmon',
    'pmc': 'application/x-perfmon',
    'pml': 'application/x-perfmon',
    'pmr': 'application/x-perfmon',
    'pmw': 'application/x-perfmon',
    'p12': 'application/x-pkcs12',
    'pfx': 'application/x-pkcs12',
    'p7b': 'application/x-pkcs7-certificates',
    'spc': 'application/x-pkcs7-certificates',
    'p7r': 'application/x-pkcs7-certreqresp',
    'p7c': 'application/x-pkcs7-mime',
    'p7m': 'application/x-pkcs7-mime',
    'p7s': 'application/x-pkcs7-signature',
    'rar': 'application/x-rar',
    'sh': 'application/x-sh',
    'shar': 'application/x-shar',
    'swf': 'application/x-shockwave-flash',
    'sit': 'application/x-stuffit',
    'sv4cpio': 'application/x-sv4cpio',
    'sv4crc': 'application/x-sv4crc',
    'tar': 'application/x-tar',
    'tcl': 'application/x-tcl',
    'tex': 'application/x-tex',
    'texi': 'application/x-texinfo',
    'texinfo': 'application/x-texinfo',
    'roff': 'application/x-troff',
    't': 'application/x-troff',
    'tr': 'application/x-troff',
    'man': 'application/x-troff-man',
    'me': 'application/x-troff-me',
    'ms': 'application/x-troff-ms',
    'ustar': 'application/x-ustar',
    'src': 'application/x-wais-source',
    'cer': 'application/x-x509-ca-cert',
    'crt': 'application/x-x509-ca-cert',
    'der': 'application/x-x509-ca-cert',
    'xml': 'application/xml',
    'pko': 'application/ynd.ms-pkipko',
    'zip': 'application/zip',
    'au': 'audio/basic',
    'snd': 'audio/basic',
    'mid': 'audio/mid',
    'rmi': 'audio/mid',
    'mp4a': 'audio/mp4',
    'mp3': 'audio/mpeg',
    'ogg': 'audio/ogg',
    'weba': 'audio/webm',
    'aif': 'audio/x-aiff',
    'aifc': 'audio/x-aiff',
    'aiff': 'audio/x-aiff',
    'm3u': 'audio/x-mpegurl',
    'wma': 'audio/x-ms-wma',
    'ra': 'audio/x-pn-realaudio',
    'ram': 'audio/x-pn-realaudio',
    'wav': 'audio/x-wav',
    'bmp': 'image/bmp',
    'cod': 'image/cis-cod',
    'gif': 'image/gif',
    'ief': 'image/ief',
    'jpeg': 'image/jpeg',
    'jpg': 'image/jpeg',
    'jpe': 'image/jpeg',
    'jfif': 'image/pipeg',
    'png': 'image/png',
    'svg': 'image/svg+xml',
    'tiff': 'image/tiff',
    'tif': 'image/tiff',
    'psd': 'image/vnd.adobe.photoshop',
    'webp': 'image/webp',
    'ras': 'image/x-cmu-raster',
    'cmx': 'image/x-cmx',
    'ico': 'image/x-icon',
    'icon': 'image/x-icon',
    'pnm': 'image/x-portable-anymap',
    'pbm': 'image/x-portable-bitmap',
    'pgm': 'image/x-portable-graymap',
    'ppm': 'image/x-portable-pixmap',
    'rgb': 'image/x-rgb',
    'tga': 'image/x-targa',
    'xbm': 'image/x-xbitmap',
    'xpm': 'image/x-xpixmap',
    'xwd': 'image/x-xwindowdump',
    'mht': 'message/rfc822',
    'mhtml': 'message/rfc822',
    'nws': 'message/rfc822',
    'css': 'text/css',
    '323': 'text/h323',
    'html': 'text/html',
    'htm': 'text/html',
    'stm': 'text/html',
    'shtml': 'text/html',
    'uls': 'text/iuls',
    'md': 'text/markdown',
    'markdown': 'text/markdown',
    'txt': 'text/plain',
    'bat': 'text/plain',
    'vbs': 'text/plain',
    'ps1': 'text/plain',
    'bas': 'text/plain',
    'c': 'text/plain',
    'h': 'text/plain',
    'rdp': 'text/plain',
    'rtx': 'text/richtext',
    'rtfd': 'text/rtfd',
    'sct': 'text/scriptlet',
    'tsv': 'text/tab-separated-values',
    'htt': 'text/webviewhtml',
    'htc': 'text/x-component',
    'java': 'text/x-java-source',
    'pl': 'text/x-perl',
    'php': 'text/x-php',
    'py': 'text/x-python',
    'py3': 'text/x-python',
    'rb': 'text/x-ruby',
    'etx': 'text/x-setext',
    'sql': 'text/x-sql',
    'vcf': 'text/x-vcard',
    'mp4': 'video/mp4',
    'mpeg': 'video/mpeg',
    'mpg': 'video/mpeg',
    'mp2': 'video/mpeg',
    'mpa': 'video/mpeg',
    'mpe': 'video/mpeg',
    'mpv2': 'video/mpeg',
    'mov': 'video/quicktime',
    'qt': 'video/quicktime',
    'webm': 'video/webm',
    'dv': 'video/x-dv',
    'flv': 'video/x-flv',
    'lsf': 'video/x-la-asf',
    'lsx': 'video/x-la-asf',
    'mkv': 'video/x-matroska',
    'asf': 'video/x-ms-asf',
    'asr': 'video/x-ms-asf',
    'asx': 'video/x-ms-asf',
    'wmv': 'video/x-ms-wmv',
    'wm': 'video/x-ms-wmv',
    'avi': 'video/x-msvideo',
    'movie': 'video/x-sgi-movie',
    'flr': 'x-world/x-vrml',
    'vrml': 'x-world/x-vrml',
    'wrl': 'x-world/x-vrml',
    'wrz': 'x-world/x-vrml',
    'xaf': 'x-world/x-vrml',
    'xof': 'x-world/x-vrml'
};
/** MIME类型不是以 text 开头的文本类型 */
const MIME_TEXT = [
    MIME['json'], MIME['csh'], MIME['sh'], MIME['js']
];
/** 不判断字符编码的类型,jschardet判断文件编码时有很大几率出现错误,预设一些不需要判断编码格式的(如HTML文件代码中自带编码格式)类型 */
const MIME_NO_JUDGE = [MIME['htm'], MIME['html'], MIME['stm'], MIME['shtml']];
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值