2011-04-21 120 views
17

當我查詢這個問題時,我發現很多帖子,但他們都提到如何將文件從瀏覽器上傳到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代碼中完成此操作?

回答

7

多部分是相當複雜的,如果你想使它看起來像如何在客戶端通常處理「的multipart/form-data的」,你必須做一些事情。你首先必須選擇一個邊界鍵,這通常是一個隨機的字符串來標記這些部分的開始和結束,(在這種情況下,它只是一個部分,因爲你想發送一個文件)。每個部分(或一個部分)將需要一個標題(由邊界鍵初始化),設置內容類型,表單字段的名稱和傳輸編碼。一旦零件完成,您需要用邊界鍵標記每個零件的末端。

我從來沒有使用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 + '--'); 
    }); 

再次,我從來沒有這樣做過,但我認爲是如何可以實現。也許更有知識的人可以提供更多的見解。

如果您想將其作爲base64或除原始二進制文件之外的編碼發送,則必須自己完成所有管道。它最終會變得更加複雜,因爲你將不得不暫停讀取流並等待請求中的流失事件,以確保不會耗盡所有內存(如果它通常不是一個大文件不用擔心這個問題)。編輯:其實,沒關係的是,你可能只是在讀取流選項中設置編碼。

如果沒有一個Node模塊已經這樣做,我會感到驚訝。也許有人對這個主題有更多的瞭解可以幫助我們瞭解底層的細節,但我認爲應該有一個模塊圍繞某個地方來做。

+0

我一直在嘗試類似的事情。今天晚些時候我會試一試你的建議。我發現這個節點模塊應該讓這個任務變得簡單。 https://github.com/mikeal/request但它還沒有很好地爲我的目的工作。 – Jayesh 2011-04-22 02:20:57

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

+0

你真棒。 – 2017-07-06 17:38:37

12

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.pipereturns the piped-to stream,所以end不會被調用上。相反,在文件讀取流上等待end
  • request.end將邊界放在一條新線上。
+4

這對我很有用,但我也不得不添加一個Content-Length頭,它需要在文件上調用fs.stat來獲取它的大小並將其添加到所有其他發送的字符串數據的Buffer.bytesLength中。就像從邊界鍵開始一直到最後的' - '(包括文件大小)一樣獲得整個內容長度。 – 2012-10-13 20:56:21

+0

我想在此發送多個部分如何實現?我嘗試通過在每個部分之後添加邊界分隔符來添加它,但似乎不適用於我 – 2014-08-23 10:28:06

+0

@BjornTipling是否包含「Content-Disposition」標頭和分隔符?我希望它是整個身體的長度,包括那些。 – 2015-05-12 02:27:32

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