实现一个可以在任意目录启动的静态文件服务器

一 实现的功能

该项目是一个可以在任意目录下启动的一个静态文件服务器,可以把当前目录做为静态文件根目录。该静态文件服务器实现的功能有:

  • 显示目录下面的文件列表和返回内容
  • 实现缓存
  • 实现压缩的功能
  • 获取部分数据

二 如何实现

1 yargs实现自动化

首先在package.json中配置bin:

 "bin": {
    "mystatic": "bin/static"
  }
复制代码

这样就可以直接在命令行执行bin目录下的mystatic文件了:

$ mystatic 
复制代码

而且我们可以在命令行里配置参数,我们使用yargs模块。yargs模块为命令行参数解析工具,可以将命令行里的配置信息转化为对象。

$ mystatic -d/--root 指定静态文件根目录 -p/--port 指定端口号 -o/--host 指定监听的主机 
复制代码

2 代码的实现

let config = require('./config');
let http = require('http');
let chalk = require('chalk');
let path = require('path');
let url = require('url');
let fs = require('fs');
let zlib = require('zlib');
let handlebars = require('handlebars');
let {promisify, inspect} = require('util');
let mime = require('mime');
let stat = promisify(fs.stat);
let readdir = promisify(fs.readdir);
let debug = require('debug')('static:app');


//编译模板,得到一个渲染的方法,然后传入实际数据数据就可以得到渲染后的HTML
function list() {
    let tmpl = fs.readFileSync(path.resolve(__dirname, 'template', 'list.html'), 'utf8');
    return handlebars.compile(tmpl);
}

// 创建一个Server 类
class Server {
    constructor(argv) {
        this.list = list();
        this.config = Object.assign({}, config, argv);
    }
    
    start() {
        let server = http.createServer();
        server.on('request', this.request.bind(this));
        server.listen(this.config.port, () => {
            let url = `http://${this.config.host}:${this.config.port}`;
            debug(`server started at ${chalk.green(url)}`);
        });
    }
    
    //静态文件服务器
    async request(req, res) {
        let {pathname} = url.parse(req.url);
        if (pathname == '/favicon.ico') {
            return this.sendError('not found', req, res);
        }
        let filepath = path.join(this.config.root, pathname);
        try {
            let statObj = await stat(filepath);
            if (statObj.isDirectory()) { //如果是目录的话,显示目录下面的文件列表
                let files = await readdir(filepath);
                files = files.map(file => ({
                    name: file,
                    url: path.join(pathname, file)
                }));
                let html = this.list({
                    title: pathname,
                    files
                });
                res.setHeader('Content-Type', 'text/html;charset=utf8');
                res.end(html);
            } else {
                this.sendFile(req, res, filepath, statObj);
            }
        } catch (e) {
            debug(inspect(e));
            this.sendError(e, req, res);
        }
    }
    
    sendFile(req, res, filepath, statObj) {
        res.setHeader('Content-Type', mime.getType(filepath) + ';charset=utf-8');
        res.setHeader('Content-Encoding', 'gzip');
        // 是否缓存
        if (this.handleCache(req, res, filepath, statObj)) return; //如果走缓存,则直接返回
        
        let encoding = this.getEncoding(req, res);
        
        let rs = this.getStream(req, res, filepath, statObj);
        
        if (encoding) {
            rs.pipe(encoding).pipe(res);
        } else {
            rs.pipe(res);
        }
    }
    
    // 实现缓存
    handleCache(req, res, filepath, statObj) {
        let ifModifiedSince = req.headers['if-modified-since'];
        let isNoneMatch = req.headers['if-none-match'];
        
        let etag = statObj.size;
        res.setHeader('ETag', etag);
        let lastModified = statObj.ctime.toGMTString();
        res.setHeader('Last-Modified', lastModified);
        
        res.setHeader('Cache-Control', 'private,max-age=30');
        res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toGMTString());
        
        if ((ifModifiedSince && ifModifiedSince == lastModified) && (isNoneMatch && isNoneMatch == etag)) {
            res.writeHead(304);
            res.end();
            return true;
        } else {
            return false;
        }
        
    }
    
    // 实现压缩
    getEncoding(req, res) {
        //Accept-Encoding:gzip, deflate
        let acceptEncoding = req.headers['accept-encoding'];
        if (/\bgzip\b/.test(acceptEncoding)) {
            res.setHeader('Content-Encoding', 'gzip');
            return zlib.createGzip();
            
        } else if (/\bdeflate\b/.test(acceptEncoding)) {
            res.setHeader('Content-Encoding', 'deflate');
            return zlib.createDeflate();
            
        } else {
            return null;
        }
    }
    
    // 实现部分内容的返回
    getStream(req, res, filepath, statObj) {
        let start = 0;
        let end = statObj.size - 1;
        
        let range = req.headers['range'];
        if (range) {
            res.setHeader('Accept-Range', 'bytes');
            res.statusCode = 206;//返回整个内容的一块
            let result = range.match(/bytes=(\d*)-(\d*)/);
            if (result) {
                start = isNaN(result[1]) ? start : parseInt(result[1]);
                end = isNaN(result[2]) ? end : parseInt(result[2]) - 1;
            }
        }
        return fs.createReadStream(filepath, {
            start, end
        });
    }
    
    
    sendError(err, req, res) {
        res.statusCode = 500;
        res.end(`${err.toString()}`);
    }
    
}

module.exports = Server;
复制代码

更多细节请查看我的GitHub mystatic,欢迎star

3 发布到npm

$ npm publish
复制代码

npm上直接搜索mystatic-now即可。

三 如何使用

  • 1 安装
$ npm install mystatic-now -g
复制代码
  • 2 在所需要查看的文件目录下启动命令行,输入
$ mystatic
复制代码
  • 3 在浏览器中输入localhost:8080/即可查看。

四 参考文献

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值