From d20f4ad9353c7ddaaefd01e4f0b0d1fe563512cf Mon Sep 17 00:00:00 2001 From: Michael Manganiello Date: Mon, 12 Aug 2024 20:01:00 -0300 Subject: [PATCH] feat: Use X-Accel-Redirect to improve file download speed Instead of making FastAPI handle file download, which has serious performance issues on big files [1], this change uses nginx's `X-Accel` feature to delegate single-file downloads to nginx. Partial fix for #1079, as it solves the CPU usage issue for single-file downloads. [1] https://github.com/fastapi/fastapi/discussions/6050 --- backend/config/__init__.py | 3 ++- backend/endpoints/rom.py | 22 +++++++++++++++++----- docker/nginx/default.conf | 6 ++++++ 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/backend/config/__init__.py b/backend/config/__init__.py index af698d32e..0293bdcba 100644 --- a/backend/config/__init__.py +++ b/backend/config/__init__.py @@ -13,7 +13,8 @@ GUNICORN_WORKERS: Final = int(os.environ.get("GUNICORN_WORKERS", 2)) # PATHS ROMM_BASE_PATH: Final = os.environ.get("ROMM_BASE_PATH", "/romm") -LIBRARY_BASE_PATH: Final = f"{ROMM_BASE_PATH}/library" +LIBRARY_PATH_SUFFIX: Final = "/library" +LIBRARY_BASE_PATH: Final = f"{ROMM_BASE_PATH}{LIBRARY_PATH_SUFFIX}" RESOURCES_BASE_PATH: Final = f"{ROMM_BASE_PATH}/resources" ASSETS_BASE_PATH: Final = f"{ROMM_BASE_PATH}/assets" FRONTEND_RESOURCES_PATH: Final = "/assets/romm/resources" diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py index e69deab9e..d4868d8a0 100644 --- a/backend/endpoints/rom.py +++ b/backend/endpoints/rom.py @@ -9,6 +9,7 @@ from anyio import Path, open_file from config import ( DISABLE_DOWNLOAD_ENDPOINT_AUTH, LIBRARY_BASE_PATH, + LIBRARY_PATH_SUFFIX, RESOURCES_BASE_PATH, ) from decorators.auth import protected_route @@ -22,7 +23,7 @@ from endpoints.responses.rom import ( ) from exceptions.endpoint_exceptions import RomNotFoundInDatabaseException from exceptions.fs_exceptions import RomAlreadyExistsException -from fastapi import File, HTTPException, Query, Request, UploadFile, status +from fastapi import File, HTTPException, Query, Request, Response, UploadFile, status from fastapi.responses import FileResponse from handler.database import db_platform_handler, db_rom_handler from handler.filesystem import fs_resource_handler, fs_rom_handler @@ -210,17 +211,28 @@ async def get_rom_content( if not rom: raise RomNotFoundInDatabaseException(id) - rom_path = f"{LIBRARY_BASE_PATH}/{rom.full_path}" files_to_download = files or [r["filename"] for r in rom.files] if not rom.multi: - return FileResponse(path=rom_path, filename=rom.file_name) + return Response( + headers={ + "Content-Disposition": f'attachment; filename="{quote(rom.file_name)}"', + "Content-Type": "application/octet-stream", + "X-Accel-Redirect": f"{LIBRARY_PATH_SUFFIX}/{rom.full_path}", + }, + ) if len(files_to_download) == 1: - return FileResponse( - path=f"{rom_path}/{files_to_download[0]}", filename=files_to_download[0] + return Response( + headers={ + "Content-Disposition": f'attachment; filename="{quote(files_to_download[0])}"', + "Content-Type": "application/octet-stream", + "X-Accel-Redirect": f"{LIBRARY_PATH_SUFFIX}/{rom.full_path}/{files_to_download[0]}", + }, ) + rom_path = f"{LIBRARY_BASE_PATH}/{rom.full_path}" + # Builds a generator of tuples for each member file async def local_files() -> AsyncIterator[AsyncMemberFile]: async def contents(filename: str) -> AsyncIterator[bytes]: diff --git a/docker/nginx/default.conf b/docker/nginx/default.conf index 5565cad7b..a93592c08 100644 --- a/docker/nginx/default.conf +++ b/docker/nginx/default.conf @@ -87,5 +87,11 @@ http { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } + + # Library files + location /library { + internal; + alias /romm/library; + } } }