Node.js 静态服务器新知

1,329 阅读3分钟

用node http模块搭建服务器一直被用作项目实践及开发,深入学习后,对node搭建http服务器有了新的了解和认识,写此文总结罗列一下

  • 支持gzip压缩
  • 缓存支持/控制
  • yargs与自动化

Gzip

GZIP最早由Jean-loup Gailly和Mark Adler创建,用于UNIX系统的文件压缩。我们在Linux中经常会用到后缀为.gz的文件,它们就是GZIP格式的。现今已经成为Internet 上使用非常普遍的一种数据压缩格式,或者说一种文件格式。HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。大流量的WEB站点常常使用GZIP压缩技术来让用户感受更快的速度。这一般是指WWW服务器中安装的一个功能,当有人来访问这个服务器中的网站时,服务器中的这个功能就将网页内容压缩后传输到来访的电脑浏览器中显示出来.一般对纯文本内容可压缩到原大小的40%.这样传输就快了,效果就是你点击网址后会很快的显示出来.当然这也会增加服务器的负载. 一般服务器中都安装有这个功能模块的.

HTTP 请求头 Accept-Encoding 会将客户端能够理解的内容编码方式——通常是某种压缩算法——进行通知。通过内容协商的方式,服务端会选择一个客户端提议的方式,使用并在响应报文首部 Content-Encoding 中通知客户端该选择。

Accept-Encoding:gzip, deflate, br//当前请求支持格式为gzip,defalte,br

Content-Encoding:gzip // 当前内容压缩格式为gzip

可以用命令行设置请求 Accept-Encoding头

curl -v -H "Accept-Encoding:deflate" http://localhost:8080/msg.txt

http与Gzip

function request(req, res) {
    let { pathname } = url.parse(req.url);// /msg.txt
    //D:\vipcode\201801\16.http\msg.txt
    let filepath = path.join(__dirname, pathname);
    try {
        // await is only valid in async function
        let statObj = await stat(filepath);
        //可以根据不同的文件内容类型返回不同的Content-Type
        res.setHeader('Content-Type', mime.getType(pathname));
        //为了兼容不同的浏览器,node把所有的请求头全转成了小写
        let acceptEncoding = req.headers['accept-encoding'];
        // 内容协商
        if (acceptEncoding) {
            if (acceptEncoding.match(/\bgzip\b/)) {
                //服务器告诉 客户端我用什么压缩方法压缩了
                res.setHeader('Content-Encoding', 'gzip');
                fs.createReadStream(filepath).pipe(zlib.createGzip()).pipe(res);
            } else if (acceptEncoding.match(/\bdeflate\b/)) {
                res.setHeader('Content-Encoding', 'deflate');
                fs.createReadStream(filepath).pipe(zlib.createDeflate()).pipe(res);
            } else {
                fs.createReadStream(filepath).pipe(res);
            }
        } else {
            fs.createReadStream(filepath).pipe(res);
        }
    } catch (e) {
        res.statusCode = 404;
        res.end();
    }
}

根据拿到的 req.headers['accept-encoding'];类型对请求的静态资源进行相同类型的压缩

fs.createReadStream(filepath).pipe(zlib.xxx()).pipe(res);
tip:可以用util模块中的promisify 将一个异步方法转出一个返回promise的方法 (node v8以上)
let { promisify } = require('util'); 
let stat = promisify(fs.stat);

cache

缓存可以减少对服务器的请求,加快请求效率

  • lastModify
  • etag
  • expires
lastModify 通过最后修改时间来判断缓存是否可用
fs.stat(filepath, (err, stat) => {
        if (err) {
            return sendError(req, res);
        } else {
            let ifModifiedSince = req.headers['if-modified-since'];//请求头资源上次修改时间
            let LastModified = stat.ctime.toGMTString();//文件上次修改时间
            if (ifModifiedSince == LastModified) {// 如果相等则返回304
                res.writeHead(304);
                res.end('');
            } else {
                return send(req, res, filepath, stat);
            }
        }
    });
ETag是实体标签的缩写,根据实体内容生成的一段hash字符串,可以标识资源的状态。当资源发生改变时,ETag也随之发生变化。 ETag是Web服务端产生的,然后发给浏览器客户端。
fs.stat(file, (err, stat) => {
        if (err) {
            sendError(err, req, res, file, stat);
        } else {
            let ifNoneMatch = req.headers['if-none-match'];
            let etag = crypto.createHash('sha1').update(stat.ctime.toGMTString() + stat.size).digest('hex');
            if (ifNoneMatch) {
                if (ifNoneMatch == etag) {
                    res.writeHead(304);
                    res.end();
                } else {
                    send(req, res, file, etag);
                }
            } else {
                send(req, res, file, etag);
            }
        }
    });
设置服务器响应消息头字段Expires
res.setHeader('Expires', expires.toUTCString());
res.setHeader('Cache-Control', 'max-age=60');

自动化

mac新建自动化shell脚本区别与windows

$ touch hello.sh

$ chmod +x hello.sh

$ ./hello.sh

安装yargs

yargs 模块能够解决如何处理命令行参数。它也需要安装。
npm i yargs --save

编辑hello.sh

let yargs = require('yargs');
//它可以帮我们解析命令行参数,把参数数组变成对象的形式
// -n  --name
let argv = yargs.options('n', {
    alias: 'name',//别名
    demand: true,//必填
    default: 'yuanyuan',
    description: '请输入你的姓名'
})
    .usage('hellp [opitons]')
    .help()//指定帮助信息
    .example('hello -name yuanyuan', '执行hello命令,然后传入name参数为yuanyuan')
    .alias('h', 'help')
    .argv;
console.log(argv);

console.log('hello ' + argv.name);

添加package.json,并添加以下内容

{
  "name": "hello",
  "bin": {
    "hello": "hello"
  }
}

执行 npm link 命令。

$ npm link

就可以直接执行 hello命令了