简体   繁体   English

NodeJS - 请求文件并压缩它

[英]NodeJS - Request file and zip it

I am currently in the process of creating a REST API for my personal website.我目前正在为我的个人网站创建一个 REST API。 I'd like to include some downloads and I would like to offer the possibility of selecting multiple ones and download those as a zip file.我想包括一些下载,并且我想提供选择多个下载并将它们作为 zip 文件下载的可能性。 My first approach was pretty easy: Array with urls, request for each of them, zip it, send to user, delete.我的第一个方法很简单:带有 url 的数组,请求每个 URL,压缩它,发送给用户,删除。 However, I think that this approach is too dirty considering there are things like streams around which seems to be quite fitting for this thing.但是,我认为这种方法太肮脏了,考虑到周围有像流这样的东西似乎非常适合这个东西。

Now, I tried around and am currently struggling with the basic concept of working with streams and events throughout different scopes.现在,我尝试了一下,目前正在努力解决在不同范围内处理流和事件的基本概念。

The following worked:以下工作:

const r = request(url, options);
r.on('response', function(res) {
    res.pipe(fs.createWriteStream('./file.jpg'));
});

From my understanding r is an incoming stream in this scenario and I listen on the response event on it, as soon as it occurs, I pipe it to a stream which I use to write to the file system.据我了解,在这种情况下,r 是一个传入流,我在其上侦听响应事件,一旦它发生,我将它通过管道传输到我用来写入文件系统的流中。

My first step was to refactor this so it fits my case more but I already failed here:我的第一步是重构它,使其更适合我的情况,但我在这里已经失败了:

async function downloadFile(url) {
    return request({ method: 'GET', uri: url });
}

Now I wanted to use a function which calls "downloadFile()" with different urls and save all those files to the disk using createWriteStream() again:现在我想使用一个函数,它使用不同的 url 调用“downloadFile()”,并再次使用 createWriteStream() 将所有这些文件保存到磁盘:

const urls = ['https://download1', 'https://download2', 'https://download3'];
urls.forEach(element => {
    downloadFile(element).then(data => {
        data.pipe(fs.createWriteStream('file.jpg'));
    });
});

Using the debugger I found out that the "response" event is non existent in the data object -- Maybe that's already the issue?使用调试器,我发现数据对象中不存在“响应”事件——也许这已经是问题所在了? Moreover, I figured that data.body contains the bytes of my downloaded document (a pdf in this case) so I wonder if I could just stream this to some other place?此外,我认为 data.body 包含我下载的文档的字节(在本例中为 pdf),所以我想知道是否可以将其流式传输到其他地方?

After reading some stackoveflow threads I found the following module: archiver在阅读了一些 stackoveflow 线程后,我发现了以下模块: archiver

Reading this thread: Dynamically create and stream zip to client阅读此线程: 动态创建和流式传输 zip 到客户端

@dankohn suggested an approach like that: @dankohn 提出了这样的方法:

archive
  .append(fs.createReadStream(file1), { name: 'file1.txt' })
  .append(fs.createReadStream(file2), { name: 'file2.txt' });

Making me assume I need to be capable of extracting a stream from my data object to proceed.让我假设我需要能够从我的数据对象中提取流才能继续。

Am I on the wrong track here or am I getting something fundamentally wrong?我是在错误的轨道上还是我从根本上搞错了?

Edit: lmao thanks for fixing my question I dunno what happened编辑: lmao 感谢您解决我的问题我不知道发生了什么

Using archiver seems to be a valid approach, however it would be advisable to use streams when feeding large data from the web into the zip archive.使用归档器似乎是一种有效的方法,但是在将来自网络的大数据输入 zip 归档时,建议使用流。 Otherwise, the whole archive data would need to be held in memory.否则,整个存档数据将需要保存在内存中。

archiver does not support adding files from streams, but zip-stream does. archiver 不支持从流中添加文件,但zip-stream支持。 For reading a stream from the web, request comes in handy.对于从 Web 读取流, request就派上用场了。

Example例子

// npm install -s express zip-stream request

const request = require('request');
const ZipStream = require('zip-stream');
const express = require('express');

const app = express();

app.get('/archive.zip', (req, res) => {
    var zip = new ZipStream()
    zip.pipe(res);

    var stream = request('https://loremflickr.com/640/480')
    zip.entry(stream, { name: 'picture.jpg' }, err => {
        if(err)
            throw err;          
    })

    zip.finalize()
});

app.listen(3000)

Update: Example for using multiple files更新:使用多个文件的示例

Adding an example which processes the next file in the callback function of zip.entry() recursively.zip.entry()的回调函数中添加一个递归处理下一个文件的示例。

app.get('/archive.zip', (req, res) => {
    var zip = new ZipStream()
    zip.pipe(res);

    var queue = [
        { name: 'one.jpg', url: 'https://loremflickr.com/640/480' },
        { name: 'two.jpg', url: 'https://loremflickr.com/640/480' },
        { name: 'three.jpg', url: 'https://loremflickr.com/640/480' }
    ]

    function addNextFile() {
        var elem = queue.shift()
        var stream = request(elem.url)
        zip.entry(stream, { name: elem.name }, err => {
            if(err)
                throw err;
            if(queue.length > 0)
                addNextFile()
            else
                zip.finalize()
        })
    }

    addNextFile()
})

Using Async/Await使用异步/等待

You can encapsulate it into a promise to use async/await like:您可以将其封装成使用 async/await 的承诺,例如:

await new Promise((resolve, reject) => {
  zip.entry(stream, { name: elem.name }, err => {
    if (err) reject(err)
    resolve()
  }) 
})
zip.finalize()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM