Closed TGlock closed 1 year ago
Interesting question. I didn't think of documenting this, but I'll add an example to the docs site.
You can achieve that functionality this way, using the get_response_for_file
. The example below also includes a Cache-Control
response header with max-age=120
seconds.
from blacksheep import Application, Request, Response
from blacksheep.server.files.dynamic import get_response_for_file
app = Application()
@app.router.get("/{page}")
def landing(request: Request, page: str) -> Response:
# In this case, page can be any file which is a direct child of the CWD
# TODO: validate the input value because otherwise the user can download anything,
# for example also application settings file!
try:
return get_response_for_file(app.files_handler, request, page, 120)
except FileNotFoundError:
return Response(404)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, port=44555)
That function handles automatically chunked encoding, ETag
headers, HTTP 304 Not Modified (AFAIK, you need to specify a cache-control of at least 1 second for browsers to use etags and if-none-match), and even range-requests. Range requests are important if you are serving big files because you get out of the box support for resumable downloads (browsers can pause and resume downloads thanks to those), and if you are serving video or audio files, the clients can seek specific points of them.
In the example above, the request handler can return any file in the CWD, but not in any sub-folder. Be careful: you need to validate the input parameter.
If you want a function that can serve files in sub-folders, you would use a catch-all route with a star "*":
@app.router.get("/*")
def landing_any(request: Request) -> Response:
# In this case, the returned file can be anything file inside the CWD or any of
# its descendant folders!
assert request.route_values is not None # optional assertion
page = request.route_values["tail"]
try:
return get_response_for_file(app.files_handler, request, page, 120)
except FileNotFoundError:
return Response(404)
Note: the function is synchronous because it returns an instance of Response
, but the content is read asynchronously from the file system. The function returning chunked portions of the files is an asynchronous iterator.
Anyway, judging by what you wrote, I understand you want to serve HTML views. For this, I recommend using Controller
and returning dynamic views. I mean using the features described here: https://www.neoteroi.dev/blacksheep/mvc-project-template/
Need an example of file response that performs like serve_files.
@app.router.get(“/{page}”) def landing(request: Request) -> Response:
page = request.route_values[“page”]
How to Map page to a file and send back async file response with etag etc headers ?
The goal is to allow for approx 10 routes with a single parameter that return the main landing pages of the app. It’s not a SPA.
thanks in advance - probably missing the obvious
🚀 Feature Request