我們在Chrome 的DevTools 裡打開 network 面闆,看一下我們的一個請求,它的Request Header 中有一項是“Accept-Encoding”,它是指浏覽器可以接受的壓縮格式為 gzip, deflate, br。
下圖中,Response Header 中 “Content-Encoding” 指,傳回的資料是 gzip 格式的。
使用壓縮格式,最大的好處就是減少了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 的話,如下。
然後,我們來看一下壓縮後的大小
隻有435B, 而這個檔案非壓縮的大小是1KB.
Done!