The quick solution would be to replace yield from io.BytesIO(resp.read())
with the one below (see FastAPI documentation - StreamingResponse
for more details).
yield from resp
However, instead of using urllib.request
and resp.read()
(which would read the entire file contents into memory, hence the reason for taking too long to respond), I would suggest using the HTTPX
library, which, in contrast to urllib
and requests
libraries, provides async
support as well, which is more suitable in an async
environment such as FastAPI's. Also, it supports Streaming Responses (see async
Streaming Responses too), and thus, you can avoid loading the entire response body into memory at once (especially, when dealing with large files). Below are provided examples in both synchronous and asynchronous ways on how to stream a video from a given URL.
Note: Both versions below would allow multiple clients to connect to the server and get the video stream without being blocked, as a normal def
endpoint in FastAPI is run in an external threadpool that is then awaited, instead of being called directly (as it would block the server)—thus ensuring that FastAPI will still work asynchronously. Even if you defined the endpoint of the first example below with async def
instead, it would still not block the server, as StreamingResponse
will run the code (for sending the body chunks) in an external threadpool that is then awaited (have a look at this comment and the source code here), if the function for streaming the response body (i.e., iterfile()
in the examples below) is a normal generator/iterator (as in the first example) and not an async
one (as in the second example). However, if you had some other I/O or CPU blocking operations inside that endpoint, it would result in blocking the server, and hence, you should drop the async
definition on that endpooint. The second example demonstrates how to implement the video streaming in an async def
endpoint, which is useful when you have to call other async
functions inside the endpoint that you have to await
, as well as you thus save FastAPI from running the endpoint in an external threadpool. For more details on def
vs async def
, please have a look at this answer.
The below examples use iter_bytes()
and aiter_bytes()
methods, respectively, to get the response body in chunks. These functions, as described in the documentation links above and in the source code here, can handle gzip, deflate, and brotli encoded responses. One can alternatively use the iter_raw()
method to get the raw response bytes, without applying content decoding (if is not needed). This method, in contrast to iter_bytes()
, allows you to optionally define the chunk_size
for streaming the response content, e.g., iter_raw(1024 * 1024)
. However, this doesn't mean that you read the body in chunks of that size from the server (that is serving the file) directly. If you had a closer look at the source code of iter_raw()
, you would see that it just uses a ByteChunker
that stores the byte contents into memory (using BytesIO()
stream) and returns the content in fixed-size chunks, depending the chunk size you passed to the function (whereas raw_stream_bytes
, as shown in the linked source code above, contains the actual byte chunk read from the stream).
Using HTTPX
with def
endpoint
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import httpx
app = FastAPI()
@app.get('/video')
def get_video(url: str):
def iterfile():
with httpx.stream("GET", url) as r:
for chunk in r.iter_bytes():
yield chunk
return StreamingResponse(iterfile(), media_type="video/mp4")
Using HTTPX
with async def
endpoint
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import httpx
app = FastAPI()
@app.get('/video')
async def get_video(url: str):
async def iterfile():
async with httpx.AsyncClient() as client:
async with client.stream("GET", url) as r:
async for chunk in r.aiter_bytes():
yield chunk
return StreamingResponse(iterfile(), media_type="video/mp4")
You can use public videos provided here to test the above. Example:
http://127.0.0.1:8000/video?url=http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4
If you would like to return a custom Response
or FileResponse
instead—which I wouldn't really recommend in case you are dealing with large video files, as you should either read the entire contents into memory, or save the contents to a temporary file on disk that you later have to read again into memory, in order to send it back to the client—please have a look at this answer and this answer.
print
the response?) – Gondiresp.read()
gets any data at all? Does it get called? Doesurlopen
succeeed? – Gondi