fastapi-cache
fastapi-cache copied to clipboard
Response headers are sent only on the second request.
I noticed that the Cache-Control
and ETag
headers are not included for the first response. It takes a second request to generate and include these headers in the response. From looking at decorator.py, it seems that the reason is that the ETag header is a hash of the retrieve cache entry. The cache entry is created on the first request, and not available yet for the first response.
However, it's possible to include the response headers already in the first response. I created a modified version of the @cache
decorator, which seems to work fine (unless I'm overlooking something). I can create a PR if you like:
def cache(
expire: int = None,
coder: Type[Coder] = None,
key_builder: Callable = None,
namespace: Optional[str] = "",
):
"""
cache all function
:param namespace:
:param expire:
:param coder:
:param key_builder:
:return:
"""
def wrapper(func):
@wraps(func)
async def inner(*args, **kwargs):
nonlocal coder
nonlocal expire
nonlocal key_builder
copy_kwargs = kwargs.copy()
request = copy_kwargs.pop("request", None)
response = copy_kwargs.pop("response", None)
if (
request and request.headers.get("Cache-Control") == "no-store"
) or not FastAPICache.get_enable():
return await func(*args, **kwargs)
coder = coder or FastAPICache.get_coder()
expire = expire or FastAPICache.get_expire()
key_builder = key_builder or FastAPICache.get_key_builder()
backend = FastAPICache.get_backend()
cache_key = key_builder(
func, namespace, request=request, response=response, args=args, kwargs=copy_kwargs
)
ttl, ret_encoded = await backend.get_with_ttl(cache_key)
if not request:
if ret_encoded is not None:
return coder.decode(ret_encoded)
ret = await func(*args, **kwargs)
await backend.set(cache_key, coder.encode(ret), expire or FastAPICache.get_expire())
return ret
if request.method != "GET":
return await func(request, *args, **kwargs)
if_none_match = request.headers.get("if-none-match")
if ret_encoded is None:
ret = await func(*args, **kwargs)
ret_encoded = coder.encode(ret)
ttl = expire or FastAPICache.get_expire()
await backend.set(cache_key, ret_encoded, ttl)
else:
ret = coder.decode(ret_encoded)
if response:
response.headers["Cache-Control"] = f"max-age={ttl}"
etag = f"W/{hash(ret_encoded)}"
if if_none_match == etag:
response.status_code = 304
return response
response.headers["ETag"] = etag
return ret
return inner
return wrapper
Looks like https://github.com/long2ice/fastapi-cache/pull/52 is intended to resolve this
Fixed in https://github.com/long2ice/fastapi-cache/pull/112?
This generates also errors when the response is not set on the first request:
AttributeError: 'NoneType' object has no attribute 'headers'
as the response can be None
Pretty sure this is no longer an issue.