FastAPI StreamingResponse not streaming with generator function

First, it wouldn’t be good practice to use a POST request for requesting data from the server. Using a GET request instead would be more suitable to your case. In addition to that, you shouldn’t be sending credentials, such as auth_key as part of the URL (i.e., using the query string), but you should rather use Headers and/or Cookies (using HTTPS). Please have a look at this answer for more details and examples on the concepts of headers and cookies, as well as the risks involved when using query parameters instead. Helpful posts around this topic can also be found here and here, as well as here, here and here.

Second, if you are executing a blocking operation (i.e., blocking I/O-bound or CPU-bound tasks) inside the StreamingResponse‘s generator function, you should define the generator function with def instead of async def, as, otherwise, the blocking operation, as well as the time.sleep() function that is used inside your generator, would blcok the event loop. As explained here, if the function for streaming the response body is a normal def generator and not an async def one, FastAPI will use iterate_in_threadpool() to run the iterator/generator in a separate thread that is then awaited. If you prefer using an async def generator, then make sure to execute any blocking operations in an external ThreadPool (or ProcessPool) and await it, as well as use await asyncio.sleep() instead of time.sleep(), if you need to add delay in the execution of an operation. Have a look at this detailed answer for more details and examples.

Third, you are using requestsiter_lines() function, which iterates over the response data, one line at a time. If, however, the response data do not include any line break character (i.e., \n), you would not see the data on client’s console getting printed as they arrive, until the entire response is received by the client and printed as a whole. In that case, you should instead use iter_content() and specify the chunk_size as desired (both cases are demonstrated in the example below).

Finally, if you would like the StreamingResponse to work in every browser (including Chrome as well)—in the sense of being able to see the data as they stream in—you should specify the media_type to a different type than text/plain (e.g., application/json or text/event-stream, see here), or disable MIME Sniffing. As explained here, browsers will start buffering text/plain responses for a certain amount (around 1445 bytes, as documented here), in order to check whether or not the content received is actually plain text. To avoid that, you can either set the media_type to text/event-stream (used for server-sent events), or keep using text/plain, but set the X-Content-Type-Options response header to nosniff, which would disable MIME Sniffing (both options are demonstrated in the example below).

Working Example

app.py

from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import time


app = FastAPI()


def fake_data_streamer():
    for i in range(10):
        yield b'some fake data\n\n'
        time.sleep(0.5)


@app.get("https://stackoverflow.com/")
async def main():
    return StreamingResponse(fake_data_streamer(), media_type="text/event-stream")
    # or, use:
    #headers = {'X-Content-Type-Options': 'nosniff'}
    #return StreamingResponse(fake_data_streamer(), headers=headers, media_type="text/plain")

test.py (using Python requests)

import requests

url = "http://localhost:8000/"

with requests.get(url, stream=True) as r:
    for chunk in r.iter_content(1024):  # or, for line in r.iter_lines():
        print(chunk)

test.py (using httpx—see this, as well as this and this for the benefits of using httpx over requests)

import httpx

url="http://127.0.0.1:8000/"

with httpx.stream('GET', url) as r:
    for chunk in r.iter_raw():  # or, for line in r.iter_lines():
        print(chunk)

Leave a Comment