Загрузка файлов представляет собой проблему масштабируемости, которую легко решить с помощью бессерверной версии - без лишних затрат на ваш кошелек

Механизм загрузки файлов из браузера существует с первых дней Интернета. В полнофункциональной серверной среде очень легко использовать Django, Express или любой другой популярный фреймворк. Это не слишком интересная тема, пока вы не столкнетесь с проблемой масштабирования.

Представьте себе такой сценарий - у вас есть приложение, которое загружает файлы. Все хорошо, пока сайт внезапно не набирает популярность. Вместо обработки гигабайта загрузок в месяц, использование увеличивается до 100 ГБ в час в месяц, предшествующий налоговому дню. После этого использование снова снизится еще на год. Это как раз та проблема, которую нам предстояло решить.

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

Вы также можете столкнуться с некоторыми трудными условиями гонки, если ваш сервер выйдет из строя во время обработки загруженного файла. Добрался ли файл до места назначения? В каком состоянии находилась обработка? Может быть очень сложно воспроизвести шаги до отказа или узнать состояние транзакций, когда сервер перегружен.

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

Все это звучит великолепно, но как это работает на практике, когда сервера больше нет, чтобы выполнять аутентификацию и посредническую работу?

Демонстрационное приложение загрузчика S3

Я установил приложение в Репозитории бессерверных приложений AWS, которое вы можете развернуть в своей учетной записи AWS. Попробуйте установить приложение и развернуть домашнюю страницу Gist в документации, а затем мы рассмотрим решение:

За кулисами происходит двухэтапный процесс: сначала веб-страница вызывает функцию Lambda для запроса URL-адреса загрузки, а затем загружает файл JPG непосредственно в S3:

URL-адрес является важной частью процесса - он содержит ключ, подпись и токен в параметрах запроса, разрешающих передачу. Без них передача не состоится.

Не стесняйтесь клонировать репозиторий Github с https://github.com/jbesw/askjames-s3uploader. Общедоступное демонстрационное приложение удаляет все файлы в течение 24 часов, и для предотвращения злоупотреблений включено регулирование.

Зачем использовать лямбда-функцию?

Можно исключить лямбда-функцию и делать все из клиентского браузера, но есть ряд веских причин избегать этого подхода. Помимо добавления значительно большего количества кода на веб-страницу, функция Lambda позволяет вам управлять процессом вдали от любопытных глаз любого потенциального злоумышленника.

Например, что, если вы хотите сначала авторизовать пользователя - может быть, только платные подписчики могут загружать, тогда как бесплатные пробные версии доступны только для чтения? Или, может быть, вам нужно добавить дополнительные перехватчики в процессе, чтобы запускать другие рабочие процессы, ведение журнала или добавить прерыватель в случае слишком большого количества загрузок. Или вам может быть неудобно раскрывать имена сегментов или другую информацию в коде на стороне клиента.

Функция Lambda, запрашивающая подписанный URL - шаг 1 за этим демонстрационным приложением - довольно минимальна:

const uuidv4 = require('uuid/v4')
const AWS = require('aws-sdk')
AWS.config.update({ region: process.env.REGION || 'us-east-1' })
const s3 = new AWS.S3();
exports.handler = async (event) => {
  return await getUploadURL()
}
const getUploadURL = async () => {
  const actionId = uuidv4()
  const s3Params = {
    Bucket: '<< ENTER YOUR BUCKET NAME HERE >>',
    Key:  `${actionId}.jpg`,
    ContentType: 'image/jpeg',
    ACL: 'public-read',
  }
  return new Promise((resolve, reject) => {
    let uploadURL = s3.getSignedUrl('putObject', s3Params)
    resolve({
      "statusCode": 200,
      "isBase64Encoded": false,
      "headers": { "Access-Control-Allow-Origin": "*" },
      "body": JSON.stringify({
        "uploadURL": uploadURL,
        "photoFilename": `${actionId}.jpg`
      })
    })
  })
})

Хотя эта функция проста, на этом этапе было бы легко добавить всевозможную логику, прежде чем запрашивать подписанный URL-адрес. После того, как функция будет создана, остается лишь настроить API Gateway и создать единственный метод GET для создания конечной точки. Как вариант, вы можете выполнить развертывание с помощью Serverless Framework и автоматизировать этот шаг.

Создание интерфейса

Проект основан на стандартном шаблоне Vue, но вся важная работа по демонстрации этой функциональности происходит в s3uploader.vue.

Опять же, это минимальное кодирование без обработки ошибок или тонкостей для упрощения примера, но вы можете видеть, что код, необходимый для выполнения этой работы, представляет собой всего несколько строк. Если вы откроете консоль разработчика Chrome (нажмите F12), вы увидите вывод console.log на протяжении всего процесса.

Установите свои разрешения

Наконец, несколько слов о разрешениях: когда вы запрашиваете подписанные URL-адреса, запрашивающей функции требуются соответствующие разрешения IAM как для запроса, так и для загрузки файла.

Некоторые управляемые политики включают слишком широкое разрешение S3: *. Часто можно увидеть, что эти функции работают при разработке и не работают в производственной среде, потому что роль IAM переключается на гораздо более узкий набор привилегий при переходе в рабочую среду. Роль IAM, используемая функцией, должна иметь возможность записи в корзину, иначе она не будет работать.

Вернуться к масштабируемости

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

Кроме того, поскольку между пользователем и S3 нет переключения между сервером, вы можете исключить еще одну точку отказа. S3 славится своей долговечностью «11 девяток», поэтому вы также получаете выгоду от того факта, что файл практически невозможно просто исчезнуть.

В целом, учитывая преимущества бессерверной реализации, это кажется очевидным и простым способом управления любой формой загрузки файлов при работе с инфраструктурой AWS.