天天看點

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!

繼續閱讀