如何从node.js上传文件

当我查询这个问题时,我发现了很多post,但是他们都提到如何从浏览器上传文件到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代码发送的数据包缺less多部分数据所需的边界。 我在wireshark数据包中看到这个消息

 The multipart dissector could not find the required boundary parameter. 

任何想法如何在node.js代码中完成这个?

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

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

 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或者一个非原始二进制的编码发送,你必须自己做所有的pipe道。 这将变得更加复杂,因为你将不得不暂停阅读stream,并等待请求中的stream失事件,以确保你没有用完所有的内存(如果它不是一个大文件不用担心这个)。 编辑:其实,不要说,你可以只在读取stream选项中设置编码。

如果没有一个Node模块,我会感到惊讶。 也许有人对这个问题有更多的了解可以帮助我们了解底层的细节,但是我认为应该在某个地方有一个模块。

jhcc的答案几乎就在那里。

不得不在我们的testing中得到这个支持,我微调了一下。

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

 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 返回pipe道stream ,所以永远不会被调用。 而是等待文件读取streamend
  • request.end把边界放在一个新的行上。

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

以下是请求的样子:

内容types:

 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-- 

请注意,在身体中随机string的开头和结尾是重要的。 这些是协议的一部分。

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

我能够做到这一点的最快方法就是使用请求包。 该代码是有据可查的,它只是工作。

(对于我的testing,我想要一个JSON结果和非严格的SSL – 还有很多其他的select…)

 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 ) );