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

寻求支持ETag缓存的异步HTTP请求解决方案

异步HTTP请求的ETag缓存方案:现有选项与小空白

你提到的这个问题确实戳中了异步HTTP生态里一个不算大但挺实用的痛点——目前确实没有像CacheControl那样对所有主流异步客户端开箱即用的通用ETag缓存工具,但并非完全没有解决方案,现有工具可以通过扩展或专用库来满足需求:

  • 基于httpx的自定义扩展
    httpx本身没有内置ETag缓存,但它的异步客户端提供了灵活的event_hooks和自定义传输层接口,你可以轻松实现ETag缓存逻辑。比如用一个内存字典来存储URL对应的ETag值,在请求发送前注入If-None-Match头,收到304响应时直接返回缓存的内容:

    import httpx
    from typing import Dict, Optional
    
    etag_cache: Dict[str, str] = {}
    response_cache: Dict[str, httpx.Response] = {}
    
    async def add_etag_header(request: httpx.Request) -> None:
        url = str(request.url)
        if url in etag_cache:
            request.headers["If-None-Match"] = etag_cache[url]
    
    async def handle_etag_response(response: httpx.Response) -> httpx.Response:
        url = str(response.url)
        if response.status_code == 304 and url in response_cache:
            return response_cache[url]
        elif "ETag" in response.headers:
            etag_cache[url] = response.headers["ETag"]
            response_cache[url] = response
        return response
    
    async def main():
        async with httpx.AsyncClient(event_hooks={"request": [add_etag_header], "response": [handle_etag_response]}) as client:
            # 第一次请求会缓存ETag和响应
            resp1 = await client.get("https://example.com/api/data")
            # 第二次请求会带上If-None-Match,收到304时返回缓存
            resp2 = await client.get("https://example.com/api/data")
    
  • aiohttp专用缓存库:aiohttp-cache
    如果你愿意切换到aiohttp作为异步客户端,aiohttp-cache是一个成熟的选择,它原生支持基于ETag的缓存策略,包括自动处理If-None-Match头和304响应的缓存复用,还支持多种存储后端(内存、Redis、文件等)。只需要简单配置就能启用:

    from aiohttp import ClientSession
    from aiohttp_cache import CacheBackend, setup_cache
    
    async def main():
        async with ClientSession() as session:
            # 配置缓存后端,默认内存存储,启用ETag验证
            await setup_cache(session, CacheBackend(), allowed_caches=["etag"])
            resp1 = await session.get("https://example.com/api/data")
            resp2 = await session.get("https://example.com/api/data")  # 自动复用缓存
    
  • asks的手动缓存实现
    trio的asks虽然没有内置缓存,但同样可以结合cachetools这类通用缓存库手动实现ETag逻辑。比如用TTLCache来存储ETag和响应内容,请求前检查缓存并添加对应头,响应后更新缓存:

    import asks
    from cachetools import TTLCache
    
    asks.init('trio')
    etag_cache = TTLCache(maxsize=100, ttl=3600)
    response_cache = TTLCache(maxsize=100, ttl=3600)
    
    async def get_with_etag(url):
        headers = {}
        if url in etag_cache:
            headers["If-None-Match"] = etag_cache[url]
        resp = await asks.get(url, headers=headers)
        if resp.status_code == 304 and url in response_cache:
            return response_cache[url]
        elif "ETag" in resp.headers:
            etag_cache[url] = resp.headers["ETag"]
            response_cache[url] = resp
        return resp
    

至于你关心的「是否存在空白」——目前确实没有像CacheControl那样,能无缝适配httpx、asks、aiohttp等多个异步客户端的通用缓存中间件,这算是异步HTTP生态里一个尚未被完全填补的小缺口。不过上面这些方案已经能很好地满足大多数场景的需求,如果你需要跨客户端的通用方案,甚至可以基于这些思路自己封装一个轻量级的工具库。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 12:03:11