Как загружать объекты на AWS S3 прямо из браузера без посредников.

В этом туториале мы будем использовать Minio, но вместо минио вы можете использовать любой сервер поддерживающий S3 протокол.

Обычно для загрузки пользовательских объектов используют AWS lambda в которую через POST запрос пользователи отправляют файлы, и на лямбде происходит авторизация запроса, и лямбда уже сама загружает пользовательский файл на AWS S3. Но такой подход очень неэфективный вить мы тратим много трафика для получения файла на лямбду, и иеще и на загрузку в S3. 

Чтобы исправить эту проблему придумали presigned urls. Presigned urls это "подписанные ссылки", которые подписывает бекенд и отдаёт браузеру который может POST запросом загрузить файл который будет доступен в S3 хранилище. Сделано это было чтобы каждый человек из интернета не мог просто загрузить любой файл, а лишь только с разрешения вашего бекенда. Presigned urls очень схожи с JWT, они тоже подписываются через криптографические функции.

Но чтобы работать с Presigned urls вам не обязательно полностью понимать их принцип работы, за вас это сделает библиотека-клиент. В нашем случае мы будем использовать питон клиент от проекта minio который совместим с S3 протоколом и может работать как с minio хранилищем так и с AWS S3. 

Чтобы установить пакет minio нужно просто написать pip install minio. В этом туториале мы будем использовать minio сервер, чтобы его поднять мы можем использовать docker:

docker run -p 9000:9000 minio/minio server /data

После запуска обычно стоят заводские ACCESS_KEY и SECRET_KEY minioadmin minioadmin. Но ОЧЕНЬ ВАЖНО ЧТОБЫ ВЫ ПОСТАВИЛИ РАНДОМНЫЕ ЗНАЧЕНИЯ НА ПРОДАКШЕН СЕРВЕРЕ и чтобы их знал ТОЛЬКО ВАШ БЕКЕНД. Иначе любой человек сможет делать с вашим хранилищем что захочет. Для этого при запуске из докера вы должны задать свои длинные  ACCESS_KEY и SECRET_KEY, как это сделать для докера написано в документации: https://docs.min.io/docs/minio-docker-quickstart-guide.

Теперь нужно создать хранилище в minio. Сделать это можно через веб интерфейс: http://127.0.0.1:9000. Логин:пароль minioadmin, minioadmin (те самые access_key и  secret_key). 
В правом нижнем углу нажмите create bucket, и наберите например 123. Теперь у нас есть хранилище и время настроить клиент питона на создание подписанных ссылок.

Чтобы не заморачиватся с созданием http бекенда мы просто будем копировать подписанную ссылку из консоли питона, но чтобы работало на продакшене вам нужно написать бекенд например на flask, который через json будет отдавать эту подписанную ссылку.

from minio import Minio
from minio.error import ResponseError
minioClient = Minio('127.0.0.1:9000',
                    access_key='minioadmin',
                    secret_key='minioadmin',
                    secure=False) # если у вы настроили https в minio то True
                                # но по туториалу мы ничего не настраивали так что False.


Далее попробуем сгенерировать подписанную ссылку для загрузки файла пользователя на несуществующее место. 

print(minioClient.presigned_put_object('123', 'myobject')) 


123 это имя нашего хранилища которое мы создали через веб интерйейс, myobject это имя обьекта на место которого загрузится пользовательский файл. После запуска этого кода мы получим ссылку в ответ:
(Которую надо будет отдать клиенту как и говорилось выше)

Теперь чтобы загрузить в наше хранилище что-то из браузера мы можем использовать fetch. Создадим небольшую страницу



При выборе файла через форму отпрвляется запрос на наше хранилище и файл сохраняется. 

Мы можем убедится в этом проверив админку minio. 
Теперь чтобы вывести этот файл на html странице мы должны настроить ACL на read only доступ без авторизации. Для этого нажимаем edit policy:
И в поле prefix ставим *, это означает что любой файл в этом хранилище имеет доступ на просмотр. Вы так-же можете создать папку в которую пользователи будут загружать свой контент и задать превикс uploads/*. Но в этом туториале мы дадим доступ на чтение для всего. Чтобы получить файл нам нужно обратится на http://127.0.0.1:9000/123/myobject. В моём случае я выбрал mp4 файл и могу теперь проиграть его на html странице через тег video:

<video src="http://127.0.0.1:9000/123/myobject"></video>




Способ загрузки который мы рассмотрели подходит для небольших файлов (50 мегабайт - 500 мегабайт). Если вы хотите загружать файлы по 10-100 гигабайт то нужно использовать partial uploading. Пример когда использующий такой подход есть тут: https://github.com/nitisht/minio-evaporatejs-example, но сервер подписи написан на nodejs. Так-же такой способ требует больше вычислений со стороны бекенда тк нужно одобрять каждый кусочек файла. Например если файл 1 гигабайт а кусочки по 10 мегабайт то это 100 запросов на бекенд! Несмотря на простоту наш  способ загрузки довольно быстрый и спокойно выдерживает по несколько загрузок в секунду, и значительно оптимизированее чем например загрузки через встроенную в django систему загрузок!

Если этот пост найдёт свою публику то я опубликую еще пост как сделать загрузку кусочками, а не 1 большим запросом. 

Комментарии

Популярные сообщения из этого блога

DOS атака при помощи Python

Ведем телеграм канал через питон

Django migrations не видит изменения моделей