您好,登錄后才能下訂單哦!
這篇文章主要介紹了如何利用Node實(shí)現(xiàn)內(nèi)容壓縮,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
在查看自己的應(yīng)用日志時(shí),發(fā)現(xiàn)進(jìn)入日志頁面后總是要幾秒鐘才會加載(接口沒做分頁),于是打開網(wǎng)絡(luò)面板查看
這才發(fā)現(xiàn)接口返回的數(shù)據(jù)都沒有被壓縮,本以為接口用Nginx反向代理了,Nginx會自動幫我做這一層(這塊后面探究一下,理論上是可行的)
這里的后端是 Node 服務(wù)
下面的客戶端均指瀏覽器
客戶端在向服務(wù)端發(fā)起請求時(shí),會在請求頭(request header)中添加accept-encoding
字段,其值標(biāo)明客戶端支持的壓縮內(nèi)容編碼
格式
服務(wù)端在對返回內(nèi)容執(zhí)行壓縮后,通過在響應(yīng)頭(response header)中添加content-encoding
,來告訴瀏覽器內(nèi)容實(shí)際壓縮使用的編碼算法
deflate
是同時(shí)使用了LZ77
算法與哈夫曼編碼(Huffman Coding)
的一個(gè)無損數(shù)據(jù)壓縮算法。
gzip
是基于 DEFLATE
的算法
br
指代Brotli
,該數(shù)據(jù)格式旨在進(jìn)一步提高壓縮比,對文本的壓縮相對deflate
能增加20%
的壓縮密度,而其壓縮與解壓縮速度則大致不變
Node.js包含一個(gè)zlib 模塊
,提供了使用 Gzip
、Deflate/Inflate
、以及 Brotli
實(shí)現(xiàn)的壓縮功能
這里以gzip
為例分場景列舉多種使用方式,Deflate/Inflate
與Brotli
使用方式一樣,只是API不一樣
基于stream
的操作
基于buffer
的操作
引入幾個(gè)所需的模塊
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`
解/壓縮結(jié)果查看,這里使用du
指令直接統(tǒng)計(jì)解壓縮前后結(jié)果
# 執(zhí)行 du -ah tests # 結(jié)果如下 108K tests/origin.log.gz 2.2M tests/origin.log 2.2M tests/origin.log.un.gz 4.6M tests
流(stream)
的操作使用createGzip
與createUnzip
注:所有 zlib
API,除了那些顯式同步的 API,都使用 Node.js 內(nèi)部線程池,可以看做是異步的
因此下面的示例中的壓縮和解壓代碼應(yīng)分開執(zhí)行,否則會報(bào)錯(cuò)
方式1: 直接利用實(shí)例上的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
,可在回掉中單獨(dú)做其它的處理
// 壓縮 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
的操作利用 gzip
與 unzip
API,這兩個(gè)方法包含同步
與異步
類型
壓縮
gzip
gzipSync
解壓
unzip
unzipSync
方式1: 將readStream
轉(zhuǎn)Buffer
,然后進(jìn)行進(jìn)一步操作
gzip:異步
// 壓縮 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) }) })
gzipSync:同步
// 壓縮 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)
除了對文件壓縮,有時(shí)候也許要對傳輸?shù)膬?nèi)容進(jìn)行直接進(jìn)行解壓縮
這里以壓縮文本內(nèi)容為例
// 測試數(shù)據(jù) const testData = fs.readFileSync(testFile, { encoding: 'utf-8' })
流(stream)
操作這塊就考慮 string
=> buffer
=> stream
的轉(zhuǎn)換就行
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)
這里以寫入到文件示例,當(dāng)然也可以寫到其它的流里,如HTTP的Response
(后面會單獨(dú)介紹)
transformStream .pipe(zlib.createGzip()) .pipe(fs.createWriteStream(targetFile))
Buffer
操作同樣利用Buffer.from
將字符串轉(zhuǎn)buffer
const buffer = Buffer.from(testData)
然后直接使用同步API進(jìn)行轉(zhuǎn)換,這里result就是壓縮后的內(nèi)容
const result = zlib.gzipSync(buffer)
可以寫入文件,在HTTP Server
中也可直接對壓縮后的內(nèi)容進(jìn)行返回
fs.writeFileSync(targetFile, result)
這里直接使用Node中 http
模塊創(chuàng)建一個(gè)簡單的 Server 進(jìn)行演示
在其他的 Node Web
框架中,處理思路類似,當(dāng)然一般也有現(xiàn)成的插件,一鍵接入
const http = require('http') const { PassThrough, pipeline } = require('stream') const zlib = require('zlib') // 測試數(shù)據(jù) const testTxt = '測試數(shù)據(jù)123'.repeat(1000) const app = http.createServer((req, res) => { const { url } = req // 讀取支持的壓縮算法 const acceptEncoding = req.headers['accept-encoding'].match(/(br|deflate|gzip)/g) // 默認(rèn)響應(yīng)的數(shù)據(jù)類型 res.setHeader('Content-Type', 'application/json; charset=utf-8') // 幾個(gè)示例的路由 const routes = [ ['/gzip', () => { if (acceptEncoding.includes('gzip')) { res.setHeader('content-encoding', 'gzip') // 使用同步API直接壓縮文本內(nèi)容 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('<h2>BrotliCompress</h2>')) originStream.write(Buffer.from('<h3>測試數(shù)據(jù)</h3>')) 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(`<h2>404: ${url}</h2> <h3>已注冊路由</h3> <ul> ${routes.map(r => `<li><a href="${r[0]}">${r[0]}</a></li>`).join('')} </ul> `) res.end() }) app.listen(3000)
感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“如何利用Node實(shí)現(xiàn)內(nèi)容壓縮”這篇文章對大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識等著你來學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。