在Node.js中通过HTTP发送大型图像数据

30
在我的开发环境中,我有两个服务器。其中一个通过 POST http 请求将图像发送到另一个服务器。
客户端服务器执行以下操作:
    fs.readFile(rawFile.path,'binary',function (err, file){
        restler.post("http://0.0.0.0:5000",{
            data: file,
            headers:{
                "Content-Type": rawFile.type,
            }
        }).on('complete',function(data,response){                               
            console.log(data);
            res.send("file went through")
        })

接收请求的服务器会执行以下操作:
    server.post('/',function(req,res,next){
        fs.writeFileSync("test.png",req.body,"binary",function(err){
            if(err) throw err;
            res.send("OK")
        })
    })

如果我发送一个小图像,它可以正常工作。但是,如果我发送一个大图像,尽管文件被正确保存,但只显示图像的第一部分。其余部分是黑色的。图像大小是正确的。
我猜只有图像的第一个块被写入了文件。我尝试创建一个readStream和一个writeStream,但似乎不起作用:
req.body.pipe(fs.createWriteStream('test.png'))

我可以直接从二进制数据流中传输并将其pipe到文件中吗?根据我所见,readStream通常用于从文件而不是原始二进制数据流中进行传输。我读了几篇post,但似乎对我无效。
我在客户端服务器中使用restler模块,在另一个服务器中使用restify。谢谢!
3个回答

72

抱歉直言,这里有很多问题。

readFile 读取文件的 全部内容 后才调用回调函数,在此时你开始上传文件。

这是不好的 - 特别是当处理像图像这样的大文件时 - 因为没有真正的理由将文件读入内存。这是浪费的;在负载下,你会发现服务器将耗尽内存并崩溃。

相反,你需要获取一个,它会在从磁盘读取数据时发出数据块。你只需要将这些块传递给上传流 (pipe),然后从内存中丢弃数据。通过这种方式,你永远不会使用超过一小部分的缓冲内存。

(可读流的默认行为是以原始二进制数据处理;只有在传递一个encoding时它才处理文本。)

request 模块使这特别容易:

fs.createReadStream('test.png').pipe(request.post('http://0.0.0.0:5000/'));

在服务器上,你有一个更大的问题。永远不要使用*Sync方法。它会阻塞你的服务器做任何事情(比如响应其他请求),直到整个文件被刷新到磁盘上,这可能需要几秒钟。
所以,我们希望将传入的数据流导向文件系统流。你最初的想法是正确的;req.body.pipe(fs.createWriteStream('test.png'))之所以无法工作是因为body不是一个流。 bodybodyParser中间件生成。在restify中,该中间件的作用类似于readFile,它将整个传入的请求实体缓存在内存中。在这种情况下,我们不想要这样。禁用body parser中间件。
那么传入的数据流在哪里?它就是req对象本身。restify的Request继承了node的http.IncomingMessage,它是一个可读流。所以:
fs.createWriteStream('test.png').pipe(req);

我应该提到的是,这一切都如此简单,因为没有表单解析开销涉及。请求只需发送文件,没有任何 multipart/form-data 包装:
POST / HTTP/1.1
host: localhost:5000
content-type: application/octet-stream
Connection: keep-alive
Transfer-Encoding: chunked

<image data>...

这意味着浏览器无法将文件发布到此URL。如果需要,请查看formidable,它可以对请求实体进行流解析。

1
好的回答!感谢您让我重新回到正轨 :) 我在问题中没有解释我需要完成什么: - Maroshii
formidable 可以解析浏览器上传的文件并将其流式传输到磁盘。 - josh3736
在Node中读取传入数据流是否需要使用Multer中间件? - A. Vin
感谢答案Josh。如果我理解正确,这意味着如果我想向请求添加任何其他参数或信息,我需要切换到使用multipart/form-data并使用像multer这样的中间件来解析它? - A. Vin
必须使用吗?不,有很多丑陋的方法可以避免使用多部分上传。您可以将数据塞入查询参数或标头中,但这并不特别符合RESTful的规范。如果您按照标准方式使用多部分解析器上传文件和其他参数,则您的生活可能会变得轻松得多。 - josh3736
显示剩余7条评论

0

我尝试了上面的解决方案,如果你只是在移动上传的文件或其他类似操作,以下方法效果更好:

fs.rename(path, newPath, callback(err) {});

我上传的文件超过200MB,使用流、同步或异步方式都会遇到错误。


0
我不太了解restler。但是发布图片是一个多部分请求。
restler.post("http://0.0.0.0:5000",{
    data: restler.file(path, filename, fileSize, encoding, contentType),
    multipart: true
})

1
使用 rest.file() 方法代替 fs.readFile。我会更新我的答案。 - Jean-Philippe Leclerc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接