Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache doesn't work with GZIPMiddleware #210

Closed
sergey-burkov-trailstone opened this issue Mar 15, 2024 · 5 comments
Closed

Cache doesn't work with GZIPMiddleware #210

sergey-burkov-trailstone opened this issue Mar 15, 2024 · 5 comments
Labels
bug Something isn't working wait for release

Comments

@sergey-burkov-trailstone
    app.add_middleware(gzip.GZipMiddleware, minimum_size=100)
    app.add_middleware(
        cors.CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],
    )
    app.add_middleware(CacheDeleteMiddleware)
    app.add_middleware(CacheEtagMiddleware)
    app.add_middleware(CacheRequestControlMiddleware)

    cache.setup(settings_url=FAST_API_CACHE_URI)

    return app

When cache is HIT, the response is 200 OK with empty body, in FASTAPI logs RuntimeExceptions:

[ERROR] [app.main] [2024-03-14 22:34:05,956]: Exception for request=<starlette.requests.Request object at 0x7f1b281a4820>
Traceback (most recent call last):
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 264, in __call__
    await wrap(partial(self.listen_for_disconnect, receive))
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 260, in wrap
    await func()
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 237, in listen_for_disconnect
    message = await receive()
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 52, in wrapped_receive
    msg = await self.receive()
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 571, in receive
    await self.message_event.wait()
  File "/home/linuxbrew/.linuxbrew/opt/python@3.10/lib/python3.10/asyncio/locks.py", line 214, in wait
    await fut
asyncio.exceptions.CancelledError: Cancelled by cancel scope 7f1b2835ae60

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 192, in __call__
    await response(scope, wrapped_receive, send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 257, in __call__
    async with anyio.create_task_group() as task_group:
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 678, in __aexit__
    raise BaseExceptionGroup(
exceptiongroup.ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/_utils.py", line 87, in collapse_excgroups
    yield
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 190, in __call__
    async with anyio.create_task_group() as task_group:
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 678, in __aexit__
    raise BaseExceptionGroup(
exceptiongroup.ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 189, in __call__
    with collapse_excgroups():
  File "/home/linuxbrew/.linuxbrew/opt/python@3.10/lib/python3.10/contextlib.py", line 153, in __exit__
    self.gen.throw(typ, value, traceback)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/_utils.py", line 93, in collapse_excgroups
    raise exc
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 260, in wrap
    await func()
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 217, in stream_response
    return await super().stream_response(send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 252, in stream_response
    await send({"type": "http.response.body", "body": chunk, "more_body": True})
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/errors.py", line 161, in _send
    await send(message)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 544, in send
    raise RuntimeError("Response content longer than Content-Length")
RuntimeError: Response content longer than Content-Length
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 264, in __call__
    await wrap(partial(self.listen_for_disconnect, receive))
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 260, in wrap
    await func()
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 237, in listen_for_disconnect
    message = await receive()
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 52, in wrapped_receive
    msg = await self.receive()
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 571, in receive
    await self.message_event.wait()
  File "/home/linuxbrew/.linuxbrew/opt/python@3.10/lib/python3.10/asyncio/locks.py", line 214, in wait
    await fut
asyncio.exceptions.CancelledError: Cancelled by cancel scope 7f1b2835ae60

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 192, in __call__
    await response(scope, wrapped_receive, send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 257, in __call__
    async with anyio.create_task_group() as task_group:
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 678, in __aexit__
    raise BaseExceptionGroup(
exceptiongroup.ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/_utils.py", line 87, in collapse_excgroups
    yield
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 190, in __call__
    async with anyio.create_task_group() as task_group:
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 678, in __aexit__
    raise BaseExceptionGroup(
exceptiongroup.ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 412, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 69, in __call__
    return await self.app(scope, receive, send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__
    raise exc
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 189, in __call__
    with collapse_excgroups():
  File "/home/linuxbrew/.linuxbrew/opt/python@3.10/lib/python3.10/contextlib.py", line 153, in __exit__
    self.gen.throw(typ, value, traceback)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/_utils.py", line 93, in collapse_excgroups
    raise exc
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 260, in wrap
    await func()
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/base.py", line 217, in stream_response
    return await super().stream_response(send)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/responses.py", line 252, in stream_response
    await send({"type": "http.response.body", "body": chunk, "more_body": True})
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/starlette/middleware/errors.py", line 161, in _send
    await send(message)
  File "/home/sburkov/.virtualenvs/tscs-v2/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 544, in send
    raise RuntimeError("Response content longer than Content-Length")
RuntimeError: Response content longer than Content-Length

@AndreyKuchko
Copy link

I have the same problem with following(simplified) middlewares setup in fastapi application:

app.add_middleware(gzip.GZipMiddleware, minimum_size=100)
app.add_middleware(CacheRequestControlMiddleware)

cache.setup(settings_url="mem://?check_interval=10&size=10000")
$ python --version
Python 3.9.18
$ pip freeze
...
cashews==7.0.2
fastapi==0.110.0
starlette==0.36.3
uvicorn==0.28.0
...

After quick debug I see that CacheRequestControlMiddleware stores decompressed response body with headers from compressed response(it contains Content-Encoding: gzip) as a cached page to the memory. On the next request GZipMiddleware ignores compressing the response body, because according to Content-Encoding it assumes that it is already done.
If I use redis backed for the cache instead of mem, the same code works without any problems. As I see in this case it stores correct(without Content-Encoding: gzip) response headers together with decompressed response body as a cache page.

I hope this information will help with investigation.

@Krukov Krukov added the bug Something isn't working label May 5, 2024
@Krukov
Copy link
Owner

Krukov commented May 5, 2024

Sorry for a delay

I spend some time trying to reproduce it but can't
Can you help me with a code?
I have a next snippet but it works as expected

from fastapi import FastAPI, Response
from starlette.middleware import gzip

from cashews import cache
from cashews.contrib.fastapi import (
    CacheDeleteMiddleware,
    CacheEtagMiddleware,
    CacheRequestControlMiddleware,
)

app = FastAPI()

app.add_middleware(gzip.GZipMiddleware, minimum_size=100)
app.add_middleware(CacheDeleteMiddleware)
app.add_middleware(CacheEtagMiddleware)
app.add_middleware(CacheRequestControlMiddleware)

cache.setup("mem://")


@app.get("/")
@cache(ttl="5m")
async def root():
    return Response(status_code=200)


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

@sergey-burkov-trailstone
Copy link
Author

Hey, the underlying issue happens only when caching RAW FastAPI response objects. As they are getting modified later in the chain by GZipMiddleware - additional headers are added. And as the cache is only maintaining references of the objects, they would get modified by it. One approach would be to make a copy of object placed into the cache.

@Krukov
Copy link
Owner

Krukov commented May 10, 2024

Thanks for explanation. And sure that we need to store a copy of object - I thought that it so ) , I gonna fix it

@Krukov
Copy link
Owner

Krukov commented May 12, 2024

Fixed with 7.1.0 version. Please reopen the issue if it still exist

@Krukov Krukov closed this as completed May 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working wait for release
Projects
None yet
Development

No branches or pull requests

3 participants