Завантаження файлу за допомогою запиту POST у Node.js


76

У мене виникла проблема із завантаженням файлу за допомогою запиту POST у Node.js. Для цього мені потрібно використовувати requestмодуль (відсутність зовнішніх npms). Серверу потрібно, щоб він був багаточастинним запитом ізfile полем, що містить дані файлу. Те, що здається простим, досить складно зробити в Node.js, не використовуючи жодного зовнішнього модуля.

Я спробував використати цей приклад, але безуспішно:

request.post({
  uri: url,
  method: 'POST',
  multipart: [{
    body: '<FILE_DATA>'
  }]
}, function (err, resp, body) {
  if (err) {
    console.log('Error!');
  } else {
    console.log('URL: ' + body);
  }
});

1
у вас є форма з варіантом enctype="multipart/form-data"?
monkeyinsight

2
Я не використовую жодної форми. Це запит сервера. Я надсилаю файл із браузера на сервер за допомогою сокетів, а пізніше я повинен відправити цей файл на інший сервер за допомогою запиту POST.
kasukasz Jagodziński

Відповіді:


115

Схоже, ви вже використовуєте requestмодуль .

у цьому випадку все, що вам потрібно розмістити, multipart/form-data- це скористатися його formфункцією :

var req = request.post(url, function (err, resp, body) {
  if (err) {
    console.log('Error!');
  } else {
    console.log('URL: ' + body);
  }
});
var form = req.form();
form.append('file', '<FILE_DATA>', {
  filename: 'myfile.txt',
  contentType: 'text/plain'
});

але якщо ви хочете опублікувати якийсь існуючий файл із вашої файлової системи, ви можете просто передати його як читабельний потік:

form.append('file', fs.createReadStream(filepath));

request витягне всі пов'язані метадані самостійно.

Для отримання додаткової інформації про проводку multipart/form-dataдивіться node-form-dataмодуль , який внутрішньо використовується request.


83
Коли я вивчав вузол та модуль запиту, я був збентежений, чому форму можна змінити після виклику postметоду. Пояснення поховано в документах запиту - форму " можна змінити, доки запит не буде запущений у наступному циклі подійного циклу ".
Дуг Донохо

3
Я продовжую отримувати '[Помилка: писати після закінчення]' при використанні form і form.append, хтось знає чому?
Vitor Freitas

1
@VitorFreitas вам слід зателефонувати req.form()та заповнити його усіма відповідними даними синхронно відразу після дзвінка request.post. Важливо робити це під час однієї і тієї ж галочки циклу події, інакше ваш запит може бути вже відправлений і базовий потік закритий.
Леонід Бесчасний

@LeonidBeschastny Чи можете ви заглянути в мій код? pastebin.com/E6b0cvag . Мені це здається правильним, дякую! requestService - це модуль запитів
Vitor Freitas

1
Запит засуджувалися, у вас є альтернатива?
Девід

21

Недокументована особливість formDataполя, яке requestреалізує, - це можливість передавати параметри form-dataмодулю, який він використовує:

request({
  url: 'http://example.com',
  method: 'POST',
  formData: {
    'regularField': 'someValue',
    'regularFile': someFileStream,
    'customBufferFile': {
      value: fileBufferData,
      options: {
        filename: 'myfile.bin'
      }
    }
  }
}, handleResponse);

Це корисно, якщо вам потрібно уникати дзвінків, requestObj.form()але потрібно завантажити буфер як файл. form-dataМодуль також приймає contentType(тип MIME) і knownLengthпараметри.

Ця зміна була додана в жовтні 2014 року (тому через 2 місяці після того, як було задано це питання), тому вона повинна бути безпечною для використання зараз (у 2017+). Це прирівнюється до версії v2.46.0або вище request.


4

Відповідь Леоніда Бесчасного працює, але мені також довелося перетворити ArrayBuffer на Buffer, який використовується в requestмодулі Node . Після завантаження файлу на сервер я мав його в тому ж форматі, що й з HTML5 FileAPI (я використовую Meteor). Повний код нижче - можливо, це буде корисно для інших.

function toBuffer(ab) {
  var buffer = new Buffer(ab.byteLength);
  var view = new Uint8Array(ab);
  for (var i = 0; i < buffer.length; ++i) {
    buffer[i] = view[i];
  }
  return buffer;
}

var req = request.post(url, function (err, resp, body) {
  if (err) {
    console.log('Error!');
  } else {
    console.log('URL: ' + body);
  }
});
var form = req.form();
form.append('file', toBuffer(file.data), {
  filename: file.name,
  contentType: file.type
});

4
Існує більш простий спосіб перетворити ArrayBufferв Buffer, використовуючи вбудований Buffer конструктор з масиву октетів :var buffer = new Buffer(new Uint8Array(ab));
Леонід Beschastny

2
Звідки взявся "файл" у файлах file.data, file.name та file.type у вашій останній функції? Я більше ніде не бачу цієї змінної.
michaelAdam

Я використовую Meteor і пакет спільноти для управління файлами. Однак якщо ви використовуєте чистий вузол, тоді ви можете використовувати функції файлової системи, щоб отримати всю інформацію про файл та його дані nodejs.org/api/fs.html
Łukasz Jagodziński

4

Ви також можете скористатися підтримкою "користувацьких параметрів" із бібліотеки запитів. Цей формат дозволяє створити багатодольову форму завантаження, але з комбінованим записом як для файлу, так і для додаткової інформації про форму, як-от ім’я файлу або тип вмісту. Я виявив, що деякі бібліотеки розраховують отримувати завантаження файлів у цьому форматі, зокрема такі бібліотеки, як multer.

Цей підхід офіційно задокументований у розділі форм документації запиту - https://github.com/request/request#forms

//toUpload is the name of the input file: <input type="file" name="toUpload">

let fileToUpload = req.file;

let formData = {
    toUpload: {
      value: fs.createReadStream(path.join(__dirname, '..', '..','upload', fileToUpload.filename)),
      options: {
        filename: fileToUpload.originalname,
        contentType: fileToUpload.mimeType
      }
    }
  };
let options = {
    url: url,
    method: 'POST',
    formData: formData
  }
request(options, function (err, resp, body) {
    if (err)
      cb(err);

    if (!err && resp.statusCode == 200) {
      cb(null, body);
    }
  });

5
Будь ласка, відредагуйте свою відповідь та додайте пояснення чи коментарі щодо того, як працює ваш код. Це допомогло б іншим користувачам вирішити, чи є ваша відповідь достатньо цікавою для розгляду. В іншому випадку людям доводиться аналізувати ваш код (що вимагає часу), навіть маючи неясне уявлення, чи це може бути тим, що їм потрібно. Дякую!
Фабіо каже "Поновити Моніку"

Через 5 років хтось захоче пояснень, і ви не будете поруч або не будете турбувати. Тому Фабіо попросив вас пояснити пояснення у відповіді, а не на запит.
user985366

0
 const remoteReq = request({
    method: 'POST',
    uri: 'http://host.com/api/upload',
    headers: {
      'Authorization': 'Bearer ' + req.query.token,
      'Content-Type': req.headers['content-type'] || 'multipart/form-data;'
    }
  })
  req.pipe(remoteReq);
  remoteReq.pipe(res);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.