NodeJS,Axios - 将本地服务器上的文件上传到另一个服务器

47

我有一个API端点,可以让客户端将他们的CSV文件发布到我们的服务器,然后再发布到其他人的服务器上。我已经完成了我们服务器的部分,将上传的文件保存到了我们的服务器上,但是我无法完成另一部分。我一直收到错误 { message: 'File not found', code: 400 },这可能意味着文件从未到达服务器。我正在使用axios作为代理,有人知道如何完成此操作吗?谢谢。

// file = uploaded file
const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));
const request_config = {
    method: "post",
    url: url,
    headers: {
        "Authorization": "Bearer " + access_token,
        "Content-Type": "multipart/form-data"
    },
    data: form_data
};
return axios(request_config);

更新

如axios文档所述,并且我正在尝试调用的API需要一个文件。

// data 是要作为请求主体发送的数据 // 仅适用于请求方法 'PUT'、'POST' 和 'PATCH' // 当未设置 transformRequest 时,必须是以下类型之一: // - 字符串、纯对象、ArrayBuffer、ArrayBufferView、URLSearchParams // - 仅限浏览器:FormData、File、Blob // - 仅限 Node:Stream、Buffer

有没有办法使axios将整个文件发送?谢谢。


https://www.npmjs.com/package/axios#using-multipartform-data-format - Archmede
7个回答

70

对我来说,前两个答案都不起作用。然而,这个方法有效:

const FormData = require('form-data'); // npm install --save form-data

const form = new FormData();
form.append('file', fs.createReadStream(file.path));

const request_config = {
  headers: {
    'Authorization': `Bearer ${access_token}`,
    ...form.getHeaders()
  }
};

return axios.post(url, form, request_config);

form.getHeaders() 返回一个对象,其中包含 content-type 和 boundary 信息。
例如:


例如:

{ "content-type": "multipart/form-data; boundary=-------------------0123456789" }

4
非常有帮助,非常感谢!这里还有另一个有用的例子,展示了如何在不使用文件系统的情况下模拟文件上传:https://github.com/axios/axios/issues/1006#issuecomment-320165427。 - Yuriy Rypka
1
运行得非常好。这非常有帮助。以上解决方案都没有起作用。 - kasunb
1
嗨!感谢您的回答!在TypeScript中,我遇到了一个错误:类型为“ReadStream”的参数无法分配给类型为“string | Blob”的参数。类型“ReadStream”缺少类型“Blob”的以下属性:大小、类型、arrayBuffer、slice和另外2个。有什么想法吗? - Gabriel Augusto
1
如果我有一个文件缓冲区而不是文件路径怎么办? - Marcelo Machado
@GabrielAugusto 使用 import FormData from "form-data"; - Jonathan Monsonego
显示剩余2条评论

15

我认为createReadStream是你的问题,因为它是异步的。尝试这样做。 由于createReadStream扩展了事件发射器,我们可以“监听”它何时完成/结束。

var newFile = fs.createReadStream(file.path);

// personally I'd function out the inner body here and just call 
// to the function and pass in the newFile
newFile.on('end', function() {
  const form_data = new FormData();
  form_data.append("file", newFile, "filename.ext");
  const request_config = {
    method: "post",
    url: url,
    headers: {
        "Authorization": "Bearer " + access_token,
        "Content-Type": "multipart/form-data"
    },
    data: form_data
  };
  return axios(request_config);
});

2
嗨,詹姆斯。你的解决方案确实起作用了。但是,如果createReadStream将整个文件读入内存,那么对于大型文件我可能会遇到内存问题。 - Tri Nguyen
3
使用Request模块(导入它),你可以这样轻松地实现: newFile.pipe(request(request_config)) 。我认为axios也能做到。但是,思路就是将文件流传输给request/axios进行传输。 - james emanon
如果想要上传多个文件,应该如何操作?@jamesemanon - Salim Shamim
2
嗨!感谢您的回答!在TypeScript中,我遇到了一个错误:类型为'ReadStream'的参数无法分配给类型为'string | Blob'的参数。 类型'ReadStream'缺少类型'Blob'的以下属性:size、type、arrayBuffer、slice和2个更多。有什么想法吗? - Gabriel Augusto
1
对于 TypeScript,请添加 import FormData from "form-data"; - CleverPatrick
显示剩余2条评论

10

这就是你真正需要的:

const form_data = new FormData();
form_data.append("file", fs.createReadStream(file.path));

const request_config = {
  headers: {
    "Authorization": "Bearer " + access_token,
    "Content-Type": "multipart/form-data"
  },
  data: form_data
};

return axios
  .post(url, form_data, request_config);

你的示例中似乎有一个小错误: 你漏了文件名... 正确的代码应该是 form_data.append("file", fs.createReadStream(file.path), "filename.ext"); - Didier68

8

在我的情况下, fs.createReadStream(file.path) 没有起作用。
我不得不使用缓冲区(buffer)。

const form = new FormData();
form.append('file', fs.readFileSync(filePath), fileName);

const config = {
  headers: {
    Authorization: `Bearer ${auth.access_token}`,
    ...form.getHeaders(),
  },
};

axios.post(api, form.getBuffer(), config);


1

我已经制作了一个拦截器,你可以连接到axios中来处理这种情况,适用于node环境: axios-form-data。欢迎提供任何反馈。

  • npm i axios-form-data
  • 示例:
import axiosFormData from 'axios-form-data';
import axios from 'axios';

// connect axiosFormData interceptor to axios
axios.interceptors.request.use(axiosFormData);

// send request with a file in it, it automatically becomes form-data
const response = await axios.request({
  method: 'POST',
  url: 'http://httpbin.org/post',
  data: {
    nonfile: 'Non-file value',
    // if there is at least one streamable value, the interceptor wraps the data into FormData
    file: createReadStream('somefile'),
  },
});

// response should show "files" with file content, "form" with other values
// and multipart/form-data with random boundary as request header
console.log(response.data);

1

对于任何想要从本地文件系统(实际上是任何具有正确流结构的地方)上传文件而不想使用任何外部包(如form-data)的人,可以使用axios

只需创建一个可读流并将其直接插入到axios请求函数中,如下所示:

await axios.put(
  url,
  fs.createReadStream(path_to_file)
) 

Axios在Node环境中接受类型为Stream的数据参数。

至少在我的Node v.16.13.1版本和axios v.0.27.2版本中,它可以正常工作。


0

我曾经遇到过同样的问题,我有一个“pdf-creator-service”用于从HTML生成PDF文档。

我使用mustache模板引擎创建HTML文档 - https://www.npmjs.com/package/mustache Mustache.render函数返回HTML字符串,我需要怎么做才能将其传递给pdf-generator-service?因此,让我们看看我的建议如下:

//...

async function getPdfDoc(props: {foo: string, bar: string}): Promise<Buffer> {
    const temlateFile = readFileSync(joinPath(process.cwd(), 'file.html'))

    mustache.render(temlateFile, props)
    const readableStream = this.getReadableStreamFromString(htmlString)

    const formData = new FormData() // from 'form-data'
    formData.append('file', options.file, { filename: options.fileName })
    const formHeaders = formData.getHeaders()

    return await axios.send<Buffer>(
      {
        method: 'POST',
        url: 'https://pdf-generator-service-url/pdf',
        data: formData,
        headers: {
          ...formHeaders,
        },
        responseType: 'arraybuffer', // ! important
      },
    )

}

getReadableStreamFromString(str: string): Readable {
    const bufferHtmlString = Buffer.from(str)
    const readableStream = new Readable() // from 'stream'

    readableStream._read = () => null // workaround error
    readableStream.push(bufferHtmlString)
    readableStream.push(null) // mark end of stream

    return readableStream
}

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