You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CherryPy使用serve_file返回文件时浏览器未触发下载仅展示响应内容

问题说明

实现zip文件生成后的网页端下载功能时,接口可正常返回目标文件,但浏览器未触发文件下载动作,直接将文件内容作为响应体展示在页面中,预期效果为直接触发文件保存下载。该功能此前可正常运行,当前无法复现正常下载效果。

功能实现代码

def download(self, **kwargs):
        filename = kwargs["filename"]
        path = os.path.join(os.getcwd(), filename)
        return cherrypy.lib.static.serve_file(path, "application/x-download", "attachment", os.path.basename(path))
download.exposed = True

接口响应校验结果

通过Chrome开发者工具检查/download接口响应,Content-Type、Content-Disposition响应头符合预期,响应体可正常读取到zip文件内容,具体响应头信息如下:

HTTP/1.1 200 OK
Content-Type: application/x-download
Server: CherryPy/18.6.1
Date: Wed, 01 Jun 2022 20:14:59 GMT
Last-Modified: Wed, 01 Jun 2022 20:04:11 GMT
Content-Disposition: attachment; filename="benny.txt"
Accept-Ranges: bytes
Content-Length: 14
解决方案

响应头配置完全符合下载要求,问题根源是下载接口的请求发起方式错误:

  • 若通过fetch/axios等XHR异步请求调用下载接口,无论后端响应头配置多么标准,浏览器都不会自动触发下载。异步请求拿到的响应会直接交由JS逻辑处理,不会进入浏览器原生的文件保存流程。
  • 此前功能正常,是因为当时使用<a>标签跳转、window.open或form表单提交这类非异步方式发起请求,只有这类请求才会触发浏览器的下载响应逻辑。

两种修复路径

  1. 保留异步请求逻辑:前端拿到二进制响应后,手动构造Blob对象生成临时URL触发下载,参考代码:
// fetch请求示例
fetch('/download?filename=target.zip')
  .then(res => res.blob())
  .then(blob => {
    const tempUrl = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = tempUrl
    link.download = 'target.zip' // 可从Content-Disposition响应头解析动态文件名
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    window.URL.revokeObjectURL(tempUrl)
  })
  1. 移除额外前端逻辑:直接修改请求发起方式,放弃异步调用,用原生<a>标签指向下载地址即可:
<a href="/download?filename=target.zip" download="target.zip">下载文件</a>

额外优化点

  • 当前配置的Content-Type为application/x-download,属于非标准MIME类型,zip文件的标准MIME类型为application/zip,替换后可提升多浏览器兼容性,避免被部分安全策略拦截。
  • 排查项目全局中间件/拦截器逻辑,确认是否存在改写下载接口响应头、序列化响应体的逻辑,这类操作会破坏原始文件流格式,导致下载失效。

内容的提问来源于stack exchange,提问作者JBG

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 00:39:26