Как происходит загрузка изображений с S3 на фронтенд
Что проверяют
Понимаете ли вы, что картинки не должны идти через ваш Python-сервис, и умеете ли безопасно выдавать доступ к приватным файлам. Следом обычно спрашивают, как загрузить файл с клиента напрямую в бакет и как сделать превью.
Как отвечать
Главная мысль: бэкенд раздаёт не байты, а ссылки. Байты фронтенд берёт у хранилища или CDN сам. Варианты зависят от того, публичные ли файлы:
- публичные картинки: бакет или отдельный префикс за CDN, в базе хранится ключ объекта, API отдаёт готовый URL. Кэшируется хорошо, сервер не участвует;
- приватные: бэкенд проверяет права и генерирует presigned URL через
boto3:s3.generate_presigned_url("get_object", Params=..., ExpiresIn=300). Подпись считается локально, без запроса к S3. Для подписи SigV4 срок не больше семи дней, а при временных учётных данных роли ссылка умрёт вместе с ними; - загрузка с клиента:
generate_presigned_postили presignedPUT, браузер кладёт файл прямо в бакет, потом сообщает бэкенду ключ. Для такой загрузки из JS на бакете нужен CORS, а для обычного<img src>он не нужен; - превью: при загрузке ставите задачу в Celery,
Pillowрежет несколько размеров в WebP или AVIF, либо используете сервис ресайза на лету вроде imgproxy; - метаданные: при загрузке указывайте
ContentType, иначе объект получитbinary/octet-stream, и браузер может скачать его вместо показа. Для неизменяемых файлов добавьте длинныйCache-Control.
Всё это одинаково работает с S3-совместимыми хранилищами, например MinIO: меняется только endpoint_url.
Где спотыкаются
Отдают файлы через Django или FastAPI, читая объект в память. Каждая картинка занимает воркер и память, а CDN не помогает.
Делают бакет публичным целиком, чтобы «просто заработало». В новых бакетах публичный доступ заблокирован по умолчанию именно поэтому.
Генерируют новую presigned-ссылку на каждый показ страницы. У ссылки каждый раз новая подпись, браузер считает её другим адресом и заново качает картинку, так что кэш не работает.