使用Node.js进行POST请求上传文件

91

我在Node.js中使用POST请求上传文件时遇到了问题。我必须使用request模块来完成这个过程(不使用任何外部npm)。服务器需要它作为包含文件数据的多部分请求,其中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
3
我没有使用任何表单,而是通过服务器请求发送文件从浏览器到服务器,然后我需要使用POST请求将该文件发送到另一个服务器。 - Łukasz Jagodziński
6个回答

135

看起来你已经在使用 request 模块

在这种情况下,你所需要做的就是使用它的 form 功能 来发送 multipart/form-data 数据:

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内部使用的。


91
当我学习node和request模块时,我对于为什么可以在调用'post'方法后修改表单感到困惑。在request文档中有解释 - 表单“可以在下一个事件循环周期中的请求被触发之前修改”。 - Doug Donohoe
3
当我使用form和form.append时,不断收到“[Error:write after end]”错误提示,有人知道是为什么吗? - Vitor Freitas
1
@VitorFreitas 你应该在调用 request.post 后立即同步地调用 req.form() 并填充所有适当的数据。这很重要,因为必须在同一事件循环周期内完成,否则你的请求可能已经被发送并且底层流已关闭。 - Leonid Beschastny
1
请求(request)已被弃用,你有替代方案吗? - David
1
@David got 是一个很好的替代方案。 - Leonid Beschastny
显示剩余12条评论

22

request 实现的 formData 字段的一个未记录的特性是,它能够向其使用的 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年10月添加(因此在此问题提出后2个月),所以现在使用应该是安全的(在2017+)。 这相当于request的版本v2.46.0或更高版本。


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

6
请[编辑]您的答案,添加一些说明或评论,以便其他用户决定您的答案是否足够有趣以被考虑。否则,人们必须分析您的代码(这需要时间)甚至只是大致了解它是否符合他们的需求。谢谢! - Fabio says Reinstate Monica
五年后,有人会想要解释,而你可能不在身边或者不想费心去解释。这就是为什么 Fabio 要求你将解释放在响应中,而不是在请求中的原因。 - user985366

4

Leonid Beschastny的回答是正确的,但我还需要将ArrayBuffer转换为Node的request模块中使用的Buffer。上传文件到服务器后,我得到了与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)); - Leonid Beschastny
2
你最后一个函数中的“file.data”,“file.name”和“file.type”中的“file”是从哪里来的?我没有看到这个变量在其他地方被提到过。 - michaelAdam
我正在使用Meteor和社区包来进行文件管理。不过,如果您使用的是纯Node,则可以使用文件系统函数获取有关文件及其数据的所有信息。https://nodejs.org/api/fs.html - Łukasz Jagodziński

0
我是这样做的:
// Open file as a readable stream
const fileStream = fs.createReadStream('./my-file.ext');

const form = new FormData();
// Pass file stream directly to form
form.append('my file', fileStream, 'my-file.ext');

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接