【问题标题】:node.js - streaming upload to cloud storage (busboy, request)node.js - 流式上传到云存储(busboy,请求)
【发布时间】:2016-10-17 00:32:45
【问题描述】:

我是 node.js 的新手。我想要做的是通过我的 node.js 服务器将文件从网络浏览器上传到云存储。

我正在使用“express”、“request”和“busboy”模块。

var express = require("express");
var request = require("request");
var BusBoy = require("busboy");
var router = express.Router();

router.post("/upload", function(req, res, next) {
    var busboy = new BusBoy({ headers: req.headers });
    var json = {};

    busboy.on("file", function (fieldname, file, filename, encoding, mimetype) {
        file.on("data", function(data) {
            console.log(`streamed ${data.length}`);
        });

        file.on("end", function() {
            console.log(`finished streaming ${filename}`);
        });
        
        var r = request({
            url: "http://<my_cloud_storage_api_url>",
            method: "POST",
            headers: {
                "CUSTOM-HEADER": "Hello",
            },
            formData: {
                "upload": file
            }
        }, function(err, httpResponse, body) {
            console.log("uploaded");
            json.response = body;
        });
    });

    busboy.on("field", function(name, val) {
        console.log(`name: ${name}, value: ${value}`);
    });
    
    busboy.on("finish", function() {
        res.send(json);
    });

    req.pipe(busboy);
});

module.exports = router;

但我在服务器上不断收到以下错误。我在这里做错了什么?任何帮助表示赞赏。

Error: Part terminated early due to unexpected end of multipart data
at node_modules\busboy\node_modules\dicer\lib\Dicer.js:65:36
at nextTickCallbackWith0Args (node.js:420:9)
at process._tickCallback (node.js:349:13)

【问题讨论】:

    标签: node.js upload request streaming busboy


    【解决方案1】:

    我知道这个问题已经有 7 个月的历史了,但我会在这里回答它,以帮助目前正在努力解决这个问题的其他人。

    你有两个选择,真的:增加文件大小,或者使用 Request 以外的东西。

    注意:我在首次发布后不久对其进行了编辑,希望能提供更多背景信息。

    使用其他东西

    如果您不需要 Request 具有的所有内置功能,您可以使用一些替代方案来代替它。

    • form-data 可以在简单的情况下单独使用,也可以与got 一起使用。 request 在内部使用它。
    • bhttp 宣传 Streams2+ 支持,尽管根据我的经验,Streams2+ 支持对我来说不是问题。没有内置https 支持,您必须指定custom agent
    • got 另一个瘦身。没有像request 那样对表单数据进行任何特殊处理,但可以与form-dataform-data2 一起使用。不过,我无法让它通过公司代理运行,但这可能是因为我是网络新手。
    • needle 看起来很轻,但我还没有真正尝试过。

    使用请求:添加文件大小

    Request (截至撰写时)不支持使用transfer-encoding: chunked,因此要使用它上传文件,您需要将文件的大小与文件一起添加,如果您从网络客户端上传意味着除了文件本身之外,客户端还需要将该文件大小发送到您的服务器。

    我想出的方法是在文件字段之前在自己的字段中发送文件元数据。

    我用 cmets 修改了您的示例,描述了我所做的事情。请注意,我没有对收到的数据进行任何验证,但我建议您添加。

    var express = require("express");
    var request = require("request");
    var BusBoy = require("busboy");
    var router = express.Router();
    
    router.post("/upload", function(req, res, next) {
        var busboy = new BusBoy({ headers: req.headers });
        var json = {};
    
        // Use this to cache any fields which are file metadata.
        var fileMetas = {};
    
        busboy.on("file", function (fieldname, file, filename, encoding, mimetype) {
            // Be sure to match this prop name here with the pattern you use to detect meta fields.
            var meta = fileMetas[fieldname + '.meta'];
    
            if (!meta) {
                // Make sure to dump the file.
                file.resume();
                // Then, do some sort of error handling here, because you cannot upload a file
                // without knowing it's length.
                return;
            }
    
            file.on("data", function(data) {
                console.log(`streamed ${data.length}`);
            });
    
            file.on("end", function() {
                console.log(`finished streaming ${filename}`);
            });
    
            var r = request({
                url: "http://<my_cloud_storage_api_url>",
                method: "POST",
                headers: {
                    "CUSTOM-HEADER": "Hello",
                },
                formData: {
                    // value + options form of a formData field.
                    "upload": {
                        value: file,
                        options: {
                            filename: meta.name,
                            knownLength: meta.size
                        }
                    }
                }
            }, function(err, httpResponse, body) {
                console.log("uploaded");
                json.response = body;
            });
        });
    
        busboy.on("field", function(name, val) {
            // Use whatever pattern you want.  I used (fileFieldName + ".meta").
            // Another good one might be ("meta:" + fileFieldName).
            if (/\.meta$/.test(name)) {
                // I send an object with { name, size, type, lastModified },
                // which are just the public props pulled off a File object.
                // Note: Should probably add error handling if val is somehow not parsable.
                fileMetas[name] = JSON.parse(val);
                console.log(`file metadata: name: ${name}, value: ${value}`);
                return;
            }
    
            // Otherwise, process field as normal.
            console.log(`name: ${name}, value: ${value}`);
        });
    
        busboy.on("finish", function() {
            res.send(json);
        });
    
        req.pipe(busboy);
    });
    
    module.exports = router;
    

    在客户端上,您需要在文件本身之前 的所谓字段上发送元数据。这可以通过在文件之前订购&lt;input type="hidden"&gt; 控件并更新其值onchange 来完成。 The order of values sent is guaranteed to follow the order of inputs in appearance。如果您自己使用 FormData 构建请求正文,则可以通过在附加 File 之前附加适当的元数据来完成此操作。

    &lt;form&gt; 为例

    <script>
        function extractFileMeta(file) {
            return JSON.stringify({
                size: file.size,
                name: file.name,
                type: file.type,
                lastUpdated: file.lastUpdated
            });
        }
    
        function onFileUploadChange(event) {
            // change this to use arrays if using the multiple attribute on the file input.
            var file = event.target.files[0];
            var fileMetaInput = document.querySelector('input[name=fileUpload.meta]');
    
            if (fileMetaInput) {
                fileMetaInput.value = extractFileMeta(file);
            }
        }
    </script>
    <form action="/upload-to-cloud">
        <input type="hidden" name="fileUpload.meta">
        <input type="file" name="fileUpload" onchange="onFileUploadChange(event)">
    </form>
    

    FormData 为例:

    function onSubmit(event) {
        event.preventDefault();
    
        var form = document.getElementById('my-upload-form');
        var formData = new FormData();
    
        var fileUpload = form.elements['fileUpload'];
        var fileUploadMeta = JSON.stringify({
            size: fileUpload.size,
            name: fileUpload.name,
            type: fileUpload.type,
            lastUpdated: fileUpload.lastUpdated
        });
    
        // Append fileUploadMeta BEFORE fileUpload.
        formData.append('fileUpload.meta', fileUploadMeta);
        formData.append('fileUpload', fileUpload);
    
        // Do whatever you do to POST here.
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-18
      • 2016-08-18
      • 2013-03-11
      • 1970-01-01
      • 2015-11-17
      • 2016-08-10
      • 1970-01-01
      相关资源
      最近更新 更多