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

如何缓存返回图片的Flask视图?序列化pymongo文件响应遇阻

Flask-Caching缓存MongoDB流式Response报错:无法序列化_thread.lock对象

问题原因

你通过app.mongo.send_file返回的是流式Flask Response,这类Response内部关联了MongoDB文件流的底层资源(比如游标或连接的线程锁),而Flask-Caching默认使用的pickle序列化器无法处理_thread.lock这类线程同步对象,因此缓存时抛出TypeError。

普通Response(比如直接返回字符串/bytes)没有绑定这类线程锁资源,所以能正常被缓存。

解决方案

方案1:读取文件内容到内存,生成静态Response后缓存

适合小文件场景,将流式内容转为静态bytes,避免锁对象被序列化:

from flask import make_response

@app.route("/media/<collection>/<unique_path>/<path:filename>")
@cache.cached()
def view_media_file(collection, unique_path, filename):
    document = db.find_one_or_404(collection, 'unique_path', unique_path)
    hex_id = filename.rsplit(".", 1)[0]
    if hex_id not in document.get("files", {}).keys():
        return "Error", 404
    
    # 直接从GridFS读取文件内容到内存
    fs = app.mongo.cx[db.name].fs
    gridfs_file = fs.find_one({"filename": filename})
    file_data = gridfs_file.read()
    
    # 构建静态Response
    response = make_response(file_data)
    # 复制原Response的headers(或按需自定义)
    response.headers["Content-Type"] = gridfs_file.content_type
    response.headers["Cache-Control"] = "max-age=31536000, public"
    return response

方案2:自定义缓存序列化器,只缓存Response的可序列化部分

通过自定义序列化逻辑,提取Response的状态码、headers、内容进行缓存,避开不可序列化的锁对象:

from flask import make_response, Cache

# 自定义序列化:提取Response的可序列化字段
def serialize_response(response):
    return {
        "status_code": response.status_code,
        "headers": dict(response.headers),
        "data": response.get_data()  # 读取流式内容为bytes
    }

# 自定义反序列化:从缓存数据重建Response
def deserialize_response(cached_data):
    response = make_response(cached_data["data"])
    response.status_code = cached_data["status_code"]
    response.headers.update(cached_data["headers"])
    return response

# 初始化缓存时指定自定义序列化器
cache = Cache(app, config={
    "CACHE_TYPE": "SimpleCache",  # 替换为你实际使用的缓存类型(如RedisCache)
    "CACHE_SERIALIZER": {
        "dumps": serialize_response,
        "loads": deserialize_response
    }
})

@app.route("/media/<collection>/<unique_path>/<path:filename>")
@cache.cached()
def view_media_file(collection, unique_path, filename):
    # 原逻辑保持不变
    document = db.find_one_or_404(collection, 'unique_path', unique_path)
    hex_id = filename.rsplit(".", 1)[0]
    if not hex_id in document.get("files", {}).keys():
        return "Error", 404
    response_obj = app.mongo.send_file(filename)
    return response_obj

方案3:用HTTP缓存替代服务器端缓存(推荐大文件场景)

如果是大文件,服务器端缓存会占用大量内存,更高效的方式是通过HTTP缓存头让客户端/CDN缓存,完全避开序列化问题:

@app.route("/media/<collection>/<unique_path>/<path:filename>")
def view_media_file(collection, unique_path, filename):
    document = db.find_one_or_404(collection, 'unique_path', unique_path)
    hex_id = filename.rsplit(".", 1)[0]
    if not hex_id in document.get("files", {}).keys():
        return "Error", 404
    response_obj = app.mongo.send_file(filename)
    
    # 设置强缓存:客户端直接缓存1年
    response_obj.headers["Cache-Control"] = "public, max-age=31536000, immutable"
    # 设置协商缓存:用文件哈希作为ETag,文件不变则返回304
    response_obj.set_etag(document["files"][hex_id]["file_hash"])  # 假设你的document存储了文件哈希字段
    return response_obj

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 20:05:25