65 # 实现 http-server 里的 gzip 压缩

用 zlib 来实现 gzip 压缩

服务端优化都是:压缩 + 缓存

前端可以通过 webpack 插件进行压缩

gzip 根据替换来实现的,重复率越高,压缩后的结果越小

javascript 复制代码
const zlib = require("zlib");
const fs = require("fs");
const path = require("path");

方式一:读取文件压缩,如果文件大的话都读取到内存中,耗内存

javascript 复制代码
zlib.gzip(fs.readFileSync(path.resolve(__dirname, "./65/1.txt")), (err, data) => {
    fs.writeFileSync(path.resolve(__dirname, "./65/2.txt.gz"), data);
});

方式二:用转化流(可读可写)的方式,服务端的文件 => 压缩 => 客户端

javascript 复制代码
fs.createReadStream(path.resolve(__dirname, "./65/1.txt"))
    .pipe(zlib.createGzip())
    .pipe(fs.createWriteStream(path.resolve(__dirname, "./65/2.txt.gz")));

新建 1.txt 文件用来测试压缩,里面写的都是1,大概有个 10k 的样子,两种方式执行之后都会生成压缩文件 2.txt.gz,如下

代码实现 http-server 里的 gzip 压缩

下面实现 http-server 里的 gzip 压缩

实现思路就是根据请求头 header [accept-encoding] 里是否包含 gzip 来判断浏览器是否支持 gzip 压缩,如果支持就创建转化流处理。

在上一节的 server.js 里面添加代码如下

javascript 复制代码
// 核心模块
const http = require("http");
const path = require("path");
const url = require("url");
const fs = require("fs").promises;
const { createReadStream, createWriteStream, readFileSync } = require("fs");

// 第三方模块
const ejs = require("ejs"); // 服务端读取目录进行渲染
const mime = require("mime");
const chalk = require("chalk");
const debug = require("debug")("server");
// 根据环境变量来进行打印 process.env.EDBUG
debug("hello kaimo-http-server");

// 同步读取模板
const template = readFileSync(path.resolve(__dirname, "template.ejs"), "utf-8");

class Server {
    constructor(config) {
        this.host = config.host;
        this.port = config.port;
        this.directory = config.directory;
        this.template = template;
    }
    async handleRequest(req, res) {
        let { pathname } = url.parse(req.url);
        // 需要对 pathname 进行一次转义,避免访问中文名称文件找不到问题
        console.log(pathname);
        pathname = decodeURIComponent(pathname);
        console.log(pathname);
        // 通过路径找到这个文件返回
        let filePath = path.join(this.directory, pathname);
        console.log(filePath);
        try {
            // 用流读取文件
            let statObj = await fs.stat(filePath);
            // 判断是否是文件
            if (statObj.isFile()) {
                this.sendFile(req, res, filePath, statObj);
            } else {
                // 文件夹的话就先尝试找找 index.html
                let concatFilePath = path.join(filePath, "index.html");
                try {
                    let statObj = await fs.stat(concatFilePath);
                    this.sendFile(req, res, concatFilePath, statObj);
                } catch (e) {
                    // index.html 不存在就列出目录
                    this.showList(req, res, filePath, statObj, pathname);
                }
            }
        } catch (e) {
            this.sendError(req, res, e);
        }
    }
    // 列出目录
    async showList(req, res, filePath, statObj, pathname) {
        // 读取目录包含的信息
        let dirs = await fs.readdir(filePath);
        console.log(dirs, "-------------dirs----------");
        try {
            let parseObj = dirs.map((item) => ({
                dir: item,
                href: path.join(pathname, item) // url路径拼接自己的路径
            }));
            // 渲染列表:这里采用异步渲染
            let templateStr = await ejs.render(this.template, { dirs: parseObj }, { async: true });
            console.log(templateStr, "-------------templateStr----------");
            res.setHeader("Content-type", "text/html;charset=utf-8");
            res.end(templateStr);
        } catch (e) {
            this.sendError(req, res, e);
        }
    }
    gzip(req, res, filePath, statObj) {
        if (req.headers["accept-encoding"] && req.headers["accept-encoding"].includes("gzip")) {
            // 给响应头添加内容编码类型头,告诉浏览器内容是什么编码类型
            res.setHeader("Content-Encoding", "gzip");
            // 创建转化流
            return require("zlib").createGzip();
        } else {
            return false;
        }
    }
    // 读取文件返回
    sendFile(req, res, filePath, statObj) {
        // 设置类型
        res.setHeader("Content-type", mime.getType(filePath) + ";charset=utf-8");
        // 读取文件进行响应
        // 先判断浏览器是否支持 gzip 压缩
        let gzip = this.gzip(req, res, filePath, statObj);
        if (gzip) {
            createReadStream(filePath).pipe(gzip).pipe(res);
        } else {
            createReadStream(filePath).pipe(res);
        }
    }
    // 专门处理错误信息
    sendError(req, res, e) {
        debug(e);
        res.statusCode = 404;
        res.end("Not Found");
    }
    start() {
        const server = http.createServer(this.handleRequest.bind(this));
        server.listen(this.port, this.host, () => {
            console.log(chalk.yellow(`Starting up kaimo-http-server, serving ./${this.directory.split("\\").pop()}\r\n`));
            console.log(chalk.green(`       http://${this.host}:${this.port}`));
        });
    }
}

module.exports = Server;

我们启动服务,去访问 http://localhost:3000/1.txt

bash 复制代码
kaimo-http-server

跟没有压缩的对比如下:

相关推荐
城南vision1 小时前
计算机网络——HTTP篇
网络协议·计算机网络·http
‍。。。2 小时前
使用Rust实现http/https正向代理
http·https·rust
田三番20 小时前
使用 vscode 简单配置 ESP32 连接 Wi-Fi 每日定时发送 HTTP 和 HTTPS 请求
单片机·物联网·http·https·嵌入式·esp32·sntp
dulu~dulu20 小时前
查缺补漏----用户上网过程(HTTP,DNS与ARP)
网络·网络协议·http
丶213621 小时前
【网络】HTTP(超文本传输协议)详解
网络·网络协议·http
binqian1 天前
【k8s】ClusterIP能http访问,但是不能ping 的原因
http·容器·kubernetes
MetaverseMan1 天前
http防抖和ws防抖
网络·网络协议·http
暂时先用这个名字1 天前
常见 HTTP 状态码分类和解释及服务端向前端返回响应时的最完整格式
前端·后端·网络协议·http·状态码·国产化·响应
圈圈的熊2 天前
HTTP 和 HTTPS 的区别
前端·网络协议·http·https
这题怎么做?!?2 天前
【Linux】网络编程:实现一个简易的基于HTTP协议格式、TCP传输的服务器,处理HTTP请求并返回HTTP响应;GET方法再理解
linux·服务器·c语言·网络·c++·tcp/ip·http