Модуль 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(), которая уже использовалась нами ранее. Использовать ее с сервером не удастся, потому что данные передаются в виде буферных фрагментов.
Буферизация файла в памяти может создать проблемы с масштабированием, поэтому возможно другое решение: сохранить несжатый файл, распаковать его и затем удалить временный несжатый файл. Оставляю его для самостоятельной работы.
Комментарии