Node构建一个静态文件服务器

静态文件效劳器完成的功用

读取静态文件

MIME范例支撑

缓存支撑/掌握

支撑gzip紧缩

Range支撑,断点续传

宣布为可实行敕令并能够背景运转,能够经由过程npm install -g装置

起首先构建好项目目次,项目目次以下:
project
|—bin 敕令行完成安排剧本
|
|—public 静态文件效劳器默许静态文件夹
|
|—src 完成功用的相干代码
| |
| |__template 模板文件夹
| |
| |__app.js 主要功用文件(main文件)
| |__config.js 设置文件
|
|—package.josn (初始化)

要启动一个效劳器,我们须要晓得这个效劳器的启动时的端口号,在config.js设置一下:

let config = {
 host:'localhost' //提醒用 ,
port:8080 //效劳器启动时刻的默许端口号,
path:path.resolve(__dirname,'..','test-dir') //静态效劳器启动时默许的事情目次
 }

读取静态文件之前起首要先启动效劳器,以后一切的要领都在class Server要领里

//handlebar 编译模板,获得一个衬着的要领,然后传入现实数据数据就能够获得衬着后的HTML了
function list() {
let tmpl = fs.readFileSync(path.resolve(__dirname, 'template', 'list.html'),'utf8');
return handlebars.compile(tmpl);//举行编译,末了衬着
}class Server {
    constructor(argv) {
        this.list = list();
        this.config = Object.assign({}, this.config, argv);
    }
    start() {
        let server = http.createServer();//建立效劳器
        //当客户端向效劳端发出数据的时刻,会动身request事宜
        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)}`);
        });
    }
//发送错误信息,
sendError(err, req, res) {
    res.statusCode = 500;
    res.end(`${err.toString()}`);
}}
module.exports = Server;

读取静态文件

设想思绪

起首输入一个url时,能够对应效劳器上的一个文件,或许对应一个目次,

搜检是不是文件照样目次假如文件不存在,返回404状况码,发送not found页面到客户端

假如文件存在:翻开文件读取

设置response header 发送文件到客户端

假如是目次就翻开目次列表

async request(req, res) {
    //先取到客户端想要的是文件或文件夹途径 
    let { pathname } = url.parse(req.url);//猎取途径的文件信息
    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)
            }));
            //handlebar 编译模板
            let html = this.list({
                title: pathname,
                files
            });
            res.setHeader('Content-Type', 'text/html');设置要求头
            res.end(html);
        } else {
            this.sendFile(req, res, filepath, statObj);//读取文件
        }
    } catch (e) {//不存在接见内就发送错误信息
        debug(inspect(e));//inspect把一个对象转成字符
        this.sendError(e, req, res);
    }
}

缓存支撑/掌握

设想思绪
缓存分为强迫缓存和对照缓存: 

  • 两类缓存划定规矩能够同时存在,强迫缓存优先级高于对照缓存,也就是说,当实行强迫缓存的划定规矩时,假如缓存见效,直接运用缓存,不再实行对照缓存划定规矩.
  •  强迫缓存假如见效,不须要再和效劳器发作交互,而对照缓存不论是不是见效,都须要与效劳端发作交互
  1. 第一次接见效劳器的时刻,效劳器返回资本和缓存的标识,客户端则会把此资本缓存在当地的缓存数据库中。
  2. 第二次客户端须要此数据的时刻,要获得缓存的标识,然后去问一下效劳器我的资本是不是是最新的。假如是最新的则直接运用缓存数据,假如不是最新的则效劳器返回新的资本和缓存划定规矩,客户端依据缓存划定规矩缓存新的数据。

经由过程末了修正时候来推断缓存是不是可用

  1. Last-Modified:相应时通知客户端此资本的末了修正时候 
  2. If-Modified-Since:当资本逾期时(运用Cache-Control标识的max-age),发明资本具有Last-Modified声明,则再次向效劳器要求时带上头If-Modified-Since。
  3. 效劳器收到要求后发明有头If-Modified-Since则与被要求资本的末了修正时候举行比对。若末了修正时候较新,申明资本又被改动过,则相应最新的资本内容并返回200状况码; 
  4. 若末了修正时候和If-Modified-Since一样,申明资本没有修正,则相应304示意未更新,示知浏览器继承运用所保留的缓存文件。

ETag是资本标签。假如资本没有变化它就不会变。

1.客户端想推断缓存是不是可用能够先猎取缓存中文档的ETag,然后经由过程If-None-Match发送要求给Web效劳器讯问此缓存是不是可用。
2 效劳器收到要求,将效劳器的中此文件的ETag,跟要求头中的If-None-Match相比较,假如值是一样的,申明缓存照样最新的,Web效劳器将发送304 Not Modified相应码给客户端示意缓存未修正过,能够运用。 
3.假如不一样则Web效劳器将发送该文档的最新版本给浏览器客户端

handleCache(req, res, filepath, statObj) {
    let ifModifiedSince = req.headers['if-modified-since'];
    let isNoneMatch = req.headers['is-none-match'];
    res.setHeader('Cache-Control', 'private,max-age=30');//max-age=30缓存内容将在30秒后失效
    res.setHeader('Expires', new Date(Date.now() + 30 * 1000).toGMTString());
    let etag = statObj.size;
    let lastModified = statObj.ctime.toGMTString();
    res.setHeader('ETag', etag);//猎取ETag
    res.setHeader('Last-Modified', lastModified);//效劳器文件的末了修正时候
    //任何一个对照缓存头不婚配,则不走缓存
    if (isNoneMatch && isNoneMatch != etag) {//缓存逾期
        return fasle;
    }
    if (ifModifiedSince && ifModifiedSince != lastModified) {//缓存逾期
        return fasle;
    }
    //当要求中存在任何一个对照缓存头,则返回304,否则不走缓存
    if (isNoneMatch || ifModifiedSince) {//缓存有用
        res.writeHead(304);
        res.end();
        return true;
    } else {
        return false;
    }
}

支撑gzip紧缩

设想思绪

浏览器都邑照顾本身支撑的紧缩范例,最经常使用的两种是gzip和deflate。依据要求头Accept-Encoding,返回差别的紧缩花样.

getEncoding(req, res) {

    let acceptEncoding = req.headers['accept-encoding'];//猎取客户端发送的紧缩要求头的信息
    if (/\bgzip\b/.test(acceptEncoding)) {//假如是gzip的花样
        res.setHeader('Content-Encoding', 'gzip');
        return zlib.createGzip();
    } else if (/\bdeflate\b/.test(acceptEncoding)) {//假如是deflate的花样
        res.setHeader('Content-Encoding', 'deflate');
        return zlib.createDeflate();
    } else {
        return null;//不紧缩
    }
}

Range支撑,断点续传

设想思绪

  • 该选项指定下载字节的局限,常应用于分块下载文件
  • 效劳器通知客户端能够运用range response.setHeader(‘Accept-Ranges’, ‘bytes’)  
  • Server经由过程要求头中的Range:bytes=0-xxx来推断是不是是做Range要求,假如这个值存在而且有用,则只发还要求的那部分文件内容,相应的状况码变成206,假如无效,则返回416状况码,表明Request

Range Not Satisfiable

 getStream(req, res, filepath, statObj) {
    let start = 0;//可读流肇端位置
    let end = statObj.size - 1;//可读流完毕位置
    let range = req.headers['range'];//猎取客户端的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
    });
}

宣布为可实行敕令

起首在package.json设置一下”bin”: { “http-static”: “bin/www” }

#! /usr/bin/env node     //这段代码一定要写在开首,为了兼容各个电脑平台的差异性
// -d --root 静态文件目次 -o --host 主机 -p --port 端口号let yargs = require('yargs');
let  Server = require('../src/app.js');
let argv = yargs.option('d',{   
 alias:'root', 
 demand:'false',  
 type:'string',   
 default:process.cwd(),  
 description:'静态文件跟目次'    })
.option('o',{  
  alias:'host',  
  demand:'localhost',  
  type:'string',    
description:'请设置监听的主机'})
.option('p',{  
  alias:'root',  
  demand:'false',   
 type:'number',   
 default:8080,  
  description:'请设置端口号'})
.usage('http-static [options]').example(  
  'http-static -d / 8080 -o localhost','在本机的9090端口上监听客户端的要求'
).help('h').argv;
// argv = {d,root,o,host,p,port}let server = new Server(argv);//启动效劳server.start();

如许敕令行当中经由过程输入http-static来直接启动静态文件效劳器了,那末敕令行挪用的功用也就完成了,末了用npm publish宣布一下,宣布到npm上面去了,我们就能够经由过程npm install -g来举行全局装置了

参考资料

Node.js静态文件效劳器实战

    原文作者:yingzi
    原文地址: https://segmentfault.com/a/1190000013871109
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞