如何从 node.js 上传文件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5744990/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 14:04:05  来源:igfitidea点击:

how to upload a file from node.js

file-uploadnode.js

提问by Jayesh

I found many posts when I queried for this problem, but they all refer to how to upload a file from your browser to a node.js server. I want to upload a file from node.js code to another server. I tried to write it based on my limited knowledge of node.js, but it doesn't work.

我在查询这个问题时发现了很多帖子,但它们都是指如何将文件从浏览器上传到 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();
})

The above function is called by other code that generates data.

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

I tried to upload same data file using curl -F "file=@<filepath>" and the upload is successful. But my code fails. The server returns an application specific error which hints that the uploaded file was invalid/corrupt.

我尝试使用 curl -F "file=@<filepath>" 上传相同的数据文件,上传成功。但是我的代码失败了。服务器返回一个应用程序特定错误,提示上传的文件无效/损坏。

I collected tcpdump data and analysed it in wireshark. The packet sent from my node.js code lacks the boundary required for the multipart data. I see this message in wireshark packet

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

The multipart dissector could not find the required boundary parameter.

Any idea how to accomplish this in node.js code?

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

采纳答案by chjj

Multipart is pretty complex, if you want to make it look like how a client usually handles "multipart/form-data", you have to do a few things. You first have to select a boundary key, this is usually a random string to mark the beginning and end of the parts, (in this case it would be only one part since you want to send a single file). Each part (or the one part) will need a header (initialized by the boundary key), setting the content-type, the name of the form field and the transfer encoding. Once the part(s) are completed, you need to mark the end of each part with the boundary key.

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

I've never worked with multipart, but I think this is how it could be done. Someone please correct me if I'm wrong:

我从未与 multipart 合作过,但我认为这是可以做到的。如果我错了,请有人纠正我:

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 + '--'); 
  });

Again, I've never done this before, but I thinkthat is how it could be accomplished. Maybe someone more knowledgable could provide some more insight.

再说一次,我以前从未这样做过,但我认为这就是可以实现的方式。也许知识渊博的人可以提供更多的见解。

If you wanted to send it as base64 or an encoding other than raw binary, you would have to do all the piping yourself. It will end up being more complicated, because you're going to have to be pausing the read stream and waiting for drain events on the request to make sure you don't use up all your memory (if it's not a big file you generally wouldn't have to worry about this though). EDIT:Actually, nevermind that, you couldjust set the encoding in the read stream options.

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

I'll be surprised if there isn't a Node module that does this already. Maybe someone more informed on the subject can help with the low-level details, but I think there should be a module around somewhere that does this.

如果没有一个 Node 模块已经做到了这一点,我会感到惊讶。也许对这个主题更了解的人可以帮助处理低级细节,但我认为应该有一个模块可以做到这一点。

回答by mtkopone

jhcc's answeris almost there.

jhcc 的回答差不多了。

Having to come up with support for this in our tests, I tweaked it slightly.

为了在我们的测试中对此提供支持,我稍微调整了它。

Here's the modified version that works for us:

这是适用于我们的修改版本:

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?

Changes are:

变化是:

  • ReadableStream.pipereturns the piped-to stream, so endnever gets called on that. Instead, wait for endon the file read stream.
  • request.endputs the boundary on a new line.
  • ReadableStream.pipe返回管道到流,所以end永远不会被调用。相反,等待end文件读取流。
  • request.end将边界放在一个新行上。

回答by Oscar Kilhed

As the error message states you are missing the boundary parameter. You need to add a random string to separate each file from the rest of the files/form-data.

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

Here is how a request could look like:

以下是请求的样子:

The content type:

内容类型:

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

The body:

身体:

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

[data goes here]

------randomstring1337--

Note that the --in the beginning and end of of the random string in the body is significant. Those are part of the protocol.

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

More info here http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

更多信息在这里http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html

回答by clay

The fastest way I was able to do this, that worked, was using the requestpackage. The code was well documented and it just worked.

我能够做到这一点的最快方法是使用请求包。该代码有据可查,并且可以正常工作。

(For my testing I wanted a JSON result and non-strict SSL - there are many other options...)

(对于我的测试,我想要一个 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 ) );