Как разрабатывать программное обеспечение - программы загрузки изображений

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

Неудивительно, что многие из этих стартапов, решая проблемы загрузки, страдали от тех же проблем. Логика обработки загрузки изображений была спонтанной. Обработка файла произошла одновременно с запросом, что привело к резервному копированию очереди запросов сервера приложений.

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

Я здесь, чтобы показать тебе лучший способ.

Понимание технических проблем

Загрузка изображений может быть сложной, и варианты использования зависят от системы.

Есть много проблем с изображениями в целом, о которых большинство людей не задумывается, когда они погружаются в их создание.

Изображения в веб-приложении могут легко затрагивать следующие технические проблемы:

  • Отображение изображения в интерфейсе
  • Разрешение пользователям загружать изображение
  • Разрешение пользователям загружать изображение
  • Загрузка изображения на ваш сервер масштабируемым способом
  • Проверка данных изображения
  • Обработка изображения, выполнение кадрирования, оптимизации и других задач
  • Создание вариантов изображений, таких как баннеры и миниатюры
  • Хранение изображений
  • Связывание изображений с любыми записями, для которых вы их загружаете (например, с аватарами пользователей или баннерами кампании).

Хотя не все из них будут присутствовать, прочная система сможет гибко адаптироваться для поддержки этих вариантов использования с минимальными изменениями.

Компромиссы

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

Было бы полезно погрузиться в компромиссы, которые следует учитывать при выборе решения.

Масштабирование

Загрузка изображений печально известна сбоями серверов или возникновением тайм-аутов. Если пользователь пытается загрузить изображение размером 10 мегабайт, это потребляет много ресурсов:

  • 10 мегабайт памяти вашего сервера занято
  • Обработчик запроса привязан на все время, необходимое для загрузки 10 мегабайт
  • Использование ЦП для загрузки изображения

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

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

Безопасность

Загрузка и обработка изображений - огромный источник дыр в безопасности. Любая конечная точка, которая позволяет связать огромное количество ресурсов, уязвима для атак типа «отказ в обслуживании» (преднамеренных или нет).

Что еще более тревожно, обработка изображений - это плохо изученный аспект инженерии, который привел к некоторым трагическим недостаткам безопасности, предоставляя случайным пользователям возможность выполнять произвольные команды в качестве пользователя корневого уровня.

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

Авторизация

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

Наша система должна легко обрабатывать эти случаи авторизации для конкретного домена.

Последовательность

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

Варианты

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

В любом случае, мы должны поддерживать создание и использование вариантов в нашей системе загрузки. Создание вариантов может потребовать больших вычислительных ресурсов, и это не то, что мы хотим, чтобы наш основной веб-сервер делал. Архитектура должна передать это отдельной асинхронной службе.

Рост

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

Архитектура

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

Компоненты

Для чего предназначена каждая из частей архитектуры?

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

Но что это за нюансы? Что приобретается или теряется каждой точкой принятия решения? Почему мы предпочитаем использовать подписанные URL-адреса или другие элементы? Давайте углубимся в детали.

API загрузки изображений

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

Это никогда не касается файловых данных. Я оставляю это упражнение, чтобы читатель нашел способы сделать его RESTful.

Служба загрузки изображений

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

Подписанные URL

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

Подписанные URL загрузки
В простом случае /images/get_upload_url может вернуть неподписанный URL: /uploads.

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

/images/get_upload_url может вернуть подписанный URL:
/uploads?write_token=a99Xioajksf23.

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

Подписанные URL-адреса загрузки
Если бы у нас не было подписанных URL-адресов загрузки, любой мог бы загрузить изображение, если бы знал URL: /images/joseph-gefroh.png

Это может быть желательно во многих случаях, но в некоторых случаях, например в личных файлах, это может быть крайне нежелательно. В таких случаях мы не хотим, чтобы URL был общедоступным.

Мы можем заблокировать доступ за нашей собственной конечной точкой.
images/get_download_url, который возвращает временный токен, например:
/downloads?read_token=a99Xioajksf23.

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

Облачное хранилище файлов

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

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

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

Работа по обработке изображений

Обработка изображений может потребовать значительных ресурсов памяти и ЦП - это не то, что вы хотите выполнять в своем приложении или веб-сервере, который занят обработкой других запросов.

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

Запись метаданных изображения

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

Важно отметить, что мы не храним само изображение в нашей базе данных, мы просто сохраняем метаданные, которые затем используем позже для создания различных URL-адресов.

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

Алгоритм

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

  • Шаг 1. Клиент запрашивает URL загрузки с сервера (ЗАПРОС)
  • Шаг 2. Клиент загружает данные изображения на URL-адрес загрузки (ЗАГРУЗИТЬ)
  • Шаг 3. Клиент сообщает серверу, что загрузка завершена (ПОДТВЕРДИТЬ)
  • Шаг 4. Сервер обрабатывает изображение в фоновом режиме (ПРОЦЕСС)
  • Шаг 5. Клиент проверяет статус обработки изображения (ПРОВЕРИТЬ)
  • Шаг 6: Сервер завершил обработку изображения, уведомил клиента (FINALIZE)

Шаг 1. Клиент запрашивает URL загрузки с сервера (ЗАПРОС)

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

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

На этом этапе сервер может сгенерировать случайный URL-адрес:

  • ограничено по времени
  • проверенный
  • уполномоченный

Эти URL-адреса загрузки являются предварительно подписанными URL-адресами. То есть URL-адрес, который возвращает сервер, имеет параметры запроса, которые указывают на всю авторизацию, необходимую для загрузки в стороннюю службу.

После выполнения проверок авторизации сервер также создает запись в базе данных для отслеживания загрузки этого отдельного изображения с данными о:

  • имя файла
  • тип файла
  • URL-адрес файла
  • статус (например, requested, uploaded, processed)
  • ассоциации изображения (например, user, campaign)
  • вид ассоциации (например, banner, avatar)
  • токен записи (сгенерированный токен, который должен быть предоставлен для изменения изображения)
  • токен чтения (сгенерированный токен, который должен быть предоставлен для чтения изображения)
  • любые другие данные аудита

Сервер возвращает эти данные клиенту.

Шаг 2. Клиент загружает данные изображения на URL-адрес загрузки (ЗАГРУЗИТЬ)

Это довольно простой шаг.

Клиент, теперь вооруженный URL-адресом загрузки с сервера, просто выполняет запрос POST для этого URL-адреса. Затем эта служба принимает эти данные и сохраняет их.

Шаг 3. Клиент сообщает серверу, что загрузка завершена (ПОДТВЕРДИТЬ)

Клиент делает запрос к серверу с токеном, который сервер вернул ранее, сообщая серверу, что загрузка была завершена.

Шаг 4. Сервер обрабатывает изображение в фоновом режиме (ПРОЦЕСС)

Сервер проверяет токен, а затем проверяет этот запрос на загрузку.

Он запускает работу по обработке образа - проверяет целостность файла, создает варианты и выполняет оптимизацию без блокировки запросов к веб-серверу.

Шаг 5. Клиент проверяет статус обработки изображения (ПРОВЕРИТЬ)

Обработка изображений занимает некоторое время, и вы не хотите, чтобы клиент блокировал запрос. Клиент должен время от времени проверять, завершена ли обработка.

Шаг 6: Сервер завершил обработку изображения, уведомил клиента (FINALIZE)

В конце концов, проверка пройдет, и сервер вернет URL-адрес изображения. Теперь клиент может использовать изображение бесплатно.

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

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

Вот и все.

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

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

Построенный правильно, он также может поддерживать общую загрузку файлов!

Вы нашли эту историю полезной? Пожалуйста, хлопайте, чтобы выразить свою поддержку!
Если вы не нашли его полезным, сообщите мне, почему, с помощью комментария!