"""파일 응답에 브라우저 캐시 검증(ETag)을 붙이는 공통 유틸. 3D 프리뷰·등고선처럼 한 번 만들면 잘 바뀌지 않는 파일은, 브라우저가 이미 받아 둔 것을 그대로 쓰게 해야 새로고침이 빠르다. 그렇다고 무조건 캐시를 믿게 두면 모델을 다시 만들었을 때 옛 파일이 계속 보인다. 그래서 파일의 수정시각·크기로 ETag를 만들어 보내고, 브라우저가 같은 ETag를 들고 오면 본문 없이 304만 돌려준다. 파일이 바뀌면 ETag가 저절로 달라져 새 파일을 받는다. """ from __future__ import annotations from pathlib import Path from fastapi import Request from fastapi.responses import FileResponse, Response # 항상 서버에 물어보되(변경 감지), 안 바뀌었으면 본문을 다시 받지 않는다. CACHE_CONTROL = "private, max-age=0, must-revalidate" def file_etag(path: Path) -> str: """파일 수정시각·크기로 만든 ETag. 파일이 바뀌면 값이 달라진다.""" stat = path.stat() return f'"{int(stat.st_mtime)}-{stat.st_size}"' def cached_file_response( request: Request, path: Path, media_type: str, filename: str | None = None, ) -> Response: """ETag를 붙인 파일 응답. 브라우저가 가진 것과 같으면 304만 돌려준다.""" etag = file_etag(path) headers = {"ETag": etag, "Cache-Control": CACHE_CONTROL} if request.headers.get("if-none-match") == etag: return Response(status_code=304, headers=headers) return FileResponse(path, media_type=media_type, filename=filename, headers=headers)