如何从node.js上传文件

时间:2011-04-21 13:49:02

标签: file-upload node.js

我在查询此问题时发现了很多帖子,但它们都是指如何将文件从浏览器上传到node.js服务器。我想将node.js代码中的文件上传到另一台服务器。我试图根据我对node.js的有限知识来编写它,但它不起作用。

function (data) {
  var reqdata = 'file='+data;
  var request = http.request({
    host : HOST_NAME,
    port : HOST_PORT,
    path : PATH,
    method : 'POST',
    headers : {
      'Content-Type' : 'multipart/form-data',
      'Content-Length' : reqdata.length
    }
  }, function (response) {
      var data = '';
      response.on('data', function(chunk) {
        data += chunk.toString();
      });
      response.on('end', function() {
        console.log(data);
      });
    });

  request.write(reqdata+'\r\n\r\n');
  request.end();
})

上述函数由生成数据的其他代码调用。

我尝试使用curl -F“file = @< filepath>”上传相同的数据文件并且上传成功。但我的代码失败了。服务器返回特定于应用程序的错误,该错误暗示上载的文件无效/已损坏。

我收集了tcpdump数据并在wireshark中对其进行了分析。从我的node.js代码发送的数据包缺少多部分数据所需的边界。我在wireshark数据包中看到了这条消息

The multipart dissector could not find the required boundary parameter.

知道如何在node.js代码中完成此任务吗?

4 个答案:

答案 0 :(得分:13)

jhcc's answer几乎就在那里。

在我们的测试中不得不对此提出支持,我​​稍微调整了一下。

以下是适合我们的修改版本:

var boundaryKey = Math.random().toString(16); // random string
request.setHeader('Content-Type', 'multipart/form-data; boundary="'+boundaryKey+'"');
// the header for the one and only part (need to use CRLF here)
request.write( 
  '--' + boundaryKey + '\r\n'
  // use your file's mime type here, if known
  + 'Content-Type: application/octet-stream\r\n' 
  // "name" is the name of the form field
  // "filename" is the name of the original file
  + 'Content-Disposition: form-data; name="my_file"; filename="my_file.bin"\r\n'
  + 'Content-Transfer-Encoding: binary\r\n\r\n' 
);
fs.createReadStream('./my_file.bin', { bufferSize: 4 * 1024 })
  .on('end', function() {
    // mark the end of the one and only part
    request.end('\r\n--' + boundaryKey + '--'); 
  })
  // set "end" to false in the options so .end() isn't called on the request
  .pipe(request, { end: false }) // maybe write directly to the socket here?

变更是:

  • ReadableStream.pipe returns the piped-to stream,因此end永远不会被调用。而是等待文件读取流上的end
  • request.end将边界放在一个新行上。

答案 1 :(得分:7)

Multipart非常复杂,如果你想使它看起来像客户端通常处理“multipart / form-data”,你必须做一些事情。首先必须选择一个边界键,这通常是一个随机字符串来标记部件的开头和结尾(在这种情况下,它只是一部分,因为你想发送一个文件)。每个部分(或一个部分)将需要一个标题(由边界键初始化),设置内容类型,表单字段的名称和传输编码。零件完成后,需要用边界键标记每个零件的末尾。

我从未使用过多部分,但我认为这是可以做到的。如果我错了,请有人纠正我:

var boundaryKey = Math.random().toString(16); // random string
request.setHeader('Content-Type', 'multipart/form-data; boundary="'+boundaryKey+'"');
// the header for the one and only part (need to use CRLF here)
request.write( 
  '--' + boundaryKey + '\r\n'
  // use your file's mime type here, if known
  + 'Content-Type: application/octet-stream\r\n' 
  // "name" is the name of the form field
  // "filename" is the name of the original file
  + 'Content-Disposition: form-data; name="my_file"; filename="my_file.bin"\r\n'
  + 'Content-Transfer-Encoding: binary\r\n\r\n' 
);
fs.createReadStream('./my_file.bin', { bufferSize: 4 * 1024 })
  // set "end" to false in the options so .end() isnt called on the request
  .pipe(request, { end: false }) // maybe write directly to the socket here?
  .on('end', function() {
    // mark the end of the one and only part
    request.end('--' + boundaryKey + '--'); 
  });

同样,我以前从未这样做过,但我认为就是如何实现的。也许更有知识的人可以提供更多的见解。

如果您想将其作为base64或原始二进制以外的编码发送,则必须自己完成所有管道。它最终会变得更复杂,因为你将不得不暂停读取流并等待请求上的排水事件,以确保你不会耗尽你所有的内存(如果它通常不是一个大文件)不必担心这个)。 编辑:实际上,没关系,你可以只在读取流选项中设置编码。

如果没有Node模块已经执行此操作,我会感到惊讶。也许有人对这个主题有了更多的了解可以帮助解决这个问题,但我认为应该有一个模块可以解决这个问题。

答案 2 :(得分:3)

由于错误消息指出您缺少边界参数。您需要添加一个随机字符串,以将每个文件与其余文件/表单数据分开。

以下是请求的外观:

内容类型:

Content-Type:multipart/form-data; boundary=----randomstring1337

身体:

------randomstring1337
Content-Disposition: form-data; name="file"; filename="thefile.txt"
Content-Type: application/octet-stream

[data goes here]

------randomstring1337--

请注意,正文中随机字符串开头和结尾的--很重要。这些是协议的一部分。

此处有更多信息http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

答案 3 :(得分:1)

我能够做到这一点的最快方法就是使用request包。代码记录良好,只是起作用。

(对于我的测试,我想要一个JSON结果和非严格的SSL - 还有很多其他选项......)

var url = "http://"; //you get the idea
var filePath = "/Users/me/Documents/file.csv"; //absolute path created elsewhere
var r = request.post( {
  url: url,
  json: true,
  strictSSL: false
}, function( err, res, data ) {
  //console.log( "Finished uploading a file" );
  expect( err ).to.not.be.ok();
  expect( data ).to.be.ok();
  //callback(); //mine was an async test
} );
var form = r.form();
form.append( 'csv', fs.createReadStream( filePath ) );