Streaming HTML content from local file
StreamingResponse
takes an async
or a normal generator/iterator, and streams the response body. You can create a generator function to iterate over a file-like object (e.g., the object returned by open()
), then pass it to the StreamingResponse
and return it. Make sure to specify the media_type
to "text/html"
. Also, any static files (e.g., images, JS scripts, etc) that you may have, you can serve them from a directory using StaticFiles
. In the example below, the directory="static"
refers to the name of the directory that contains your static files. The first "/static"
refers to the sub-path that StaticFiles
will be "mounted" on; hence, any path that starts with "/static"
will be handled by it. Thus, you can add static files to your .html
file like this: <script src="/static/some_script.js"></script>
. Additionally, if you find yield from f
being rather slow when using StreamingResponse
, you could instead create a custom generator, as described in this answer.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
from fastapi.staticfiles import StaticFiles
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
path = "index.html"
@app.get("/")
def main():
def iter_file():
with open(path, 'rb') as f:
yield from f
return StreamingResponse(iter_file(), media_type="text/html")
Streaming HTML content from online source (URL)
To stream HTML content from an online source, you can use the httpx
library, which provides async
support and Streaming responses too. See this answer as well for more details.
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import httpx
app = FastAPI()
url = "https://github.com/tiangolo/fastapi/issues/1788"
@app.get("/")
def main():
async def iter_url():
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(iter_url(), media_type="text/html")
Solution for streaming HTML content, static files and hyperlinks from another service
Regarding your case specifically (streaming from another service), there are a couple of options to solve the issue with static files that you mentioned in the comments section.
Option 1: If both servers run on the same machine/network, and your public server has access to the folder containing the static files of the private service, then you could define a StaticFiles
instance (as described earlier) with the directory
pointing to that specific path on the disk (e.g., ...StaticFiles(directory="path/to/service/static/folder")
), or create a symbolic link (i.e., a file pointing to a directory) and mount a StaticFiles
instance on that file, as described in this answer. Make sure to have your static files in the HTML file prefixed with that specific path that you gave when mounting the StaticFiles
instance. If, for example, the path was /static-files
, then in your HTML content you should use, for instance: <script src="/static-files/some_script.js"></script>
Option 2: If the public server runs on a separate machine/network from the private service, then you could define an endpoint in your public server that can capture arbitrary paths, using Starlette's path
convertor—as described in this answer—and serve the files in a similar way to serving the HTML content. To avoid any conflicts with other endpoints or a mounted static directory that your public server may have to serve its own static files, better have the files in the HTML content prefixed with a specific path (as shown above), which will be used for your endpoint (e.g., "/static-files/{_:path}"
).
Working Example:
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse
import httpx
from urllib.parse import urljoin
app = FastAPI()
host = "http://127.0.0.1:9001"
stream_url = host + "/stream"
@app.get("/stream")
def main():
async def iter_url():
async with httpx.AsyncClient() as client:
async with client.stream("GET", stream_url) as r:
async for chunk in r.aiter_bytes():
yield chunk
return StreamingResponse(iter_url(), media_type="text/html")
@app.get("/static-files/{_:path}")
def get_resource(request: Request):
async def iter_url(url):
async with httpx.AsyncClient() as client:
async with client.stream("GET", url) as r:
async for chunk in r.aiter_bytes():
yield chunk
url = urljoin(host, request.url.path)
return StreamingResponse(iter_url(url))
As for the hyperlinks (e.g., /test
), you can serve them in the exact same way as the static files above, as long as you can have them prefixed with some unique path, so that they won't confict with the public server's routes. If the private service is also a FastAPI application, you can use a Sub Application, or an APIRouter
, which would allow you to define a path prefix, e.g., /subapi
—a hyperlink in your HTML content would then look like this: <a href="/subapi/test">Click here</a>
. Hence, your endpoint would now be able to handle both static files and hyperlinks, as long as you define both paths in your path operation function, for example:
@app.get("/subapi/{_:path}")
@app.get("/static-files/{_:path}")
def get_resource(request: Request):
...
The working example above could be simplified by declaring the iter_url()
function outside the endpoints once, and having a single endpoint to handle every request regarding the streaming. Example:
#...
@app.get("/stream")
@app.get("/subapi/{_:path}")
@app.get("/static-files/{_:path}")
def get_resource(request: Request):
url = urljoin(host, request.url.path)
return StreamingResponse(iter_url(url))
MIME Type
Regarding the media_type
(also known as MIME type), you can either leave it out when instantiating the StreamingResponse
and let the browser determine what the mime type is, or, in the case of static files, you could use Python's built-in mimetypes
module to guess the MIME associated with the filename extension. For example:
import mimetypes
...
mt = mimetypes.guess_type(url) # url here includes a filename at the end
if mt[0]:
return StreamingResponse(iter_url(url), media_type=mt[0])
Alternatively, for both static files and hypelinks, you can use the python-magic library which identifies file types from content as well, for example:
result = magic.from_buffer(chunk, mime=True)
However, every test performed so far with StreamingResponse(iter_url(url))
has been successful, using neither mimetypes
nor magic
; meaning that browsers (specifically, tests were performed using FireFox, Chrome and Opera) were able to identify the MIME type and display the content correctly for HTML content, images, etc. So, you can either let the browser figure it out from context that the content delivers, or—since identifying the correct MIME type is important—use any of the two options described above. You could also create your own custom dictionary of file extensions and hyperlinks with their associated media_type
, and check each requested path against it to determine the correct MIME type.
http://0.0.0.0:9000/test/
doesn't exist andhttp://0.0.0.0:9001/test/
does exist. Once i streamhttp://0.0.0.0:9001/
overhttp://0.0.0.0:9000/stream/
, the hyperlink redirects me tohttp://0.0.0.0:9000/test/
, which is invalid. – Cory