我们在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!