Flask-Restful使用AJAX POST方法下载Excel报方法不允许问题
问题现象
基于Python Flask-Restful框架开发接口,搭配AJAX实现Excel文件下载时遇到两个互斥问题:
- 待传递的JSON数据体积较大,选择POST方法提交请求,接口返回Excel文件时触发
method not allowed(方法不允许)错误 - 改用GET方法发起请求,可以正常返回Excel文件,但服务端无法接收提交的JSON数据
原实现代码如下:
原服务端POST接口代码
def post(self): output = io.BytesIO() workbook = xlsxwriter.Workbook(output) worksheet = workbook.add_worksheet() filename = 'excel'+".xls" worksheet.merge_range('A2:E2', 'WORK!!') workbook.close() response = make_response(output.getvalue()) response.headers['Content-Type'] = "application/x-xlsx" response.headers["Cache-Control"] = "no-cache" response.headers["Content-Disposition"] = "attachment; filename="+filename return response
原前端AJAX请求代码
$.ajax({ url: '/downloadFile', type: 'POST', data: JSON.stringify({ data1: data1, data2: data2 }), dataType: "JSON", contentType: "application/json; charset=utf-8", traditional: true, success: function (response) { alert('ok') window.location = this.url;}})
错误根因
- 前端AJAX配置和逻辑错误
dataType: "JSON"配置强制要求jQuery按JSON格式解析服务端响应,但接口实际返回的是二进制Excel文件流,解析阶段就会出错- success回调里的
window.location = this.url会重新向接口地址发起GET请求,这个请求既没带上之前POST提交的JSON参数,又因为接口只实现了POST方法,直接触发405方法不允许错误 - 基础认知误区:AJAX请求拿到的响应存储在JS内存中,不会自动触发浏览器的文件下载行为,直接跳转页面的写法完全绕开了POST传参逻辑
- 后端实现细节不规范
- 响应头的Content-Type值
application/x-xlsx是非标准类型,标准xlsx格式MIME值应为application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - 文件名未做编码处理,遇到中文文件名会出现乱码
- 跨域场景下未暴露
Content-Disposition响应头,前端无法读取下载文件名
- 响应头的Content-Type值
修复方案
不要用页面跳转的方式实现POST请求后的文件下载,直接在AJAX拿到二进制流后,转成Blob对象手动触发浏览器下载即可,同时修正后端响应头配置。
修正后的前端代码
$.ajax({ url: '/downloadFile', type: 'POST', data: JSON.stringify({ data1: data1, data2: data2 }), // 告诉jQuery不要解析响应,直接返回二进制Blob对象 xhrFields: { responseType: 'blob' }, contentType: "application/json; charset=utf-8", success: function (response) { // 从响应头提取文件名 let disposition = this.getResponseHeader('Content-Disposition'); let filename = decodeURI(disposition.split('filename=')[1]); // 创建Blob对象,匹配Excel文件MIME类型 let blob = new Blob([response], {type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); // 创建临时下载链接触发点击 let downloadUrl = window.URL.createObjectURL(blob); let a = document.createElement('a'); a.href = downloadUrl; a.download = filename; document.body.appendChild(a); a.click(); // 释放临时URL占用的内存 window.URL.revokeObjectURL(downloadUrl); document.body.removeChild(a); } })
修正后的后端接口代码
from flask import make_response import io import xlsxwriter from urllib.parse import quote def post(self): output = io.BytesIO() workbook = xlsxwriter.Workbook(output) worksheet = workbook.add_worksheet() filename = '导出数据.xlsx' worksheet.merge_range('A2:E2', 'WORK!!') workbook.close() # 将流指针移到起始位置 output.seek(0) response = make_response(output.getvalue()) # 设置标准MIME类型 response.headers['Content-Type'] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" response.headers["Cache-Control"] = "no-cache" # 文件名做url编码,避免中文乱码 response.headers["Content-Disposition"] = f"attachment; filename={quote(filename)}" # 跨域场景下暴露响应头,供前端读取文件名 response.headers['Access-Control-Expose-Headers'] = 'Content-Disposition' return response
注意:不要用GET方式传递大体积JSON,GET参数长度受浏览器、服务端配置限制,很容易出现参数截断问题。如果使用Flask-CORS做跨域处理,需要手动在CORS配置中添加
expose_headers=['Content-Disposition']规则,否则前端依然无法读取到响应头里的文件名。
内容的提问来源于stack exchange,提问作者GRACIELA FAUSTEN NOVINDRI
相关产品推荐
相关产品推荐

