Практикум S3: объект, multipart и подписанный URL
Оглавление · S3 · Медиа
Нужны отдельный учебный Garage 2.3.0, приватный bucket lesson-upload, ключ
с правами только на этот bucket и AWS CLI v2. Фиксируйте aws --version
в отчёте. Bucket и права создаются по инструкции Garage
в собственной среде, а не в production-медиабиблиотеке. Это ручной опыт
со своим сервисом; при подготовке учебника живой Garage не запускался.
В локальной конфигурации проекта S3 API доступен на 127.0.0.1:3900, website
endpoint — на 3902. Для подписи нужен API endpoint. Публичный website URL
и подписанный API URL не взаимозаменяемы. Настройте отдельный CLI profile
lesson-garage с region garage и path-style addressing. Credentials храните
локально через штатную настройку клиента, не в тексте команд.
Put, Head, Get и List
Из временного каталога создайте вымышленный файл:
printf 'Учебный объект
' > sample.txt
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api put-object --bucket lesson-upload --key lesson/sample.txt --body sample.txt --content-type text/plain
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api head-object --bucket lesson-upload --key lesson/sample.txt
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api get-object --bucket lesson-upload --key lesson/sample.txt downloaded.txt
cmp sample.txt downloaded.txt
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api list-objects-v2 --bucket lesson-upload --prefix lesson/
Head показывает Content-Type/Length/ETag, Get возвращает те же байты, List —
ключ. CLI может автоматически проходить страницы listing; при использовании
SDK следите за continuation token. Добавьте key с пробелом/Unicode и проверьте
encoding. lesson/ — prefix, не настоящий каталог с отдельными POSIX-правами.
В production Strapi хранит и метаданные файла, и связи; один PutObject не создаёт
запись медиабиблиотеки CMS.
Presigned GET
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3 presign s3://lesson-upload/lesson/sample.txt --expires-in 60 > signed-url.txt
URL из этого локального файла используйте для GET без передачи credentials. Не публикуйте файл/URL: до истечения он даёт доступ к объекту. Скачайте дважды, проверьте одинаковые байты; подпись не является одноразовым разрешением. После истечения повторите: ожидаем отказ. Измените key, метод или подписанный host — исходная подпись не должна подходить. Перенос с API endpoint на website endpoint нарушает подписанный запрос.
aws s3 presign здесь подписывает GET. Для PUT используйте SDK presigner
с PutObject, отдельно ограничивайте key, срок, входные параметры и проверку
полученного файла. Подписание само по себе не валидирует изображение, размер
или права пользователя приложения. Direct upload браузера также требует
проверки CORS на API endpoint; терминальный GET этого не подтверждает.
В Atmanki готового endpoint выдачи presigned PUT нет.
Multipart: один объект из двух частей
Создайте две небольшие учебные части. Первая 5 MiB, вторая 1 MiB; минимальный размер применяется ко всем частям, кроме последней. Повторяющиеся байты здесь — удобная фикстура, не реальное пользовательское медиа.
python3 - <<'PYDATA'
from pathlib import Path
Path('part1.bin').write_bytes(b'a' * (5 * 1024 * 1024))
Path('part2.bin').write_bytes(b'b' * (1024 * 1024))
Path('whole.bin').write_bytes(Path('part1.bin').read_bytes() + Path('part2.bin').read_bytes())
PYDATA
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api create-multipart-upload --bucket lesson-upload --key lesson/multipart.bin
Сохраните возвращённый UploadId в shell variable lesson_upload_id, не путайте
с key. Затем:
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api upload-part --bucket lesson-upload --key lesson/multipart.bin --upload-id "$lesson_upload_id" --part-number 1 --body part1.bin
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api upload-part --bucket lesson-upload --key lesson/multipart.bin --upload-id "$lesson_upload_id" --part-number 2 --body part2.bin
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api list-parts --bucket lesson-upload --key lesson/multipart.bin --upload-id "$lesson_upload_id"
До complete итоговый новый object не готов для обычного Get. В parts.json
вставьте фактические ETags ответов, сохраняя кавычки внутри JSON string:
{
"Parts": [
{ "ETag": "\"ETAG_FROM_PART_1\"", "PartNumber": 1 },
{ "ETag": "\"ETAG_FROM_PART_2\"", "PartNumber": 2 }
]
}
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api complete-multipart-upload --bucket lesson-upload --key lesson/multipart.bin --upload-id "$lesson_upload_id" --multipart-upload file://parts.json
aws --profile lesson-garage --endpoint-url http://127.0.0.1:3900 s3api get-object --bucket lesson-upload --key lesson/multipart.bin completed.bin
cmp whole.bin completed.bin
Повтор part с тем же номером заменяет эту часть по контракту upload. Отдельно
создайте ещё один upload с другим key, загрузите часть и выполните
abort-multipart-upload с его UploadId. Проверьте list-multipart-uploads,
что незавершённый upload больше не перечисляется. После неизвестного результата
complete сначала исследуйте object и upload state; не начинайте слепо новую
загрузку и не обещайте exactly-once только по совпадению bytes.
Исходник схемы
stateDiagram-v2 [*] --> Started: CreateMultipartUpload Started --> Parts: UploadPart Parts --> Parts: Следующая часть или повтор номера Parts --> Object: CompleteMultipartUpload Started --> Aborted: AbortMultipartUpload Parts --> Aborted: AbortMultipartUpload
Совместимость и приёмка
Garage поддерживает SigV4/presigned URLs и multipart API, но имеет собственную модель bucket/key permissions. AWS ACL и IAM bucket policy нельзя копировать как его правила доступа; bucket versioning также не является доступной страховкой перезаписи по контракту Garage. Проверяйте версию и конкретные операции. ETag multipart не используйте как универсальный hash содержимого: сравните байты или заранее выбранную checksum.
Результат: Put/Get совпадают, private read без подписи запрещён,
подписанный GET работает до срока, multipart совпадает с whole, abort убирает
незавершённую загрузку. Зафиксируйте статусы и ошибки, не credentials и URL.
Очистите только свои два завершённых lesson/ объекта через delete-object
и перечисленные uploads; не удаляйте bucket media или чужие ключи.
Связь объекта с CMS, публикацией и backup проверяется отдельно в
жизненном цикле медиа.
Источники: совместимость Garage, CLI presign, multipart.