Интеграция AWS S3 для хранения файлов мобильного приложения
Когда пользователь загружает аватар или документ через мобильное приложение, первый вопрос — куда это отправить. Хранить бинарники в базе данных никто в здравом уме не будет. Передавать через собственный бэкенд — значит гонять мегабайты через лишний hop и платить за трафик дважды. AWS S3 с presigned URL решает это элегантно: клиент загружает напрямую в S3, бэкенд выдаёт только временный токен.
Как устроена правильная интеграция
Стандартная схема выглядит так: мобильный клиент запрашивает у бэкенда presigned URL, получает его, делает PUT-запрос прямо в S3, и только потом сообщает бэкенду, что файл загружен. Бэкенд никогда не видит байты файла — только метаданные.
На iOS используем AWS SDK for Swift (aws-sdk-swift) или более lightweight вариант — AWSS3 через Amplify. На Android — AWS SDK for Kotlin или Amplify Android. Для React Native — @aws-sdk/client-s3 из официального JS SDK v3.
// iOS: загрузка через presigned URL без SDK — через URLSession
let presignedURL = URL(string: urlFromBackend)!
var request = URLRequest(url: presignedURL)
request.httpMethod = "PUT"
request.setValue("image/jpeg", forHTTPHeaderField: "Content-Type")
let uploadTask = URLSession.shared.uploadTask(with: request, fromFile: localFileURL) { data, response, error in
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
// retry logic здесь
return
}
// Уведомляем бэкенд об успешной загрузке
self.confirmUpload(fileKey: fileKey)
}
uploadTask.resume()
На Android через OkHttp или Retrofit — аналогично. Главное — не забыть про Content-Type в заголовке: S3 проверяет его совпадение с тем, что было указано при генерации presigned URL. Расхождение даёт 403.
Где чаще всего возникают проблемы
Срок действия presigned URL. По умолчанию — 15 минут. Если пользователь на медленном соединении или приложение долго ждало выбора файла, URL просрочится ещё до начала загрузки. Практика: генерировать URL непосредственно перед PUT-запросом, а не в момент открытия picker.
CORS для веб-превью. Если те же файлы нужно показывать в WebView или на сайте, S3 bucket требует настройки CORS policy. Без неё браузер блокирует запрос, хотя мобильный клиент работает нормально — URLSession и OkHttp CORS не проверяют.
Мультипарт для больших файлов. S3 Multipart Upload нужен от 5 МБ. Для видео это обязательно. AWS Amplify Storage абстрагирует это автоматически через Amplify.Storage.uploadFile, но при ручной реализации через presigned URL мультипарт требует отдельного API: CreateMultipartUpload → UploadPart × N → CompleteMultipartUpload.
// Android: использование Amplify для автоматического multipart
Amplify.Storage.uploadFile(
StoragePath.fromString("uploads/${userId}/${filename}"),
localFile,
StorageUploadFileOptions.builder()
.contentType("video/mp4")
.build(),
progress -> Log.i("Upload", "Progress: ${progress.fractionCompleted}"),
result -> confirmUpload(result.path),
error -> handleUploadError(error)
)
Права доступа через IAM. Bucket policy должна разрешать s3:PutObject только для конкретных prefix-ов. Никаких wildcard s3:* на весь bucket в production. Используйте Resource-based policy + IAM role с минимальными правами.
Структура файлов и жизненный цикл
Заранее продуманная структура ключей в S3 спасает от головной боли позже:
uploads/{userId}/avatar/{timestamp}.jpg
uploads/{userId}/documents/{uuid}.pdf
public/content/{category}/{slug}.webp
temp/{sessionId}/{filename} ← очищается lifecycle rule через 24h
S3 Lifecycle Rules позволяют автоматически удалять временные файлы и переносить редко используемые в S3 Glacier для экономии. Настраивается через Terraform или AWS Console — ничего в коде приложения.
CloudFront перед S3
Отдавать медиафайлы напрямую из S3 — нормально для старта. Но для production с пользователями в разных регионах стоит поставить CloudFront. Это CDN с edge nodes, который кэширует файлы ближе к пользователю и снижает latency при загрузке изображений. На iOS это ощутимо при загрузке thumbnail-листов: без CDN каждый запрос идёт в us-east-1, с CDN — с ближайшего edge.
Для presigned URL с CloudFront нужен CloudFront Signed URL (не S3 presigned) — другой механизм с другой логикой подписи.
Сроки
Базовая интеграция (presigned upload + download, один платформа): 3–5 дней. Полная реализация с мультипарт, progress tracking, retry, lifecycle rules, CloudFront: 2–3 недели. Стоимость рассчитывается индивидуально.







