首頁  >  文章  >  web前端  >  透過實踐聊聊利用Node怎麼實現內容壓縮

透過實踐聊聊利用Node怎麼實現內容壓縮

青灯夜游
青灯夜游轉載
2022-03-08 20:00:582018瀏覽

利用Nodejs怎麼實作內容壓縮?以下這篇文章給大家透過實作來聊聊Node側實作內容壓縮(gzip/br/deflate)的方法,希望對大家有幫助!

透過實踐聊聊利用Node怎麼實現內容壓縮

在查看自己的應用程式日誌時,發現進入日誌頁面後總是要幾秒鐘才會載入(介面沒做分頁),於是打開網路面板查看

透過實踐聊聊利用Node怎麼實現內容壓縮

透過實踐聊聊利用Node怎麼實現內容壓縮

這才發現介面回傳的資料都沒有被壓縮,本來以為介面用Nginx反向代理了,Nginx會自動幫我做這一層(這塊後面探究一下,理論上是可行的)

這裡的後端是Node 服務

本文就分享一下HTTP資料壓縮相關知識以及在Node側的實踐

#前置知識

下面的客戶端均指瀏覽器

accept-encoding

透過實踐聊聊利用Node怎麼實現內容壓縮

#客戶端在向服務端發起請求時,會在請求頭(request header)中加入accept-encoding字段,其值標示客戶端支援的壓縮內容編碼格式

content-encoding

透過實踐聊聊利用Node怎麼實現內容壓縮

##服務端在對返回內容執行壓縮後,透過在回應頭(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的操作

透過實踐聊聊利用Node怎麼實現內容壓縮

##基於

buffer 的操作

透過實踐聊聊利用Node怎麼實現內容壓縮引入幾個所需的模組

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

指令直接統計解壓縮前後結果<pre class="brush:js;toolbar:false;"># 执行 du -ah tests # 结果如下 108K tests/origin.log.gz 2.2M tests/origin.log 2.2M tests/origin.log.un.gz 4.6M tests</pre>基於

流(stream)

的操作#使用

createGzip

createUnzip

註:所有
    zlib
  • API,除了那些明確同步的API,都使用Node.js 內部執行緒池,可以看做是異步的因此下面的範例中的壓縮和解壓縮程式碼應分開執行,否則會報錯
  • ##方式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(&#39;util&#39;)
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

#的操作

利用gzip

unzip API,這兩個方法包含同步異步類型 壓縮

  • gzip
    • gzipSync
    • 解壓縮
  • #unzip
    • unzipSync
  • #方式1:

readStreamBuffer,然後進行進一步操作gzip:非同步

    // 压缩
    const buff = []
    readStream.on(&#39;data&#39;, (chunk) => {
        buff.push(chunk)
    })
    readStream.on(&#39;end&#39;, () => {
        zlib.gzip(Buffer.concat(buff), targetFile, (err, resBuff) => {
            if(err){
                console.error(err);
                process.exit()
            }
            fs.writeFileSync(targetFile,resBuff)
        })
    })
  • gzipSync:同步
    #
    // 压缩
    const buff = []
    readStream.on(&#39;data&#39;, (chunk) => {
        buff.push(chunk)
    })
    readStream.on(&#39;end&#39;, () => {
        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: &#39;utf-8&#39; })

基于流(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 框架中,处理思路类似,当然一般也有现成的插件,一键接入

透過實踐聊聊利用Node怎麼實現內容壓縮

const http = require(&#39;http&#39;)
const { PassThrough, pipeline } = require(&#39;stream&#39;)
const zlib = require(&#39;zlib&#39;)

// 测试数据
const testTxt = &#39;测试数据123&#39;.repeat(1000)

const app = http.createServer((req, res) => {
    const { url } = req
    // 读取支持的压缩算法
    const acceptEncoding = req.headers[&#39;accept-encoding&#39;].match(/(br|deflate|gzip)/g)

    // 默认响应的数据类型
    res.setHeader(&#39;Content-Type&#39;, &#39;application/json; charset=utf-8&#39;)

    // 几个示例的路由
    const routes = [
        [&#39;/gzip&#39;, () => {
            if (acceptEncoding.includes(&#39;gzip&#39;)) {
                res.setHeader(&#39;content-encoding&#39;, &#39;gzip&#39;)
                // 使用同步API直接压缩文本内容
                res.end(zlib.gzipSync(Buffer.from(testTxt)))
                return
            }
            res.end(testTxt)
        }],
        [&#39;/deflate&#39;, () => {
            if (acceptEncoding.includes(&#39;deflate&#39;)) {
                res.setHeader(&#39;content-encoding&#39;, &#39;deflate&#39;)
                // 基于流的单次操作
                const originStream = new PassThrough()
                originStream.write(Buffer.from(testTxt))
                originStream.pipe(zlib.createDeflate()).pipe(res)
                originStream.end()
                return
            }
            res.end(testTxt)
        }],
        [&#39;/br&#39;, () => {
            if (acceptEncoding.includes(&#39;br&#39;)) {
                res.setHeader(&#39;content-encoding&#39;, &#39;br&#39;)
                res.setHeader(&#39;Content-Type&#39;, &#39;text/html; charset=utf-8&#39;)
                // 基于流的多次写操作
                const originStream = new PassThrough()
                pipeline(originStream, zlib.createBrotliCompress(), res, (err) => {
                    if (err) {
                        console.error(err);
                    }
                })
                originStream.write(Buffer.from(&#39;<h1>BrotliCompress</h1>&#39;))
                originStream.write(Buffer.from(&#39;<h2>测试数据</h2>&#39;))
                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(&#39;Content-Type&#39;, &#39;text/html; charset=utf-8&#39;)
    res.end(`<h1>404: ${url}</h1>
    <h2>已注册路由</h2>
    <ul>
        ${routes.map(r => `<li><a href="${r[0]}">${r[0]}</a></li>`).join(&#39;&#39;)}
    </ul>
    `)
    res.end()
})

app.listen(3000)

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

以上是透過實踐聊聊利用Node怎麼實現內容壓縮的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除