Tải lên tệp bằng yêu cầu POST trong Node.js


76

Tôi gặp sự cố khi tải tệp lên bằng cách sử dụng yêu cầu POST trong Node.js. Tôi phải sử dụng requestmô-đun để thực hiện điều đó (không có npms bên ngoài). Máy chủ cần nó là yêu cầu nhiều phần với filetrường chứa dữ liệu của tệp. Điều có vẻ dễ dàng nhưng lại khá khó để thực hiện trong Node.js mà không sử dụng bất kỳ mô-đun bên ngoài nào.

Tôi đã thử sử dụng ví dụ này nhưng không thành công:

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
bạn có biểu mẫu của bạn với tùy chọn enctype="multipart/form-data"?
Monkeyinsight

2
Tôi không sử dụng bất kỳ hình thức nào. Đó là yêu cầu máy chủ. Tôi đang gửi tệp từ trình duyệt đến máy chủ bằng ổ cắm và sau đó tôi phải gửi tệp đó đến máy chủ khác bằng cách sử dụng yêu cầu POST.
Łukasz Jagodziński

Câu trả lời:


115

Có vẻ như bạn đã sử dụng requestmô-đun .

trong trường hợp này, tất cả những gì bạn cần đăng multipart/form-datalà sử dụng formtính năng của nó :

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

nhưng nếu bạn muốn đăng một số tệp hiện có từ hệ thống tệp của mình, thì bạn có thể chỉ cần chuyển nó dưới dạng một luồng có thể đọc được:

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

request sẽ tự trích xuất tất cả siêu dữ liệu liên quan.

Để biết thêm thông tin về đăng bài, multipart/form-datahãy xem node-form-datamô-đun , được sử dụng nội bộ bởi request.


83
Khi tôi đang tìm hiểu nút và mô-đun yêu cầu, tôi đã bối rối không hiểu tại sao biểu mẫu có thể được sửa đổi sau khi postphương thức được gọi. Bị chôn vùi trong tài liệu yêu cầu là lời giải thích - biểu mẫu " có thể được sửa đổi cho đến khi yêu cầu được kích hoạt vào chu kỳ tiếp theo của vòng lặp sự kiện ".
Doug Donohoe

3
Tôi liên tục nhận được '[Lỗi: viết sau khi kết thúc] "khi sử dụng biểu mẫu và form.append, có ai biết tại sao không?
Vitor Freitas

1
@VitorFreitas bạn nên gọi req.form()và điền đồng bộ vào nó với tất cả dữ liệu thích hợp ngay sau khi gọi request.post. Điều quan trọng là phải làm điều đó trong cùng một lần đánh dấu vòng lặp sự kiện, nếu không yêu cầu của bạn có thể đã được gửi và luồng bên dưới đã bị đóng.
Leonid Beschastny 19/02/16

@LeonidBeschastny Bạn có thể xem mã của tôi không? pastebin.com/E6b0cvag . Nó có vẻ đúng với tôi, cảm ơn! requestService là module theo yêu cầu
Vitor Freitas

1
Các yêu cầu đã được tán thành, bạn có một lựa chọn?
David

21

Một tính năng không có tài liệu của formDatatrường requesttriển khai là khả năng chuyển các tùy chọn đến form-datamô-đun mà nó sử dụng:

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

Điều này hữu ích nếu bạn cần tránh gọi requestObj.form()nhưng cần tải lên bộ đệm dưới dạng tệp. Các form-datamô-đun cũng chấp nhận contentType(kiểu MIME) và knownLengthtùy chọn.

Thay đổi này đã được thêm vào tháng 10 năm 2014 (vì vậy 2 tháng sau khi câu hỏi này được hỏi), vì vậy nó sẽ an toàn để sử dụng ngay bây giờ (trong năm 2017+). Điều này tương đương với phiên bản v2.46.0hoặc cao hơn của request.


4

Câu trả lời của Leonid Beschastny hoạt động nhưng tôi cũng phải chuyển đổi ArrayBuffer thành Buffer được sử dụng trong requestmô-đun của Node . Sau khi tải tệp lên máy chủ, tôi đã có tệp ở định dạng giống như từ HTML5 FileAPI (Tôi đang sử dụng Meteor). Mã đầy đủ bên dưới - có thể nó sẽ hữu ích cho những người khác.

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
Có một cách đơn giản để chuyển đổi ArrayBufferđể Buffersử dụng xây dựng trong Buffer constructor từ một loạt các octet :var buffer = new Buffer(new Uint8Array(ab));
Leonid Beschastny

2
"Tệp" trong file.data, file.name và file.type đến từ đâu trong hàm cuối cùng của bạn? Tôi không thấy biến đó được đề cập ở bất kỳ nơi nào khác.
michaelAdam

Tôi đang sử dụng Meteor và gói cộng đồng để quản lý tệp. Tuy nhiên nếu bạn đang sử dụng nút tinh khiết sau đó bạn có thể sử dụng chức năng hệ thống tập tin để có được tất cả các thông tin về các tập tin và dữ liệu của nó nodejs.org/api/fs.html
Łukasz Jagodziński

4

Bạn cũng có thể sử dụng hỗ trợ "tùy chọn tùy chỉnh" từ thư viện yêu cầu. Định dạng này cho phép bạn tạo tải lên biểu mẫu nhiều phần, nhưng với mục nhập kết hợp cho cả tệp và thông tin biểu mẫu bổ sung, như tên tệp hoặc loại nội dung. Tôi nhận thấy rằng một số thư viện mong đợi nhận được tệp tải lên bằng định dạng này, cụ thể là các thư viện như multer.

Phương pháp này chính thức được ghi lại trong phần biểu mẫu của tài liệu yêu cầu - 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
Vui lòng chỉnh sửa câu trả lời của bạn và thêm một số giải thích hoặc nhận xét về cách mã của bạn hoạt động. Điều này sẽ giúp những người dùng khác quyết định xem câu trả lời của bạn có đủ thú vị để được xem xét hay không. Nếu không, mọi người phải phân tích mã của bạn (điều này mất thời gian) thậm chí có một ý tưởng mơ hồ liệu đây có thể là thứ họ cần hay không. Cảm ơn bạn!
Fabio nói Hãy phục hồi Monica vào

5 năm sau, ai đó sẽ muốn một lời giải thích và bạn sẽ không ở bên cạnh hoặc sẽ không bận tâm. Đó là lý do tại sao Fabio yêu cầu bạn đưa lời giải thích vào câu trả lời chứ không phải theo yêu cầu.
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);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.