天天看点

Node.js 写一个简单的静态资源服务器 6 压缩文件

我们在Chrome 的DevTools 里打开 network 面板,看一下我们的一个请求,它的Request Header 中有一项是“Accept-Encoding”,它是指浏览器可以接受的压缩格式为 gzip, deflate, br。

Node.js 写一个简单的静态资源服务器 6 压缩文件

下图中,Response Header 中 “Content-Encoding” 指,返回的数据是 gzip 格式的。

Node.js 写一个简单的静态资源服务器 6 压缩文件

使用压缩格式,最大的好处就是减少了HTTP 的传输量。

那我们就在我们的静态服务器上实现一下文件压缩传输。

不是所有的文件都要压缩,我们在 src/config 下的 defaultConfig.js 里定义一下,哪些文件是要压缩的。如下。

module.exports = {
  root: process.cwd(),
  hostname: '127.0.0.1',
  port: 9527,
  compress: /\.(html|css|js|md)$/
}
           

接下来,我们来写一个压缩的方法。在src/helper 里面新建文件 compress.js 。

当然,我们先去 src/helper 下的 route.js 里看一看我们需要压缩的内容。如下。当路径是文件时,我们做压缩。这时候返回给res 的是一个ReadStream。所以我们应该是对这个 ReadStream 进行压缩。

const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
const promisify = require('util').promisify;
const conf = require('../config/defaultConfig');
const mime = require('./mime');

const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);

const ejsPath = path.join(__dirname, '../templates/main-page.ejs');
const source = fs.readFileSync(ejsPath,'utf-8');

module.exports = async function (req, res, filePath) {
    try {
        const stats = await stat(filePath);
        if (stats.isFile()) {
          const contentType = mime(filePath);
          res.statusCode = 200;
          res.setHeader('Content-Type', contentType);
          fs.createReadStream(filePath).pipe(res)
        }
        if (stats.isDirectory()) {
          const files = await readdir(filePath);
          res.statusCode = 200;
          res.setHeader('Content-Type', 'text/html');
          const dir = path.relative(conf.root, filePath)
          const data = {
            title: path.basename(filePath),
            dir: dir ? `/${dir}` : '',
            files: files.map((file) => {
              return {
                file,
                icon: mime(file)
              }
            })
          };
          res.end(ejs.render(source, data));
        }
      } catch(ex) {
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        res.end(`${filePath} is not a file or directory`);
      }
}
           

下面我们来写compress.js 如下。

const {createGzip, createDeflate} = require('zlib');

module.exports = (rs, req, res) => {
    const acceptEncoding = req.headers['accept-encoding'];
    if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
        // 浏览器没有传递accept-encoding,或者传递过来的accept-encoding 与我们定义的不匹配
        return rs;
    } else if(acceptEncoding.match(/\bgzip\b/)){
        res.setHeader('Content-Encoding', 'gzip');
        return rs.pipe(createGzip());
    } else if(acceptEncoding.match(/\bdeflate\b/)){
        res.setHeader('Content-Encoding', 'deflate');
        return rs.pipe(createDeflate());
    }
}
           

然后,在route.js 中修改一下,如下。

const fs = require('fs');
const path = require('path');
const ejs = require('ejs');
const promisify = require('util').promisify;
const conf = require('../config/defaultConfig');
const mime = require('./mime');
const compress = require('./compress');

const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);

const ejsPath = path.join(__dirname, '../templates/main-page.ejs');
const source = fs.readFileSync(ejsPath,'utf-8');

module.exports = async function (req, res, filePath) {
    try {
        const stats = await stat(filePath);
        if (stats.isFile()) {
          const contentType = mime(filePath);
          res.statusCode = 200;
          res.setHeader('Content-Type', contentType);
          let rs = fs.createReadStream(filePath);
          if (filePath.match(conf.compress)) {
            rs = compress(rs, req, res)
          }
          rs.pipe(res)
        }
        if (stats.isDirectory()) {
          const files = await readdir(filePath);
          res.statusCode = 200;
          res.setHeader('Content-Type', 'text/html');
          const dir = path.relative(conf.root, filePath)
          const data = {
            title: path.basename(filePath),
            dir: dir ? `/${dir}` : '',
            files: files.map((file) => {
              return {
                file,
                icon: mime(file)
              }
            })
          };
          res.end(ejs.render(source, data));
        }
      } catch(ex) {
        console.log(ex);
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        res.end(`${filePath} is not a file or directory`);
      }
}
           

我们启动服务查看一下,发送 .js 结尾的url 的话,如下。

Node.js 写一个简单的静态资源服务器 6 压缩文件

然后,我们来看一下压缩后的大小

Node.js 写一个简单的静态资源服务器 6 压缩文件

只有435B, 而这个文件非压缩的大小是1KB.

Done!

继续阅读