基于Node.js的简易http服务器
Python的 http
模块可以直接开启一个简单的http服务器,平时用来测试很方便
python -m http.server
但是如此开启的服务器不支持post请求,因此照着文档使用node.js实现了一个,在Windows10下测试基本满足需要
由于对于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']];