4

we know that through request.state we can pass in some custom data to handlers from middleware's before-handle process and thus influence its behaviour, I'm currently wondering how can a handler influence the middleware's logic after it.

My specific business scenario is that I have a routed address (let's take /api for example) whose function is to calculate a dynamic result that will consume a relatively long time and return a relatively large json response. Operationally, an effective way to improve efficiency is to use some buffer (like redis) to cache its results, in this way, the calculation time can be saved everytime the cache hits.

Since my memory is limited, I want to store the gzip-compressed bytes in a buffer instead of raw json streams, this will greatly increase the amount of cache I could handle. Specifically, because of there's a large number of numbers in the response, a response content is usually about 20MB in size without gzip, while after compression it is about only 1MB. This means that with 1GB of memory, I can only cache 50 different responses without compression, while with compression I can cache 1000, which is a significant difference that can hardly be ignored.

Because of these requirements, I wanted to implement a full-featured gzip middleware, but there were several technical confusions. The first is that I want to control the middleware whether to compress or not, apparently, if the response is not hitting the cache but is generated dynamically then it should be compressed, but on the contrary, it should not be compressed again since it has already been compressed once. The second question is, even if I can control the middleware not to compress, how do I replace its results with bytes that have already been compressed when there's no need to run the compress logic?

Since I yet don't know how to implement, forgive me that I can only provide some pseudo-code to illustrate my thinking.

The following code describes a relatively complex response address that does not contain middleware:

from fastapi import FastAPI, Request
import uvicorn

app = FastAPI()

@app.post("/api")
async def root(request :Request, some_args: int):

    # Using the Fibonacci series to simulate a time-consuming 
    # computational operation
    def fib_recur(n):
    if n <= 1: return n
    return fib_recur(n-1) + fib_recur(n-2)
    
    # In addition to the time-consuming calculation, the size of 
    # the returned content is also large.
    return {"response_content":  [fib_recur(31)] * 10000000 }

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8080)

I want to achieve the following after adding middleware:

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from utils import fib_recur, search_for_if_cache_hit
import uvicorn

app = FastAPI()

@app.post("/api")
async def root(request :Request, some_args: int):
    cache_hit_flag , cache_content = search_for_if_cache_hit('/api' , some_args)
    if cache_hit_flag == True:
        # Skip Calculation
        response = SomeKindOfResponse(message_content = cache_content)
        response.store['need_gzip'] = False
    else:
        # Calculate normally
        response = JSONResponse( {"response_content":  [fib_recur(31)] * 10000000 } )
        response.store['need_gzip'] = True
    return response

@app.middleware("http")
async def gzip_middleware(request: Request, call_next):
    response = await call_next(request)
    if response.store['need_gzip'] == True:
        response = gzip_handler(response)
    else:
        pass
    return response

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8080)

Thanks!

AdamHommer
  • 657
  • 7
  • 22

0 Answers0