Учебник веб-разработки
Разделы учебника
На этой странице

IX. Strapi и медиа

Практикум 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.