node静态服务器tudo

实现以下功能
  • 读取静态文件或目录
  • MIME类型支持
  • 缓存支持/控制
  • 支持gzip压缩
  • 访问目录可以自动寻找下面的index.html文件
  • Range支持,断点续传
  • 图片防盗链
  • 后台运行

基本功能读取静态文件

此功能分为两部分:

* 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}}
    
    
        
    
    
    
    // 读取当前路径下的所有目录
    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静态文件服务器实战

你可能感兴趣的:(node静态服务器tudo)