如何使用axios下载文件

我使用axios的基本http请求,如GET和POST,它工作得很好。现在我需要能够下载Excel文件。这在axios中可行吗?如果是,谁有一些示例代码?如果不是,我还可以在React应用程序中使用什么来做同样的事情?

483006 次浏览

当响应带有可下载文件时,响应头将类似于

Content-Disposition: "attachment;filename=report.xls"
Content-Type: "application/octet-stream" // or Content-type: "application/vnd.ms-excel"

您可以创建一个单独的组件,该组件将包含一个隐藏的iframe。

  import * as React from 'react';


var MyIframe = React.createClass({


render: function() {
return (
<div style=\{\{display: 'none'}}>
<iframe src={this.props.iframeSrc} />
</div>
);
}
});

现在,你可以将可下载文件的url作为prop传递给这个组件,所以当这个组件接收到prop时,它将重新渲染,文件将被下载。

你也可以使用js-file-download模块。链接到Github回购

const FileDownload = require('js-file-download');


Axios({
url: 'http://localhost/downloadFile',
method: 'GET',
responseType: 'blob', // Important
}).then((response) => {
FileDownload(response.data, 'report.csv');
});

希望这对你有所帮助。

下载文件(使用Axios和Security)

当您希望使用Axios和一些安全手段下载文件时,这实际上更加复杂。为了防止其他人花太多时间来弄清楚这个问题,让我来给你介绍一下。

你需要做三件事:

  1. 配置您的服务器以允许浏览器看到所需的HTTP报头
  2. 实现服务器端服务,并使其为下载的文件发布正确的文件类型。
  3. 实现Axios处理程序以在浏览器中触发FileDownload对话框

这些步骤大部分都是可行的——但是由于浏览器与CORS的关系而变得相当复杂。一步一个脚印:

1. 配置您的HTTP服务器

当使用传输安全性时,在浏览器中执行的JavaScript(根据设计)只能访问HTTP服务器实际发送的6个HTTP头。如果我们想让服务器为下载建议一个文件名,我们必须通知浏览器它是“ok”。允许JavaScript访问其他头文件,其中建议的文件名将被传输。

为了便于讨论,我们假设服务器要在名为X-Suggested-Filename的HTTP头文件中传输建议的文件名。HTTP服务器告诉浏览器它是好吧,用下面的头向JavaScript/Axios公开这个接收到的自定义头:

Access-Control-Expose-Headers: X-Suggested-Filename

配置HTTP服务器来设置此标头的确切方式因产品而异。

有关这些标准标头的完整解释和详细描述,请参见https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

2. 实现服务器端服务

您的服务器端服务实现现在必须执行两件事:

  1. 创建(二进制)文档并将正确的ContentType分配给响应
  2. 为客户端分配包含建议文件名的自定义头文件(X-Suggested-Filename)

这取决于您选择的技术堆栈,以不同的方式完成。我将草拟一个使用JavaEE 7标准的示例,它应该发出一个Excel报告:

    @GET
@Path("/report/excel")
@Produces("application/vnd.ms-excel")
public Response getAllergyAndPreferencesReport() {


// Create the document which should be downloaded
final byte[] theDocumentData = ....


// Define a suggested filename
final String filename = ...
     

// Create the JAXRS response
// Don't forget to include the filename in 2 HTTP headers:
//
// a) The standard 'Content-Disposition' one, and
// b) The custom 'X-Suggested-Filename'
//
final Response.ResponseBuilder builder = Response.ok(
theDocumentData, "application/vnd.ms-excel")
.header("X-Suggested-Filename", fileName);
builder.header("Content-Disposition", "attachment; filename=" + fileName);


// All Done.
return builder.build();
}

该服务现在发出二进制文档(在本例中是Excel报告),设置正确的内容类型,并发送一个自定义HTTP报头,其中包含保存文档时使用的建议文件名。

3.为Received文档实现一个Axios处理程序

这里有一些陷阱,所以让我们确保所有细节都正确配置:

  1. 服务响应@GET(即HTTP GET),因此Axios调用必须是' Axios . GET(…)'。
  2. 文档以字节流的形式传输,因此必须告诉Axios将响应视为HTML5 Blob。(例如responseType:“blob”)。
  3. 在本例中,文件保护程序JavaScript库用于弹出浏览器对话框。然而,你可以选择另一个。

Axios的框架实现将如下所示:

     // Fetch the dynamically generated excel document from the server.
axios.get(resource, {responseType: 'blob'}).then((response) => {


// Log somewhat to show that the browser actually exposes the custom HTTP header
const fileNameHeader = "x-suggested-filename";
const suggestedFileName = response.headers[fileNameHeader];
const effectiveFileName = (suggestedFileName === undefined
? "allergierOchPreferenser.xls"
: suggestedFileName);
console.log(`Received header [${fileNameHeader}]: ${suggestedFileName}, effective fileName: ${effectiveFileName}`);


// Let the user save the file.
FileSaver.saveAs(response.data, effectiveFileName);


}).catch((response) => {
console.error("Could not Download the Excel report from the backend.", response);
});

我的答案是总攻击-我刚刚创建了一个看起来像按钮的链接,并将URL添加到其中。

<a class="el-button"
style="color: white; background-color: #58B7FF;"
:href="<YOUR URL ENDPOINT HERE>"
:download="<FILE NAME NERE>">
<i class="fa fa-file-excel-o"></i>&nbsp;Excel
</a>

我正在使用优秀的VueJs,因此奇怪的表示法,然而,这个解决方案是框架不可知的。这个想法适用于任何基于HTML的设计。

诀窍是在render()中创建一个不可见的锚标记,并添加一个React ref,允许在我们得到axios响应时触发单击:

class Example extends Component {
state = {
ref: React.createRef()
}


exportCSV = () => {
axios.get(
'/app/export'
).then(response => {
let blob = new Blob([response.data], {type: 'application/octet-stream'})
let ref = this.state.ref
ref.current.href = URL.createObjectURL(blob)
ref.current.download = 'data.csv'
ref.current.click()
})
}


render(){
return(
<div>
<a style=\{\{display: 'none'}} href='empty' ref={this.state.ref}>ref</a>
<button onClick={this.exportCSV}>Export CSV</button>
</div>
)
}
}

下面是文档:https://reactjs.org/docs/refs-and-the-dom.html。你可以在这里找到类似的想法:https://thewebtier.com/snippets/download-files-with-axios/

  1. 下载带有Axios的文件作为responseType: 'blob'
  2. 使用Axios/Server响应中的blob创建文件链接
  3. 创建<a> HTML元素,其中href链接到步骤2中创建的文件链接&点击链接
  4. 清理动态创建的文件链接和HTML元素
axios({
url: 'http://api.dev/file-download', //your url
method: 'GET',
responseType: 'blob', // important
}).then((response) => {
// create file link in browser's memory
const href = URL.createObjectURL(response.data);


// create "a" HTML element with href to file & click
const link = document.createElement('a');
link.href = href;
link.setAttribute('download', 'file.pdf'); //or any other extension
document.body.appendChild(link);
link.click();


// clean up "a" element & remove ObjectURL
document.body.removeChild(link);
URL.revokeObjectURL(href);
});

看看https://gist.github.com/javilobo8/097c30a233786be52070986d8cdb1743的怪癖

全部学分:https://gist.github.com/javilobo8

更多关于URL.createObjectURL的文档可以在MDN上找到。使用URL.revokeObjectURL释放对象以防止内存泄漏是至关重要的。在上面的函数中,因为我们已经下载了文件,所以可以立即撤销对象。

每次调用createObjectURL()时,都会创建一个新的对象URL,即使您已经为同一个对象创建了一个URL。当你不再需要它们时,必须调用URL.revokeObjectURL()来释放它们。

当文档被卸载时,浏览器会自动释放对象url;但是,为了优化性能和内存使用,如果存在可以显式卸载它们的安全时间,则应该这样做。

        axios.get(
'/app/export'
).then(response => {
const url = window.URL.createObjectURL(new Blob([response]));
const link = document.createElement('a');
link.href = url;
const fileName = `${+ new Date()}.csv`// whatever your file name .
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
link.remove();// you need to remove that elelment which is created before.
})

Axios。后解决方案与IE和其他浏览器

我在这里找到了一些不可思议的解决方案。但他们通常不会考虑IE浏览器的问题。也许这样可以为别人节省一些时间。

axios.post("/yourUrl",
data,
{ responseType: 'blob' }
).then(function (response) {
let fileName = response.headers["content-disposition"].split("filename=")[1];
if (window.navigator && window.navigator.msSaveOrOpenBlob) { // IE variant
window.navigator.msSaveOrOpenBlob(new Blob([response.data],
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
),
fileName
);
} else {
const url = window.URL.createObjectURL(new Blob([response.data],
{ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download',
response.headers["content-disposition"].split("filename=")[1]);
document.body.appendChild(link);
link.click();
}
}
);

上面的例子适用于excel文件,但是稍加改变就可以应用于任何格式。

在服务器上,我这样做是为了发送一个excel文件。

response.contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"


response.addHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=exceptions.xlsx")

这是为用户触发下载的非常简单的javascript代码:

window.open("<insert URL here>")

这个操作不需要axios;它应该是标准的,只是让浏览器做它的事情。

注意:如果你需要授权下载,那么这可能无法工作。我很确定你可以使用cookie来授权这样的请求,前提是它在同一个域中,但不管怎样,在这种情况下,这可能不会立即起作用。


至于是不是可能的没有内置的文件下载机制,没有

使用axios进行API调用的函数:

function getFileToDownload (apiUrl) {
return axios.get(apiUrl, {
responseType: 'arraybuffer',
headers: {
'Content-Type': 'application/json'
}
})
}

调用函数,然后下载excel文件:

getFileToDownload('putApiUrlHere')
.then (response => {
const type = response.headers['content-type']
const blob = new Blob([response.data], { type: type, encoding: 'UTF-8' })
const link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = 'file.xlsx'
link.click()
})
对于axios POST请求,请求应该是这样的: 这里的关键是responseTypeheader字段必须在Post的第3个参数中。第二个参数是应用程序参数
export const requestDownloadReport = (requestParams) => async dispatch => {
let response = null;
try {
response = await frontEndApi.post('createPdf', {
requestParams: requestParams,
},
{
responseType: 'arraybuffer', // important...because we need to convert it to a blob. If we don't specify this, response.data will be the raw data. It cannot be converted to blob directly.
headers: {
'Content-Type': 'application/json',
'Accept': 'application/pdf'
}
});
}
catch(err) {
console.log('[requestDownloadReport][ERROR]', err);
return err
}


return response;
}

这对我很管用。我在reactJS中实现了这个解决方案

const requestOptions = {`enter code here`
method: 'GET',
headers: { 'Content-Type': 'application/json' }
};


fetch(`${url}`, requestOptions)
.then((res) => {
return res.blob();
})
.then((blob) => {
const href = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = href;
link.setAttribute('download', 'config.json'); //or any other extension
document.body.appendChild(link);
link.click();
})
.catch((err) => {
return Promise.reject({ Error: 'Something Went Wrong', err });
})
为Received文档实现一个Axios处理程序,数据格式为octect-stream, 数据可能看起来很奇怪PK something JbxfFGvddvbdfbVVH34365436fdkln作为它的八字节流格式,你可能最终创建的文件与此数据可能被损坏,{responseType: 'blob'}将数据转换为可读格式,

axios.get("URL", {responseType: 'blob'})
.then((r) => {
let fileName =  r.headers['content-disposition'].split('filename=')[1];
let blob = new Blob([r.data]);
window.saveAs(blob, fileName);
}).catch(err => {
console.log(err);
});

你可能尝试过这样失败的解决方案, window.saveAs(blob, 'file.zip')将尝试将文件保存为zip,但将不会工作,

const downloadFile = (fileData) => {
axios.get(baseUrl+"/file/download/"+fileData.id)
.then((response) => {
console.log(response.data);
const blob = new Blob([response.data], {type: response.headers['content-type'], encoding:'UTF-8'});
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = 'file.zip';
link.click();
})
.catch((err) => console.log(err))
}
const downloadFile = (fileData) => {
axios.get(baseUrl+"/file/download/"+fileData.id)
.then((response) => {
console.log(response);
//const binaryString = window.atob(response.data)
//const bytes = new Uint8Array(response.data)
//const arrBuff = bytes.map((byte, i) => response.data.charCodeAt(i));
//var base64 = btoa(String.fromCharCode.apply(null, new Uint8Array(response.data)));
const blob = new Blob([response.data], {type:"application/octet-stream"});
window.saveAs(blob, 'file.zip')
// const link = document.createElement('a');
// link.href = window.URL.createObjectURL(blob);
// link.download = 'file.zip';
// link.click();
})
.catch((err) => console.log(err))
}
function base64ToArrayBuffer(base64) {
var binaryString = window.atob(base64);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
};


return bytes;
}

另一个简单的解决方法是,

window.open("URL")

将继续打开不必要的新选项卡,用户可能不得不使allow popups为工作这段代码,如果用户想同时下载多个文件,所以先去解决方案或如果不尝试其他解决方案也怎么办

你需要返回File({file_to_download}, "application/vnd.ms-excel")从你的后端到前端,在你的js文件中,你需要更新下面写的代码:

function exportToExcel() {
        

axios.post({path to call your controller}, null,
{
headers:
{
'Content-Disposition': "attachment; filename=XYZ.xlsx",
'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
},
responseType: 'arraybuffer',
}
).then((r) => {
const path= window.URL.createObjectURL(new Blob([r.data]));
const link = document.createElement('a');
link.href = path;
link.setAttribute('download', 'XYZ.xlsx');
document.body.appendChild(link);
link.click();
}).catch((error) => console.log(error));
}

这个函数将帮助您下载一个现成的xlsx, csv等文件下载。我只是从后端发送一个就绪的xlsx静态文件,它在反应。

const downloadFabricFormat = async () => {
try{
await axios({
url: '/api/fabric/fabric_excel_format/',
method: 'GET',
responseType: 'blob',
}).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'Fabric Excel Format.xlsx');
document.body.appendChild(link);
link.click();
});
} catch(error){
console.log(error)
}
};

有几个关键点大部分答案都被忽略了。

我将在这里进行更深入的解释。

TLDR;

如果您正在创建a标记链接并通过浏览器请求启动下载,则

  1. 总是调用window.URL.revokeObjectURL(url);否则就会有

  2. 不需要使用document.body.appendChild(link);将创建的链接附加到文档主体,从而避免以后不必要地删除子链接。


有关组件代码和更深入的分析,请进一步阅读

首先要弄清楚试图从中下载数据的API端点是公共的还是私有的。你能控制服务器吗?


如果服务器响应

Content-Disposition: attachment; filename=dummy.pdf
Content-Type: application/pdf

浏览器总是会尝试下载名称为'dummy.pdf'的文件


如果服务器响应

Content-Disposition: inline; filename=dummy.pdf
Content-Type: application/pdf

浏览器将首先尝试打开一个本地文件阅读器,如果名称为'dummy.pdf',否则它将开始下载文件。


如果服务器响应上述2个头中的既不

如果没有设置下载属性,浏览器(至少chrome)将尝试打开该文件。如果设置,它将下载文件。在url不是blob的情况下,文件名将是最后一个路径参数的值。


除此之外,请记住使用Transfer-Encoding: chunked from server从服务器传输大量数据。这将确保客户端知道在缺少Content-Length报头的情况下何时停止从当前请求中读取

私人档案

import { useState, useEffect } from "react";
import axios from "axios";


export default function DownloadPrivateFile(props) {
const [download, setDownload] = useState(false);


useEffect(() => {
async function downloadApi() {
try {
// It doesn't matter whether this api responds with the Content-Disposition header or not
const response = await axios.get(
"http://localhost:9000/api/v1/service/email/attachment/1mbdoc.docx",
{
responseType: "blob", // this is important!
headers: { Authorization: "sometoken" },
}
);
const url = window.URL.createObjectURL(new Blob([response.data])); // you can mention a type if you wish
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", "dummy.docx"); //this is the name with which the file will be downloaded
link.click();
// no need to append link as child to body.
setTimeout(() => window.URL.revokeObjectURL(url), 0); // this is important too, otherwise we will be unnecessarily spiking memory!
setDownload(false);
} catch (e) {} //error handling }
}


if (download) {
downloadApi();
}
}, [download]);


return <button onClick={() => setDownload(true)}>Download Private</button>;
}




公共档案

import { useState, useEffect } from "react";
export default function DownloadPublicFile(props) {
const [download, setDownload] = useState(false);


useEffect(() => {
if (download) {
const link = document.createElement("a");
link.href =
"http://localhost:9000/api/v1/service/email/attachment/dummy.pdf";
link.setAttribute("download", "dummy.pdf");
link.click();
setDownload(false);
}
}, [download]);


return <button onClick={() => setDownload(true)}>Download Public</button>;
}


很高兴知道:

  1. 始终控制从服务器下载的文件。

  2. 浏览器中的Axios在底层使用XHR,其中响应流

  3. 使用Axios中的onDownloadProgress方法来实现进度条。

  4. 来自服务器的分块响应不(不能)指示Content-Length。因此,如果您在构建进度条时使用它们,则需要某种方法来了解响应大小。

  5. <a>标签链接只能做出GET HTTP请求,没有任何能力发送报头或 到服务器的cookie(非常适合从公共端点下载)

  6. Brower请求与XHR请求在代码中略有不同。

裁判:AJAX请求和普通浏览器请求之间的区别

文件下载自定义头请求。在这个例子中,它展示了如何用承载令牌发送文件下载请求。适合授权下载内容。

    download(urlHere) {
 

axios.get(urlHere, {
headers: {
"Access-Control-Allow-Origin": "*",
Authorization: `Bearer ${sessionStorage.getItem("auth-token")}`,
}
}).then((response) => {
const temp = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = temp;
link.setAttribute('download', 'file.csv'); //or any other extension
document.body.appendChild(link);
link.click();
});
}

对于那些想要实现经过身份验证的本地下载的人。

我目前正在用Axios开发一个SPA 不幸的是,Axios在这种情况下不允许stream响应类型。 从文档:< / p >
// `responseType` indicates the type of data that the server will respond with
// options are: 'arraybuffer', 'document', 'json', 'text', 'stream'
// browser only: 'blob'

但是我想出了一个变通办法,正如前面提到的在本主题中.
窍门是发送一个基本的Form POST,其中包含您的令牌和目标文件

目标是一个新的窗口。一旦浏览器读取服务器响应上的附件标题,它将关闭新选项卡并开始下载。

下面是一个例子:

let form = document.createElement('form');
form.method = 'post';
form.target = '_blank';


form.action = `${API_URL}/${targetedResource}`;
form.innerHTML = `'<input type="hidden" name="jwtToken" value="${jwtToken}">'`;


document.body.appendChild(form);
form.submit();
document.body.removeChild(form);

“您可能需要将您的处理程序标记为未验证/匿名,以便您可以手动验证JWT以确保正确的授权。”

结果是我的ASP。NET实现:

[AllowAnonymous]
[HttpPost("{targetedResource}")]
public async Task<IActionResult> GetFile(string targetedResource, [FromForm] string jwtToken)
{
var jsonWebTokenHandler = new JsonWebTokenHandler();


var validationParameters = new TokenValidationParameters()
{
// Your token validation parameters here
};


var tokenValidationResult = jsonWebTokenHandler.ValidateToken(jwtToken, validationParameters);


if (!tokenValidationResult.IsValid)
{
return Unauthorized();
}


// Your file upload implementation here
}

我有一个问题,我从axios const axiosResponse = await axios.get(pdf.url)下载的一个文件传输到谷歌驱动器googleDrive.files.create({media: {body: axiosResponse.data, mimeType}, requestBody: {name: fileName, parents: [parentFolder], mimeType}, auth: jwtClient})上传了一个损坏的文件。

文件损坏的原因是axios将axiosResponse.data转换为字符串。为了解决这个问题,我必须要求axios返回一个流axios.get(pdf.url, { responseType: 'stream' })

使用URL.CreateObject()的答案对我来说工作得很好。 我仍然想指出使用HTTP报头的选项

使用HttpHeaders有这些优势:

  • 广泛的浏览器支持
  • 不需要在浏览器的内存中创建一个blob对象
  • 在显示给用户反馈之前,不需要等待服务器的完整响应吗
  • 无尺寸限制

使用HttpHeaders 要求您有权访问下载文件的后端服务器(这似乎是OP的Excel文件的情况)

HttpHeaders解决方案:

前端:

//...
// the download link
<a href="download/destination?parameter1=foo&param2=bar">
click me to download!
</a>

后端

(本例中是c#,但也可以是任何语言。根据需要进行调整)

...
var fs = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Read);
Response.Headers["Content-Disposition"] = "attachment; filename=someName.txt";
return File(fs, "application/octet-stream");
...

此解决方案假定您可以控制响应的后端服务器。

https://github.com/eligrey/FileSaver.js/wiki/Saving-a-remote-file#using-http-header