Al revisar los registros de mi propia aplicación, descubrí que siempre tarda unos segundos en cargarse después de ingresar a la página de registro (la interfaz no está paginada), así que abrí el panel de red para verificar
Solo entonces descubrí que los datos devueltos por la interfaz no estaban comprimidos. Pensé que la interfaz usaba el proxy inverso de Nginx y que Nginx me ayudaría automáticamente a hacer esta capa (exploraré esto más adelante, es teóricamente factible)
. Aquí está el servicio Node.
Este artículo compartirá conocimientos sobre HTTP数据压缩
Node侧的实践
. Todos los siguientes clientes se refieren a la
Cuando el cliente inicia una solicitud al servidor, agregará el campo accept-encoding
al encabezado de la solicitud, cuyo valor indica支持的压缩内容编码
实际压缩使用的编码算法
del contenido agregando content-encoding
al encabezado de respuesta.
deflate
utiliza tanto el algoritmo LZ77
como哈夫曼编码(Huffman Coding)
Algoritmo de compresión de datos basado en哈夫曼编码(Huffman Coding)
.
gzip
es un algoritmo basado en DEFLATE
br
se refiere a Brotli
. Este formato de datos tiene como objetivo mejorar aún más la relación de compresión. En comparación con deflate
, la compresión de texto puede aumentar la densidad de compresión en un 20%
, mientras que su velocidad de compresión y descompresión permanece aproximadamente sin cambios
Node.js contiene un zlib 模块
, que proporciona funciones de compresión implementadas usando Gzip
, Deflate/Inflate
y Brotli
Deflate/Inflate
Brotli
gzip
se usa como ejemplo para enumerar varios métodos de uso según los escenarios. de la misma manera, pero la API se
basa en la operación stream
.
Operaciones basadas en buffer
Introduzca varios módulos necesarios
const zlib = require('zlib') constante fs = requerir('fs') flujo constante = requerir('flujo') const testFile = 'pruebas/origin.log' const archivodestino = `${testFile}.gz` Const decodeFile = `${testFile}.un.gz`vista de resultados
/compresión del archivo, aquí use el comando du
para contar directamente los resultados antes y después de la descompresión
# Ejecutar pruebas du -ah # Los resultados son los siguientes 108K tests/origin.log.gz 2,2 millones de pruebas/origin.log 2,2 millones de pruebas/origin.log.un.gz 4,6 millones de pruebas
流(stream)
utilizando createGzip
y createUnzip
zlib
, excepto aquellas explícitamente síncronas, utilizan el grupo de subprocesos interno de Node.js y pueden considerarse asíncronas,Método 1: utilice directamente el método pipe
en la instancia para transferir la secuencia
// Compresión const readStream = fs.createReadStream(testFile) const writeStream = fs.createWriteStream(archivodestino) readStream.pipe(zlib.createGzip()).pipe(writeStream) // Descomprimir const readStream = fs.createReadStream(targetFile) const writeStream = fs.createWriteStream(decodeFile) readStream.pipe(zlib.createUnzip()).pipe(writeStream)
Método 2: utilizando pipeline
en stream
, puede realizar otro procesamiento por separado en la devolución de llamada
// Compresión const readStream = fs.createReadStream(testFile) const writeStream = fs.createWriteStream(archivodestino) stream.pipeline(readStream, zlib.createGzip(), writeStream, err => { si (errar) { consola.error(err); } }) // Descomprimir const readStream = fs.createReadStream(targetFile) const writeStream = fs.createWriteStream(decodeFile) stream.pipeline(readStream, zlib.createUnzip(), writeStream, err => { si (errar) { consola.error(err); } })
Método 3: Método pipeline
de promesa
const { promisify } = require('util') canalización constante = promisify(stream.pipeline) // Comprimir const readStream = fs.createReadStream(testFile) const writeStream = fs.createWriteStream(archivodestino) canalización (readStream, zlib.createGzip(), writeStream) .catch(err => { consola.error(err); }) // Descomprimir const readStream = fs.createReadStream(targetFile) const writeStream = fs.createWriteStream(decodeFile) canalización (readStream, zlib.createUnzip(), writeStream) .catch(err => { consola.error(err); })
Buffer
utilizan las API gzip
y unzip
. Estos dos同步
异步
gzip
gzipSync
unzip
unzipSync
método 1: convierta readStream
en Buffer
y luego realice más operaciones
// Compresión const buff = [] readStream.on('datos', (fragmento) => { buff.push(trozo) }) readStream.on('fin', () => { zlib.gzip(Buffer.concat(buff), targetFile, (err, resBuff) => { si(errar){ consola.error(err); proceso.salir() } fs.writeFileSync(archivodestino,resBuff) }) })
// compresión constante buff = [] readStream.on('datos', (fragmento) => { buff.push(trozo) }) readStream.on('fin', () => { fs.writeFileSync(targetFile,zlib.gzipSync(Buffer.concat(buff))) })
Método 2: Leer directamente a través de readFileSync
// Const readBuffer comprimido = fs.readFileSync(testFile) const decodeBuffer = zlib.gzipSync(readBuffer) fs.writeFileSync(archivo de destino,decodeBuffer) // Descomprime const readBuffer = fs.readFileSync(targetFile) const decodeBuffer = zlib.gzipSync(decodeFile) fs.writeFileSync(targetFile,decodeBuffer)
Además de la compresión de archivos, a veces puede ser necesario descomprimir directamente el contenido transmitido.
Aquí se muestra el contenido de texto comprimido como ejemplo
// Datos de prueba const testData = fs. readFileSync( testFile, { encoding: 'utf-8' })
流(stream)
, solo considere la conversión de string
=> buffer
=> stream
string
=> buffer
const buffer = Buffer.from(testData)
buffer
=> stream
const transformStream = nueva secuencia.PassThrough() transformStream.write(búfer) // o const transformStream = nueva secuencia.Duplex() transformStream.push(Buffer.from(testData)) transformStream.push(null)
Aquí hay un ejemplo de escritura en un archivo. Por supuesto, también se puede escribir en otras secuencias, como HTTP的Response
(se presentará por separado más adelante)
transformStream. .pipe(zlib.createGzip()) .pipe(fs.createWriteStream(targetFile))
también usa Buffer.from
para convertir la cadena en buffer
Buffer
const buffer = Buffer.from(testData)
y luego usa directamente la API de sincronización para la conversión. El resultado aquí es el comprimido. content
const result = zlib.gzipSync(buffer)
puede escribir archivos, y en HTTP Server
el contenido comprimido también se puede devolver directamente
fs.writeFileSync(targetFile, result)
Aquí usamos directamente el módulo http
en Node para crear un servidor simple Para demostraciones
en otros marcos Node Web
, las ideas de procesamiento son similares. Por supuesto, generalmente hay complementos listos para usar a los que se puede acceder con un solo clic.
constante http = requerir('http') const {PassThrough, canalización} = requerir('flujo') const zlib = requerir('zlib') //Datos de prueba const testTxt = 'Datos de prueba 123'.repeat(1000) aplicación constante = http.createServer((req, res) => { constante {url} = solicitud // Leer el algoritmo de compresión admitido const AcceptEncoding = req.headers['accept-encoding'].match(/(br|deflate|gzip)/g) //Tipo de datos de respuesta predeterminado res.setHeader('Content-Type', 'application/json; charset=utf-8') // Varias rutas de ejemplo const rutas = [ ['/gzip', () => { si (acceptEncoding.includes('gzip')) { res.setHeader('codificación de contenido', 'gzip') // Utilice la API de sincronización para comprimir directamente el contenido de texto res.end(zlib.gzipSync(Buffer.from(testTxt))) devolver } res.end(testTxt) }], ['/desinflar', () => { if (acceptEncoding.includes('desinflar')) { res.setHeader('codificación de contenido', 'desinflar') // Operación única basada en la secuencia const originStream = new PassThrough() origenStream.write(Buffer.from(testTxt)) origenStream.pipe(zlib.createDeflate()).pipe(res) origenStream.end() devolver } res.end(testTxt) }], ['/br', () => { si (acceptEncoding.includes('br')) { res.setHeader('codificación de contenido', 'br') res.setHeader('Tipo de contenido', 'texto/html; charset=utf-8') // Múltiples operaciones de escritura basadas en flujos const originStream = new PassThrough() canalización (originStream, zlib.createBrotliCompress(), res, (err) => { si (errar) { consola.error(err); } }) originStream.write(Buffer.from('<h1>BrotliCompress</h1>')) originStream.write(Buffer.from('<h2>Datos de prueba</h2>')) origenStream.write(Buffer.from(testTxt)) origenStream.end() devolver } res.end(testTxt) }] ] ruta constante = rutas.find(v => url.startsWith(v[0])) si (ruta) { ruta[1]() devolver } // Volver al principio res.setHeader('Content-Type', 'text/html; charset=utf-8') res.end(`<h1>404: ${url}</h1> <h2>Ruta registrada</h2> <ul> ${routes.map(r => `<li><a href="${r[0]}">${r[0]}</a></li>`).join('') } </ul> `) res.end() }) aplicación.escuchar(3000)