node静态服务器tudo

实现以下功能

  • 读取静态文件或目录
  • MIME类型支持
  • 缓存支持/控制
  • 支持gzip压缩
  • 访问目录可以自动寻找下面的index.html文件
  • Range支持,断点续传
  • 图片防盗链
  • 后台运行
【node静态服务器tudo】基本功能读取静态文件
此功能分为两部分:
* 1、返回文件 调用fs建立可读流读取文件返回客户端 fs.createReadStream(filepath).pipe(res); * 2、返回目录 因为是要返回当前请求路径下的所有目录,并且支持点击进入下一级,所有返回文件为html 采用handlebars模板引擎编辑模板// 处理模板函数 let list = function () { let template = fs.readFileSync(listTemplatePath, 'utf8'); return handlebars.compile(template); }// template 文件 {{title}} - 锐客网
    {{#each files}}
  • {{name}}
  • {{/each}}
// 读取当前路径下的所有目录 let { promisify, inspect } = util; // 转变fs.readdir 异步方法返回promise let readdirPromise = promisify(fs.readdir); // 返回filepath下所有目录 let files = await readdirPromise(filepath) // 处理files为我们想要的数据 files = files.map(file => ({ name: file, url: path.join(pathname, file), }))// 最终返回客户端的html // list let html = this.list({ title: pathname, files, });

MIME类型支持
res.setHeader('Content-Type', mime.getType(filepath));

缓存支持/控制
toCache (req, res, filepath, stat) { // 强制缓存 res.setHeader('Cache-Control', 'private,max-age=60'); // http 1.1 // private 客户端可以缓存 // public 客户端和代理服务器都可以缓存 // max-age=60 缓存内容将在60秒后失效 // no-cache 需要使用对比缓存验证数据,强制向源服务器再次验证 // no-store 所有内容都不会缓存,强制缓存和对比缓存都不会触发 res.setHeader('Expires', new Date(Date.now() + 60 * 1000).toUTCString()); // http 1.0// 对比缓存 // last-modify let ifModifiedSince = req.headers['if-modified-since']; let lastModified = stat.ctime.toGMTString(); // etag let ifNoneMatch = req.headers['if-none-match']; let eTag = cypto.createHash('sha1').update(stat.ctime.toGMTString() + stat.size).digest('hex'); if (ifModifiedSince || ifNoneMatch) { if (ifNoneMatch === eTag && lastModified === ifModifiedSince) { res.statusCode = 304; res.end(''); return false; } if ((ifModifiedSince && lastModified === ifModifiedSince) || (ifNoneMatch && ifNoneMatch === eTag)) { res.statusCode = 304; res.end(''); return false; } } res.setHeader('Last-Modified', lastModified); res.setHeader('ETag', eTag); return true; }

压缩支持
async gzip(req, res, gzipInfo) { let encoding = req.headers['accept-encoding']; let gzipReg = /\bgzip\b/; let deflateReg = /\bdeflate\b/; let type, streamInfo; if (gzipReg.test(encoding)) { streamInfo = gzipInfo ? await gzipPromise(gzipInfo) : zlib.createGzip(); type = 'gzip'; } else if (deflateReg.test(encoding)) { streamInfo = gzipInfo ? await deflatePromise(gzipInfo) : zlib.createDeflate(); type = 'deflate'; } if (type) { res.setHeader('Content-Encoding', type); } return streamInfo; }

访问目录可以自动寻找下面的index.html文件
let indexFilepath = path.join(filepath, '/', 'index.html'); try { let statIndex = await statPromise(indexFilepath); if (statIndex) { // 返回 inde.html文件 return this.sendFile(req, res, indexFilepath, stat); } } catch (e) {}

Range支持,断点续传
range (req, res, filepath, stat) { res.setHeader('Accept-Range', 'bytes'); // 通知客户端支持获取部分资源 let range = req.headers['range']; // Range: bytes=0-xxx let start = 0, end = stat.size; if (range) { let result = range.match(/bytes=(\d*)-(\d*)/); start = isNaN(result[1]) ? result[1] : start; end = isNaN(result[2]) ? result[2] : end; } return fs.createReadStream(filepath, { start, end, }) }

图片防盗链
notSteal (req, res) { console.log(path.join(process.cwd(), 'imgs/load.png')) let refer = req.headers['referer'] || req.headers['refer']; //如果说有refer的话,则表示是从HTML页面中引用过来的 if (refer) { let { host } = Url.parse(refer); if (host !== hostDomain) { // 返回默认图 return fs.createReadStream(path.join(process.cwd(), 'imgs/load.png')); } } }

开启子进程运行
let { spawn } =require('child_process'); let fs = require('fs'); let serverPath = '../src/child.js'; let child = spawn('node', [serverPath], { detached: true, stdio: ['ignore', process.stdout, 'ignore'] })child.unref();

  • 功能尚不完善,之后会慢慢丰富!
  • 更多细节源码请查看我的GitHub sevenStatic
参考
  • Node.js静态文件服务器实战

    推荐阅读