• 技术文章 >web前端 >js教程

    通过实践来聊聊利用Node怎么实现内容压缩

    青灯夜游青灯夜游2022-03-11 20:05:54转载359
    利用Nodejs怎么实现内容压缩?下面本篇文章给大家通过实践来聊聊Node侧实现内容压缩(gzip/br/deflate)的方法,希望对大家有所帮助!

    在查看自己的应用日志时,发现进入日志页面后总是要几秒钟才会加载(接口没做分页),于是打开网络面板查看

    1.png

    2.png

    这才发现接口返回的数据都没有被压缩,本以为接口用Nginx反向代理了,Nginx会自动帮我做这一层(这块后面探究一下,理论上是可行的)

    这里的后端是 Node 服务

    本文就分享一下 HTTP数据压缩相关知识以及在Node侧的实践

    前置知识

    下面的客户端均指浏览器

    accept-encoding

    3.png

    客户端在向服务端发起请求时,会在请求头(request header)中添加accept-encoding字段,其值标明客户端支持的压缩内容编码格式

    content-encoding

    4.png

    服务端在对返回内容执行压缩后,通过在响应头(response header)中添加content-encoding,来告诉浏览器内容实际压缩使用的编码算法

    deflate/gzip/br

    deflate是同时使用了LZ77算法与哈夫曼编码(Huffman Coding)的一个无损数据压缩算法。

    gzip 是基于 DEFLATE 的算法

    br指代Brotli,该数据格式旨在进一步提高压缩比,对文本的压缩相对deflate能增加20%的压缩密度,而其压缩与解压缩速度则大致不变

    zlib模块

    Node.js包含一个zlib 模块,提供了使用 GzipDeflate/Inflate、以及 Brotli 实现的压缩功能

    这里以gzip为例分场景列举多种使用方式,Deflate/InflateBrotli使用方式一样,只是API不一样

    基于stream的操作

    5.png

    基于buffer的操作

    6.png

    引入几个所需的模块

    const zlib = require('zlib')
    const fs = require('fs')
    const stream = require('stream')
    const testFile = 'tests/origin.log'
    const targetFile = `${testFile}.gz`
    const decodeFile = `${testFile}.un.gz`

    文件的解/压缩

    解/压缩结果查看,这里使用du指令直接统计解压缩前后结果

    # 执行
    du -ah tests
    
    # 结果如下
    108K    tests/origin.log.gz
    2.2M    tests/origin.log
    2.2M    tests/origin.log.un.gz
    4.6M    tests

    基于流(stream)的操作

    使用createGzipcreateUnzip

    方式1: 直接利用实例上的pipe方法传递流

    // 压缩
    const readStream = fs.createReadStream(testFile)
    const writeStream = fs.createWriteStream(targetFile)
    readStream.pipe(zlib.createGzip()).pipe(writeStream)
    
    // 解压
    const readStream = fs.createReadStream(targetFile)
    const writeStream = fs.createWriteStream(decodeFile)
    readStream.pipe(zlib.createUnzip()).pipe(writeStream)

    方式2: 利用stream上的pipeline,可在回掉中单独做其它的处理

    // 压缩
    const readStream = fs.createReadStream(testFile)
    const writeStream = fs.createWriteStream(targetFile)
    stream.pipeline(readStream, zlib.createGzip(), writeStream, err => {
        if (err) {
            console.error(err);
        }
    })
    
    // 解压
    const readStream = fs.createReadStream(targetFile)
    const writeStream = fs.createWriteStream(decodeFile)
    stream.pipeline(readStream, zlib.createUnzip(), writeStream, err => {
        if (err) {
            console.error(err);
        }
    })

    方式3: Promise化pipeline方法

    const { promisify } = require('util')
    const pipeline = promisify(stream.pipeline)
    
    // 压缩
    const readStream = fs.createReadStream(testFile)
    const writeStream = fs.createWriteStream(targetFile)
    pipeline(readStream, zlib.createGzip(), writeStream)
        .catch(err => {
            console.error(err);
        })
    
    // 解压
    const readStream = fs.createReadStream(targetFile)
    const writeStream = fs.createWriteStream(decodeFile)
    pipeline(readStream, zlib.createUnzip(), writeStream)
        .catch(err => {
            console.error(err);
        })

    基于Buffer的操作

    利用 gzipunzip API,这两个方法包含同步异步类型

    方式1:readStreamBuffer,然后进行进一步操作

    // 压缩
    const buff = []
    readStream.on('data', (chunk) => {
        buff.push(chunk)
    })
    readStream.on('end', () => {
        zlib.gzip(Buffer.concat(buff), targetFile, (err, resBuff) => {
            if(err){
                console.error(err);
                process.exit()
            }
            fs.writeFileSync(targetFile,resBuff)
        })
    })
    // 压缩
    const buff = []
    readStream.on('data', (chunk) => {
        buff.push(chunk)
    })
    readStream.on('end', () => {
        fs.writeFileSync(targetFile,zlib.gzipSync(Buffer.concat(buff)))
    })

    方式2: 直接通过readFileSync读取

    // 压缩
    const readBuffer = fs.readFileSync(testFile)
    const decodeBuffer = zlib.gzipSync(readBuffer)
    fs.writeFileSync(targetFile,decodeBuffer)
    
    // 解压
    const readBuffer = fs.readFileSync(targetFile)
    const decodeBuffer = zlib.gzipSync(decodeFile)
    fs.writeFileSync(targetFile,decodeBuffer)

    文本内容的解/压缩

    除了对文件压缩,有时候也许要对传输的内容进行直接进行解压缩

    这里以压缩文本内容为例

    // 测试数据
    const testData = fs.readFileSync(testFile, { encoding: 'utf-8' })

    基于流(stream)操作

    这块就考虑 string => buffer => stream的转换就行

    string => buffer

    const buffer = Buffer.from(testData)

    buffer => stream

    const transformStream = new stream.PassThrough()
    transformStream.write(buffer)
    
    // or
    const transformStream = new stream.Duplex()
    transformStream.push(Buffer.from(testData))
    transformStream.push(null)

    这里以写入到文件示例,当然也可以写到其它的流里,如HTTP的Response(后面会单独介绍)

    transformStream
        .pipe(zlib.createGzip())
        .pipe(fs.createWriteStream(targetFile))

    基于Buffer操作

    同样利用Buffer.from将字符串转buffer

    const buffer = Buffer.from(testData)

    然后直接使用同步API进行转换,这里result就是压缩后的内容

    const result = zlib.gzipSync(buffer)

    可以写入文件,在HTTP Server中也可直接对压缩后的内容进行返回

    fs.writeFileSync(targetFile, result)

    Node Server中的实践

    这里直接使用Node中 http 模块创建一个简单的 Server 进行演示

    在其他的 Node Web 框架中,处理思路类似,当然一般也有现成的插件,一键接入

    7.png

    const http = require('http')
    const { PassThrough, pipeline } = require('stream')
    const zlib = require('zlib')
    
    // 测试数据
    const testTxt = '测试数据123'.repeat(1000)
    
    const app = http.createServer((req, res) => {
        const { url } = req
        // 读取支持的压缩算法
        const acceptEncoding = req.headers['accept-encoding'].match(/(br|deflate|gzip)/g)
    
        // 默认响应的数据类型
        res.setHeader('Content-Type', 'application/json; charset=utf-8')
    
        // 几个示例的路由
        const routes = [
            ['/gzip', () => {
                if (acceptEncoding.includes('gzip')) {
                    res.setHeader('content-encoding', 'gzip')
                    // 使用同步API直接压缩文本内容
                    res.end(zlib.gzipSync(Buffer.from(testTxt)))
                    return
                }
                res.end(testTxt)
            }],
            ['/deflate', () => {
                if (acceptEncoding.includes('deflate')) {
                    res.setHeader('content-encoding', 'deflate')
                    // 基于流的单次操作
                    const originStream = new PassThrough()
                    originStream.write(Buffer.from(testTxt))
                    originStream.pipe(zlib.createDeflate()).pipe(res)
                    originStream.end()
                    return
                }
                res.end(testTxt)
            }],
            ['/br', () => {
                if (acceptEncoding.includes('br')) {
                    res.setHeader('content-encoding', 'br')
                    res.setHeader('Content-Type', 'text/html; charset=utf-8')
                    // 基于流的多次写操作
                    const originStream = new PassThrough()
                    pipeline(originStream, zlib.createBrotliCompress(), res, (err) => {
                        if (err) {
                            console.error(err);
                        }
                    })
                    originStream.write(Buffer.from('<h1>BrotliCompress</h1>'))
                    originStream.write(Buffer.from('<h2>测试数据</h2>'))
                    originStream.write(Buffer.from(testTxt))
                    originStream.end()
                    return
                }
                res.end(testTxt)
            }]
        ]
        const route = routes.find(v => url.startsWith(v[0]))
        if (route) {
            route[1]()
            return
        }
    
        // 兜底
        res.setHeader('Content-Type', 'text/html; charset=utf-8')
        res.end(`<h1>404: ${url}</h1>
        <h2>已注册路由</h2>
        <ul>
            ${routes.map(r => `<li><a href="${r[0]}">${r[0]}</a></li>`).join('')}
        </ul>
        `)
        res.end()
    })
    
    app.listen(3000)

    更多node相关知识,请访问:nodejs 教程

    以上就是通过实践来聊聊利用Node怎么实现内容压缩的详细内容,更多请关注php中文网其它相关文章!

    声明:本文转载于:掘金社区,如有侵犯,请联系admin@php.cn删除
    专题推荐:Node 内容压缩
    上一篇:es6怎么将对象转成数组 下一篇:怎么快速掌握正则表达式?通过 AST 来学学正则语法!
    VIP课程(WEB全栈开发)

    相关文章推荐

    • 【腾讯云】年中优惠,「专享618元」优惠券!• 一文聊聊node的多进程和多线程• 聊聊node框架 Nest.js怎么松耦合地整合 Express !• 聊聊Node两种模块规范:CJS 与 ESM,有什么不同点?• 什么是流?深入了解Node.js中的可读流• 手把手教你在node中怎么优雅打印全链路日志
    1/1

    PHP中文网