使用请求模块nodejs上传没有临时文件的文件
upload a file without a temporary file using request module nodejs
我正在尝试压缩一堆文件并将其上传到远程服务器(也是由nodejs编写并使用多方进行表单处理)
但我想要实现的是在没有临时文件的情况下做到这一点。这意味着": 创建一个 zip 存档作为流并通过 request
模块直接上传流。"
所以我写了一些测试代码,首先是服务器接收上传:
var multiparty = require('multiparty');
var http = require('http');
var util = require('util');
http.createServer(function(req, res) {
if (req.url === '/upload' && req.method === 'POST') {
// parse a file upload
var form = new multiparty.Form({encoding: null});
console.log("what?");
form.parse(req, function(err, fields, files) {
if (err) {
console.error(err);
}
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received upload:\n\n');
res.end(util.inspect({fields: fields, files: files}));
});
return;
}
// show a file upload form
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="upload" multiple="multiple"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
}).listen(8080);
然后我只使用 child_process 执行 zip
命令,但我没有创建临时文件,而是将存档输出到标准输出以创建流:
/*
upload a manually-created readable stream from string output from zip command.
*/
var exec = require('child_process').exec;
var dir = '/Users/drakedan/Documents/screenshot/1457950544039/';
var zipCmd = 'zip -r9 - ' + dir;
var Stream = require('stream');
var request = require('request');
var fs = require('fs');
exec(zipCmd, {encoding: null}, function(err, stdout, stderr) {
if (err) console.error(err);
var rs = Stream.Readable({encoding: null});
rs.push(stdout);
rs.push(null);
var req = request.post('http://127.0.0.1:8080/upload', function(err, httpResponse, body) {
if (err) console.error(err);
console.log(body);
});
var form = req.form();
form.append('image', rs);
})
但是我运行上传脚本后,服务器return
{ [Error: stream ended unexpectedly] status: 400, statusCode: 400 }
可读流似乎有问题。
为了检查该流的内容中是否有任何损坏。我将脚本修改为使用临时文件。
/*
first write the stream content to a temp file, then
upload a new stream from the temp file.
*/
var exec = require('child_process').exec;
var dir = '/Users/drakedan/Documents/screenshot/1457950544039/';
var zipCmd = 'zip -r9 - ' + dir;
var Stream = require('stream');
var request = require('request');
var fs = require('fs');
exec(zipCmd, {encoding: null}, function(err, stdout, stderr) {
if (err) console.error(err);
var rs = Stream.Readable({encoding: null});
rs.push(stdout);
rs.push(null);
var ws = fs.createWriteStream('/Users/drakedan/Desktop/test.zip');
rs.pipe(ws);// create a temp file.
rs.on('end', function(){
var req = request.post('http://127.0.0.1:8080/upload', function(err, httpResponse, body) {
if (err) console.error(err);
console.log(body);
});
var form = req.form();
form.append('image', fs.createReadStream('/Users/drakedan/Desktop/test.zip')); // readstream from temp file
});
});
这一次,服务器接受了文件。
received upload:
{ fields: {}, files: { image: [ [Object] ] } }
并且可以成功解压临时文件,这意味着内容没有损坏。
所以我的问题是:
处理这种情况的正确方法是什么?
fs.createReadStream
和我手动创建的可读流有什么区别。
我有机会全程直播吗?
我做错了什么?
经过大量挖掘,我想我找到了解决问题的方法。
根据我的假设,stream ended unexpectedly
错误主要是因为服务器端不知道流实际结束的位置。说文件的大小未知。我从这个 blog 中得到了一些提示,它改变了 transfer-encoding:'chunked'
header。在我这样做之后,服务器似乎接受了表单,而不是作为上传的文件,而是作为普通字段。
这是进步!然后我重新审视 form-data and request 我终于找到了正确的方法。
有两种方法可以解决这个问题:
使用自定义表单选项构造表单数据,直接将标准输出作为内容。给它一个正确的 content-type 然后提交,然后就完成了。
var exec = require('child_process').exec;
var dir = '/Users/drakedan/Documents/screenshot/1457950544039/';
var zipCmd = 'zip -r9 - ' + dir;
var Stream = require('stream');
var request = require('request');
var fs = require('fs');
var FormData = require('form-data');
exec(zipCmd, {encoding: null}, function(err, stdout, stderr) {
if (err) console.error(err);
var form = new FormData();
form.append('image', stdout, {
filename: 'upload.zip',
contentType: 'application/zip, application/octet-stream'
});
form.submit('http://127.0.0.1:8080/upload', function(err, res) {
if (err) throw err;
console.log(res);
});
});
2.using 手动创建可读流。虽然这似乎没有必要
在这种情况下,但仍然很高兴知道。关键是定义已知长度,否则会出现stream ended unexpectedly
。
var exec = require('child_process').exec;
var dir = '/Users/drakedan/Documents/screenshot/1457950544039/';
var zipCmd = 'zip -r9 - ' + dir;
var Stream = require('stream');
var request = require('request');
var fs = require('fs');
var FormData = require('form-data');
exec(zipCmd, {encoding: null}, function(err, stdout, stderr) {
if (err) console.error(err);
var rs = Stream.Readable({encoding: null});
rs.push(stdout);
rs.push(null);
var form = new FormData();
form.append('image', rs, {
filename: 'upload.zip',
contentType: 'application/zip, application/octet-stream',
knownLength: 116089 // necessary if using custom steam
});
form.submit('http://127.0.0.1:8080/upload', function(err, res) {
if (err) throw err;
console.log('done');
});
});
通过了解发生了什么,我只需使用 request
更改代码即可完成工作。使用自定义文件选项
var formData = {
image: {
value: stdout,
options: {
filename: 'upload.zip',
contentType: 'application/zip, application/octet-stream'
}
}
}
var req = request.post({url: 'http://127.0.0.1:8080/upload', formData: formData}, function(err, httpResponse, body) {
if (err) console.error(err);
console.log(body);
});
我正在尝试压缩一堆文件并将其上传到远程服务器(也是由nodejs编写并使用多方进行表单处理)
但我想要实现的是在没有临时文件的情况下做到这一点。这意味着": 创建一个 zip 存档作为流并通过 request
模块直接上传流。"
所以我写了一些测试代码,首先是服务器接收上传:
var multiparty = require('multiparty');
var http = require('http');
var util = require('util');
http.createServer(function(req, res) {
if (req.url === '/upload' && req.method === 'POST') {
// parse a file upload
var form = new multiparty.Form({encoding: null});
console.log("what?");
form.parse(req, function(err, fields, files) {
if (err) {
console.error(err);
}
res.writeHead(200, {'content-type': 'text/plain'});
res.write('received upload:\n\n');
res.end(util.inspect({fields: fields, files: files}));
});
return;
}
// show a file upload form
res.writeHead(200, {'content-type': 'text/html'});
res.end(
'<form action="/upload" enctype="multipart/form-data" method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="upload" multiple="multiple"><br>'+
'<input type="submit" value="Upload">'+
'</form>'
);
}).listen(8080);
然后我只使用 child_process 执行 zip
命令,但我没有创建临时文件,而是将存档输出到标准输出以创建流:
/*
upload a manually-created readable stream from string output from zip command.
*/
var exec = require('child_process').exec;
var dir = '/Users/drakedan/Documents/screenshot/1457950544039/';
var zipCmd = 'zip -r9 - ' + dir;
var Stream = require('stream');
var request = require('request');
var fs = require('fs');
exec(zipCmd, {encoding: null}, function(err, stdout, stderr) {
if (err) console.error(err);
var rs = Stream.Readable({encoding: null});
rs.push(stdout);
rs.push(null);
var req = request.post('http://127.0.0.1:8080/upload', function(err, httpResponse, body) {
if (err) console.error(err);
console.log(body);
});
var form = req.form();
form.append('image', rs);
})
但是我运行上传脚本后,服务器return
{ [Error: stream ended unexpectedly] status: 400, statusCode: 400 }
可读流似乎有问题。
为了检查该流的内容中是否有任何损坏。我将脚本修改为使用临时文件。
/*
first write the stream content to a temp file, then
upload a new stream from the temp file.
*/
var exec = require('child_process').exec;
var dir = '/Users/drakedan/Documents/screenshot/1457950544039/';
var zipCmd = 'zip -r9 - ' + dir;
var Stream = require('stream');
var request = require('request');
var fs = require('fs');
exec(zipCmd, {encoding: null}, function(err, stdout, stderr) {
if (err) console.error(err);
var rs = Stream.Readable({encoding: null});
rs.push(stdout);
rs.push(null);
var ws = fs.createWriteStream('/Users/drakedan/Desktop/test.zip');
rs.pipe(ws);// create a temp file.
rs.on('end', function(){
var req = request.post('http://127.0.0.1:8080/upload', function(err, httpResponse, body) {
if (err) console.error(err);
console.log(body);
});
var form = req.form();
form.append('image', fs.createReadStream('/Users/drakedan/Desktop/test.zip')); // readstream from temp file
});
});
这一次,服务器接受了文件。
received upload:
{ fields: {}, files: { image: [ [Object] ] } }
并且可以成功解压临时文件,这意味着内容没有损坏。
所以我的问题是:
处理这种情况的正确方法是什么?
fs.createReadStream
和我手动创建的可读流有什么区别。
我有机会全程直播吗?
我做错了什么?
经过大量挖掘,我想我找到了解决问题的方法。
根据我的假设,stream ended unexpectedly
错误主要是因为服务器端不知道流实际结束的位置。说文件的大小未知。我从这个 blog 中得到了一些提示,它改变了 transfer-encoding:'chunked'
header。在我这样做之后,服务器似乎接受了表单,而不是作为上传的文件,而是作为普通字段。
这是进步!然后我重新审视 form-data and request 我终于找到了正确的方法。
有两种方法可以解决这个问题:
使用自定义表单选项构造表单数据,直接将标准输出作为内容。给它一个正确的 content-type 然后提交,然后就完成了。
var exec = require('child_process').exec; var dir = '/Users/drakedan/Documents/screenshot/1457950544039/'; var zipCmd = 'zip -r9 - ' + dir; var Stream = require('stream'); var request = require('request'); var fs = require('fs'); var FormData = require('form-data'); exec(zipCmd, {encoding: null}, function(err, stdout, stderr) { if (err) console.error(err); var form = new FormData(); form.append('image', stdout, { filename: 'upload.zip', contentType: 'application/zip, application/octet-stream' }); form.submit('http://127.0.0.1:8080/upload', function(err, res) { if (err) throw err; console.log(res); }); });
2.using 手动创建可读流。虽然这似乎没有必要
在这种情况下,但仍然很高兴知道。关键是定义已知长度,否则会出现stream ended unexpectedly
。
var exec = require('child_process').exec;
var dir = '/Users/drakedan/Documents/screenshot/1457950544039/';
var zipCmd = 'zip -r9 - ' + dir;
var Stream = require('stream');
var request = require('request');
var fs = require('fs');
var FormData = require('form-data');
exec(zipCmd, {encoding: null}, function(err, stdout, stderr) {
if (err) console.error(err);
var rs = Stream.Readable({encoding: null});
rs.push(stdout);
rs.push(null);
var form = new FormData();
form.append('image', rs, {
filename: 'upload.zip',
contentType: 'application/zip, application/octet-stream',
knownLength: 116089 // necessary if using custom steam
});
form.submit('http://127.0.0.1:8080/upload', function(err, res) {
if (err) throw err;
console.log('done');
});
});
通过了解发生了什么,我只需使用 request
更改代码即可完成工作。使用自定义文件选项
var formData = {
image: {
value: stdout,
options: {
filename: 'upload.zip',
contentType: 'application/zip, application/octet-stream'
}
}
}
var req = request.post({url: 'http://127.0.0.1:8080/upload', formData: formData}, function(err, httpResponse, body) {
if (err) console.error(err);
console.log(body);
});