Загружайте файлы прямо в S3 из веб-приложения

Возможно, вам потребуется создать веб-приложение, позволяющее пользователям загружать очень большие файлы. Если вы создаете приложение Anvil, вы обычно используете компонент FileLoader для захвата файла пользователя, а затем сохраняете файл в Таблице данных. Но если размер вашего файла составляет много гигабайт, вы можете столкнуться с таймаутами при загрузке или превышением лимитов хранения ваших таблиц данных. Вместо этого имеет смысл хранить гигантские файлы непосредственно в облачном хранилище, таком как Amazon S3.

К счастью, Anvil упрощает использование сторонних облачных сервисов на серверной части и библиотек Javascript на внешнем интерфейсе. В этом примере мы рассмотрим, как использовать виджет uppy.io JavaScript и библиотеку Amazon boto3 для загрузки файлов из Anvil непосредственно в корзину S3.

Новичок в Анвиле? Anvil — это платформа для создания полнофункциональных веб-приложений только с Python. Это довольно продвинутое практическое руководство — вы можете начать с краткого руководства.

Посмотрите приложение, которое мы собираемся создать, клонируя исходный код.

Мы создадим приложение в пять шагов:

  1. Во-первых, мы создадим новое приложение Anvil.
  2. Затем мы настроим корзину Amazon S3 с соответствующими разрешениями.
  3. Далее мы добавим виджет Uppy в наше приложение.
  4. Затем нам нужно получить предварительно подписанный URL-адрес, используя библиотеку Amazon boto3.
  5. Наконец, мы предоставим предварительно подписанный URL-адрес Uppy, который загрузит файл на S3.

Шаг 1: Создайте приложение Anvil

Сначала войдите в Anvil и создайте новое пустое приложение. Нам нужно будет использовать Секреты приложений нашего приложения для хранения ключей доступа AWS, которые мы получим на следующем шаге, так что вперед и добавьте службу секретов приложений в ваше приложение.

Шаг 2. Настройте корзину S3

Если у вас еще нет корзины S3, вам нужно ее настроить. Руководство по началу работы можно найти здесь: Начало работы с Amazon S3.

В вашей корзине S3 должен быть включен доступ между источниками. Чтобы разрешить загрузку из браузеров, достаточно следующей политики перекрестного происхождения — вы можете вставить ее в текстовое поле политики CORS в консоли S3:

[{"AllowedHeaders": ["content-type"], "AllowedMethods": ["POST", "PUT"], "AllowedOrigins": ["*"], "ExposeHeaders": []}]

Вам также понадобится Ключ доступа AWS и Секретный ключ доступа, у которого есть разрешение на запись в вашу корзину S3.

Сохраните ваши ключи доступа в App Secrets вашего приложения. Сохраните идентификатор ключа доступа AWS в секрете с именем aws_key_id, а секретный ключ — в секрете с именем aws_secret_key.

Шаг 3. Добавьте виджет Uppy в приложение.

Uppy — это сторонний JavaScript, который отображает форму, в которой пользователи могут перетаскивать файлы для загрузки на S3. Мы можем добавить виджет в приложение Anvil, используя Native Libraries и anvil.js module.

Во-первых, добавьте следующую строку в собственные библиотеки вашего приложения:

<link href="https://releases.transloadit.com/uppy/v3.3.1/uppy.min.css" rel="stylesheet">

Затем перейдите в представление «Дизайн» вашей формы, добавьте панель столбцов и назовите ее uppy_target. Вот куда пойдет виджет Uppy.

Переключитесь в представление кода и добавьте следующие операторы импорта в код формы:

from anvil.js import window, import_from, get_dom_node
import anvil.js

Нам нужно настроить две функции для использования виджета Uppy:

  • get_upload_parameters вернет учетные данные AWS из серверного модуля, что даст Uppy временное разрешение на доступ к корзине S3.
  • on_complete будет вызываться Uppy после завершения загрузки, независимо от того, была ли она успешной.

На данный момент функции ничего не делают, но мы исправим это на шагах 4 и 5.

  def get_upload_parameters(self, file_descr):
    # This function is called to get the pre-signed URL
    # for the S3 upload.
    pass
    
    
  def on_complete(self, result, *args):
    # This function is called when the upload is finished,
    # successful or not.
    pass

Затем в методе __init__ формы добавьте следующий код:

    mod = anvil.js.import_from("https://releases.transloadit.com/uppy/v3.3.1/uppy.min.mjs")
    self.uppy = mod.Uppy().use(mod.Dashboard, {
      'inline': True,
      'target': get_dom_node(self.uppy_target)
    })
    self.uppy.use(mod.AwsS3, {
      'getUploadParameters': self.get_upload_parameters
    })
    self.uppy.on('complete', self.on_complete)

Если вы запустите приложение сейчас, вы увидите пустой загрузчик Uppy. Ничего не произойдет, если вы попытаетесь загрузить файл, потому что мы еще не настроили функцию get_upload_parameters. На следующем шаге мы будем использовать boto3 для возврата необходимых нам учетных данных AWS.

Шаг 4. Получите предварительно подписанный URL-адрес из S3 с помощью boto3

Ключ доступа к AWS, который мы настроили на шаге 2, обеспечит доступ к S3, но именно по этой причине он является мощным удостоверением. Мы не хотим выставлять это напоказ клиенту и давать кому угодно неограниченный доступ к нашему S3.

Вместо этого мы можем сгенерировать предварительно подписанный URL-адрес, который предоставляет временный доступ для загрузки одного файла в целевую корзину S3. Мы можем использовать библиотеку boto3 из модуля сервера для создания предварительно подписанного URL.

Мы можем доверять серверному коду с мощными учетными данными AWS, потому что у нас есть контроль над сервером. Поскольку клиент контролируется любым, кто посещает наше приложение, мы не можем доверять ему ничего, кроме предварительно подписанного URL-адреса с ограниченным сроком действия. Подробнее о безопасности клиентов и серверов читайте здесь.

В серверном модуле добавьте необходимые операторы импорта и настройте переменные для корзины S3 и региона AWS:

import boto3, string
from datetime import datetime
from random import SystemRandom

BUCKET_NAME="anvil-upload-demo" # Replace with your S3 bucket name
REGION_NAME="eu-west-2" # Replace with your AWS region

Далее нам нужно создать функцию для создания предварительно подписанного URL-адреса и сделать его доступным для вызова из клиента. Эта функция будет вызываться в функции get_upload_parameters, которую мы создали на предыдущем шаге:

@anvil.server.callable
def get_presigned_url(filename, content_type):
  s3_client = boto3.client(
      's3',
      aws_access_key_id=anvil.secrets.get_secret('aws_key_id'),
      aws_secret_access_key=anvil.secrets.get_secret('aws_secret_key'),
      region_name=REGION_NAME
  )

  # Because the filename is user-supplied, we use the current date and a random string to ensure
  # a unique filename for each upload
  random_string = ''.join(SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(10))
  filename_to_upload = f"{datetime.now().isoformat()}-{random_string}-{filename}"
  
  r = s3_client.generate_presigned_post(BUCKET_NAME, filename_to_upload)

  # Return the data in the format Uppy needs
  return {
    'url': r['url'],
    'fields': r['fields'],
    'method': 'POST'
  }

Шаг 5: Предоставьте предварительно подписанный URL-адрес Uppy

Когда Uppy будет готов загрузить файл, он вызовет функцию get_upload_parameters. Теперь мы можем изменить функцию, чтобы она вызывала функцию сервера get_presigned_url и возвращала результаты (предварительно подписанный URL-адрес и связанные данные) в Uppy.

Поскольку мы используем эту функцию в качестве обратного вызова из Javascript, нам нужно добавить декоратор @anvil.js.report_exceptions. Это гарантирует, что если код выдает исключение, оно правильно отображается в консоли Anvil.

Вернитесь к клиентскому коду формы и обновите get_upload_parameters:

  @anvil.js.report_exceptions
  def get_upload_parameters(self, file_descr):
    return anvil.server.call('get_presigned_url', file_descr['name'], file_descr['type'])

Если вы правильно настроили корзину и ключи доступа, теперь вы сможете загружать файлы на S3.

При желании мы можем настроить функцию on_complete для отправки отзывов от Uppy. Мы можем добавить это в предупреждение для отображения пользователю:

@anvil.js.report_exceptions
  def on_complete(self, result, *args):
    successful = result['successful']
    failed = result['failed']
    alert(f"{len(successful)} succeeded, {len(failed)} failed")

Вот и все!

Это все, что нам нужно для загрузки файлов прямо из браузера нашего пользователя в S3.

Вы можете скопировать образец приложения сюда. Конечно, вам понадобится собственное ведро S3 и ключи доступа для его работы.

Для наших следующих шагов обратитесь к Документации Uppy, чтобы узнать, как настроить виджет загрузки Uppy, или к Документации boto3, чтобы узнать, как обращаться с вашим файлом, когда он безопасно загружен в S3.

Использование других облачных хранилищ

Amazon S3 — не единственная облачная служба хранения данных. Но поскольку он был первым, многие другие сервисы используют тот же API. Вы можете адаптировать пример кода из этого примера для использования других сервисов, таких как Cloudflare R2, DigitalOcean Spaces или Backblaze B2.

Просто проверьте документацию предпочитаемого вами сервиса, чтобы узнать, как использовать его с boto3. (Например, вот страница CloudFlare по настройке boto3 для работы с их сервисом R2.)

Создайте собственное приложение с помощью Anvil

Если вы здесь впервые, добро пожаловать! Anvil — это платформа для создания полнофункциональных веб-приложений только на Python. Не нужно возиться с JS, HTML, CSS, Python, SQL и всеми их фреймворками — просто создайте все это на Python.

Зарегистрироваться в Анвиле

Первоначально опубликовано на https://anvil.works.