Сжатие / восстановление данных с использованием библиотеки Zlib

Сжатие / восстановление данных с использованием библиотеки Zlib
5 (100%) 3 votes

Модуль Zlib предоставляет функциональность сжатия/восстановления данных. В его основу заложен поток преобразования данных, что становится очевидным после знакомства с примером сжатия файла, приведенным в документации Node. Я слегка изменила этот пример для работы с большим файлом.

const zlib = require('zlib');
const fs = require('fs');

const gzip = zlib.createGzip();

const inp = fs.createReadStream('test.png');
var out = fs.createWriteStream('test.png.gz');

inp.pipe(gzip).pipe(out);

Входной поток напрямую связывается с выходным, а передаваемые между ними данные подвергаются сжатию gzip, то есть происходит преобразование данных, в данном случае файла PNG.

Zlib предоставляет поддержку сжатия zlib и deflate — более сложного и управляемого алгоритма сжатия. Если файлы, созданные с применением zlib, могут быть распакованы программой командной строки gunzip (или unzip), у файлов, созданных с применением deflate, такая возможность отсутствует. Для восстановления файла, сжатого в режиме deflate, придется использовать Node или другую функциональность.

Чтобы продемонстрировать функциональность сжатия и восстановления файлов, мы создадим две программы командной строки: compress и uncompress.

Первая программа сжимает файл с выбором алгоритма gzip или deflate в параметрах командной строки. Так как мы собираемся работать с параметрами командной строки, для них также следует включить модуль Commander:

const zlib = require('zlib');
const program = require('commander');
const fs = require('fs');

program
  .version ('0.0.1')
  .option ('-s, --source [file name]', 'Source File Name')
  .option ('-f, --file [file name]', 'Destination File name')
  .option ('-t, --type <mode>', /^(gzip|deflate)$/i)
  .parse(process.argv);

var compress;
if (program.type == 'deflate')
  compress = zlib.createDeflate();
else
  compress = zlib.createGzip();

var inp = fs.createReadStream(program.source);
var out = fs.createWriteStream(program.file);

inp.pipe(compress).pipe(out);

Такие приложения интересны и полезны (особенно в среде Windows, в которой нет встроенной реализации сжатия), но технология сжатия особенно популярна в веб-запросах. Документация Node содержит примеры функциональности Zlib с веб-запросами.

Вместо получения сжатых файлов я покажу, как отправить сжатый файл на сервер, где он затем будет восстановлен. Сейчас мы создадим клиент и сервер для сжатия большого файла PNG и отправки его с запросом HTTP. Далее сервер будет восстановить данные и сохраняет файл.



Обратите внимание: передаваемые данные читаются в массив фрагментов, который затем используется для создания нового объекта Buffer вызовом buffer.concat(). Так как мы работаем с буфером, а не с потоком, использовать функцию pipe() не удастся. Вместо этого я буду использовать вспомогательную функцию Zlib zlib.unzip, которой передается объект Buffer и функция обратного вызова. Аргументы функции обратного вызова содержат ошибку и результат. Результат также представляет собой объект Buffer, который записывается во вновь созданный поток для записи функцией write(). Чтобы программа создавала разные экземпляры файла, имя файла дополняется временной меткой.

Создание веб-сервера, который получает сжатые данные и распаковывает их в файл:

const http = require('http');
const zlib = require('zlib');
const fs = require('fs');

const server = http.createServer().listen(8080);

server.on('request', function(request,response) {
  if (request.method == 'POST') {
    var chunks = [];

    request.on('data', function(chunk) {
      chunks.push(chunk);
    });

    request.on('end', function() {
      var buf = Buffer.concat(chunks);
      zlib.unzip(buf, function(err, result) {
        if (err) {
          response.writeHead(500);
          response.end();
          return console.log('error ' + err);
        }
        var timestamp = Date.now();
        var filename = './done' + timestamp + '.png';
        fs.createWriteStream(filename).write(result);
      });
      response.writeHead(200, {'Content-Type': 'text/plain'});
      response.end('Received and undecompressed file\n');
    });
  }
});

console.log('server listening on 8080');

Ключевой момент клиентского кода в листинге — назначение правильной кодировки Content-Encoding в заголовке. Заголовок должен содержать значение ‘gzip,deflate’. Также заголовок Content-Type: ‘application/javascript’.

Клиент сжимает файл и передает его с веб-запросом:

const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
const gzip = zlib.createGzip();

const options = {
  hostname: 'localhost',
  port: 8124,
  method: 'POST',
  headers: {
    'Content-Type': 'application/javascript',
    'Content-Encoding': 'gzip,deflate'
  }
};

const req = http.request(options, function(res) {
  res.setEncoding('utf8');
  var data = '';
  res.on('data', function (chunk) {
    data+=chunk;
  });

  res.on('end', function() {
    console.log(data)
  })
});

req.on('error', function(e) {
  console.log('problem with request: ' + e.message);
});

// Потоковая передача сжатого файла серверу
var readable = fs.createReadStream('./test.png');
readable.pipe(gzip).pipe(req);

Клиент открывает файл для сжатия и передает его в поток преобразования данных, выполняющий сжатие Zlib; результат передается в веб-запрос (который представляет собой поток для записи). В коде мы работаем исключительно с потоками, что позволяет нам использовать функциональность pipe(), которая уже использовалась нами ранее. Использовать ее с сервером не удастся, потому что данные передаются в виде буферных фрагментов.

Буферизация файла в памяти может создать проблемы с масштабированием, поэтому возможно другое решение: сохранить несжатый файл, распаковать его и затем удалить временный несжатый файл. Оставляю его для самостоятельной работы.


Об авторе

Занимаюсь программированием уже более 7 лет. Часто использую JavaScript (Node.js) и Python.

Комментарии