【问题标题】:streaming (pipe-ing) from client directly to gcloud storage via Node通过 Node 从客户端直接流式传输(管道)到云存储
【发布时间】:2022-10-13 19:13:28
【问题描述】:

我尝试允许用户上传文件并直接保存在 Google Cloud Storage 中。我使用 Node.js 作为服务器。

下面的代码有效,但是......

const uploadFile = async (req, res, next) => {
    const file = bucket.file('sample/folder/file.txt');
    // Create a pass through stream from a string
    const passthroughStream = new stream.PassThrough();
    
    passthroughStream.pipe(file.createWriteStream()).on('finish', () => {
        // The file upload is complete
        console.log('write-stream ended');
        res.status(200).send({
            succes: true
        });
    });
    req.on('data', chunk => {
        passthroughStream.write(chunk);
    });
    req.on('end', () => {
        passthroughStream.end();
        console.log('request ended');
    });
};

我得到的是:

------WebKitFormBoundaryzsP9s0Bs6TksaKXo
Content-Disposition: form-data; name="teste.txt"; filename="teste.txt"
Content-Type: text/plain
... rest of the text file...
------WebKitFormBoundaryzsP9s0Bs6TksaKXo--

不确定是否重要,我创建了一个 8Mb 的 txt 文件以确保我会有更多的块。只有在结尾和开头我有这个txt。

我该如何摆脱它? 或者:如何以另一种方式做 id ?

【问题讨论】:

  • 你检查过Signed URLs吗?您可以使用它们直接从客户端上传文件。
  • 没有。我只知道使用签名网址下载。
  • 它们也可以用来上传文件。您对后端进行 API 调用,这将生成一个签名 URL,然后客户端可以使用它将文件直接上传到 GCS。
  • 可以作为一种解决方法,但我宁愿控制它。上传完成后我还需要在数据库中保存一些东西
  • 您可以将 Cloud Storage Triggers 用于云函数,该函数将在文件上传后运行以运行某些逻辑。这样用户就不必等到图像上传到服务器然后再上传到 GCS。但也许你可以尝试将base64字符串传递给服务器,然后按照this answer上传到GCS>

标签: node.js google-cloud-platform google-cloud-storage


【解决方案1】:

解决方案是使用一个库(在这种情况下很强大)并让他们处理流。

无论如何它都不起作用,所以如果您尝试这样做,请不要采用这种方法。

GCS 比 hdd 慢得多,因此在上传大文件时,它会消耗 RAM 或 hdd/buffer 中的所有可用内存。 Linux crush 或 nginx 报告没有可用空间。

  • 最优雅的解决方案是使用签名 URL,客户端直接在 gcs 中上传。但是我遇到了另一个问题,我将在另一个问题中发布它,在这里,关于 stackoverflow。
  • 目前使用的折衷方案是将文件上传到服务器,从服务器上传到gcs,然后从服务器中删除。它需要大约两倍的时间,不是很优雅,但它正在工作。

以下是流配对的完整代码:

const uploadFile2 = async (req, res, next) => {
    // Create a pass through stream from a string
    const passthroughStream = new stream.PassThrough();
    
    // will be set in hook. Will be used to log the new uploaded file.
    let fileName;
    let error = null;
    // override function to prevent disk writing and only to stream upload
    formidable.IncomingForm.prototype.onPart = (part) => {
        if (!part.filename) {
            // let formidable handle all non-file parts
            form._handlePart(part);
        }
        part.on('data', (data) => {
            passthroughStream.write(data, er => {
                if (er) {
                    console.error('Eroare cand am incercat sa salvez fisierul', fileName, er);
                    error = er;
                }
            });
        });
        part.on('end', () => {
            // weird error: "try to write after end". This delay fix it, but it's sure not something elegant.
            setTimeout(() => {
                passthroughStream.end();
                // we'll send the res when the cloud ends the writing
                console.log('Ended stream for:', fileName);
                res.status(200).send({
                    succes: true
                });
            }, 500);
            
        });
        part.on('error', (err) => {
            console.error('Something went wrong in uploading file:', err);
            res.status(500).send({
                success: false,
                message: err
            });
        });
    };
    
    const form = new formidable.IncomingForm({
        multiples     : true,
        keepExtensions: true,
        maxFileSize   : 1024 * 1024 * 1024
    });
    
    form.multiples = true;
    form.keepExtensions = true;
    form.maxFileSize = 1024 * 1024 * 1024;
    form.options.maxFileSize = 1024 * 1024 * 1024;
    
    
    form.parse(req, async (err, fields, files) => {
        if (err) {
            console.log('Error parsing the files', err);
            return res.status(500).json({
                error       : true,
                success     : false,
                message     : 'There was an error parsing the files',
                errorMessage: err
            });
        }
    });
    
    form.on('fileBegin', (_, file) => {
        // set filename but eliminate the /upload/ part, so 7 chars
        fileName = req.originalUrl.substring(8) + file.originalFilename;
        console.debug('begining upload for: ', req.originalUrl);
        const bucketFile = bucket.file(fileName);
        passthroughStream
            .pipe(bucketFile.createWriteStream())
            .on('finish', (error) => {
                // The file upload is complete
                if (!error) {
                    console.log('New file was uploaded:', fileName);
                    
                }
                else {
                    console.error('Error in processing passthroughStream');
                    res.status(500).send({
                        error  : true,
                        success: false,
                        message: error
                    });
                }
            })
            .on('error', er => {
                console.error('Error while trying to write on gcs', fileName, er);
                error = er;
            });
    });
    form.on('error', (err) => {
        console.error('Something went wrong in uploading file:', err);
        res.status(500).send({
            error  : true,
            success: false,
            message: err
        });
    });
};

【讨论】:

    猜你喜欢
    • 2019-01-18
    • 1970-01-01
    • 2014-12-06
    • 2023-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-10
    相关资源
    最近更新 更多