Как загрузить файл из 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 и проанализировал его в wirehark. В пакете, отправленном из моего кода node.js, не хватает границы, необходимой для многочастных данных. Я вижу это сообщение в пакете wirehark
The multipart dissector could not find the required boundary parameter.
Любая идея, как это сделать в node.js-коде?
Ответы
Ответ 1
Multipart довольно сложно, если вы хотите, чтобы он выглядел так, как обычно клиент обрабатывает "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 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 или кодировку, отличную от raw-двоичной, вам придется делать все сами трубопроводы. Это будет сложнее, потому что вам придется приостанавливать поток чтения и ждать событий утечки в запросе, чтобы убедиться, что вы не используете всю свою память (если это не большой файл, вы, как правило, не придется беспокоиться об этом, хотя). EDIT: Собственно, неважно, что вы можете просто установить кодировку в параметрах потока чтения.
Я буду удивлен, если не будет модуля Node, который сделает это уже. Может быть, кто-то более информированный по этому вопросу может помочь с деталями низкого уровня, но я думаю, что там должен быть модуль где-то, который делает это.
Ответ 2
jhcc 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 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?
Изменения:
Ответ 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
Ответ 4
Самый быстрый способ, которым я смог это сделать, работал, использовал пакет 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 ) );