Подтвердить что ты не робот

REST API - обработка файлов (т.е. изображений) - лучшие практики

Мы разрабатываем сервер с REST API, который принимает и отвечает с JSON. Проблема в том, что если вам нужно загружать изображения с клиента на сервер.

Заметим также, что я говорю о прецеденте, где сущность (пользователь) может иметь файлы (carPhoto, licensePhoto), а также иметь другие свойства (имя, адрес электронной почты...), но когда вы создаете нового пользователя, вы не отправьте эти изображения, они добавляются после процесса регистрации.


Решения, о которых я знаю, но каждый из них имеет некоторые недостатки

1. Используйте multipart/form-data вместо JSON

good: запросы POST и PUT как можно более RESTful, они могут содержать текстовые входы вместе с файлом.

минус: это не JSON больше, что намного легче тестировать, отлаживать и т.д. сравнить с multipart/form-data​​p >

2. Разрешить обновление отдельных файлов

Запрос POST для создания нового пользователя не позволяет добавлять изображения (что хорошо в нашем случае использования, как я сказал при начале), загрузка изображений выполняется с помощью запроса PUT как multipart/form-data, например, для пользователей/пользователей/4/carPhoto

good: все (кроме самого загружаемого файла) остается в JSON, его легко тестировать и отлаживать (вы можете записывать полные запросы JSON, не опасаясь их длины)

минусы: это не интуитивно понятно, вы не можете POST или PUT всех переменных сущности одновременно, а также этот адрес /users/4/carPhoto можно рассматривать скорее как коллекцию (стандартный вариант использования для REST API выглядит так: /users/4/shipments), Обычно вы не можете (и не хотите) GET/PUT каждую переменную объекта, например users/4/name. Вы можете получить имя с GET и изменить его с помощью PUT у пользователей /4. Если после идентификатора есть что-то, это обычно другая коллекция, например users/4/reviews

3. Использовать Base64

Отправьте его как JSON, но закодируйте файлы с Base64.

good: То же, что и в первом решении, это как сервис RESTful, насколько это возможно.

минус: еще раз, тестирование и отладка намного хуже (тело может иметь мегабайты данных), увеличивается размер и время обработки как на клиенте, так и на сервере


Я бы очень хотел использовать решение no. 2, но у него есть свои минусы... Любой может дать мне лучшее представление о том, "что лучше"?

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

4b9b3361

Ответ 1

OP здесь (я отвечаю на этот вопрос через два года, пост Дэниела Сереседо был неплох, но веб-сервисы развиваются очень быстро)

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

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

У нас один и тот же REST API (Node.js) как для мобильных приложений (iOS/Android), так и для веб-интерфейса (с использованием React). Это 2017 год, поэтому вы не хотите хранить изображения локально, вы хотите загрузить их в какое-то облачное хранилище (Google cloud, s3, cloudinary,...), поэтому вам нужна общая обработка этих файлов.

Наш типичный процесс заключается в том, что, как только вы выбираете изображение, оно начинает загружаться в фоновом режиме (обычно это POST on/images endpoint), возвращая вам идентификатор после загрузки. Это действительно удобно для пользователя, потому что пользователь выбирает изображение, а затем обычно переходит к некоторым другим полям (например, адрес, имя,...), поэтому, когда он нажимает кнопку "отправить", изображение обычно уже загружено. Он не ждет и смотрит на экран с надписью "загрузка...".

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

Кроме того, если данные не являются частными, то вы отправляете обратно в app/frontend только URL-адрес и загружаете его напрямую из облачного хранилища, что значительно экономит полосу пропускания и время обработки для вашего сервера. В наших больших приложениях каждый месяц загружается много терабайт, и вы не хотите обрабатывать это непосредственно на каждом из ваших REST API-серверов, который ориентирован на работу с CRUD. Вы хотите обработать это в одном месте (наш Imageserver, который имеет кэширование и т.д.) Или позволить облачным службам обрабатывать все это.


Минусы: Единственные "минусы", о которых вы должны подумать, это "не назначенные изображения". Пользователь выбирает изображения и продолжает заполнять другие поля, но затем он говорит "нет" и выключает приложение или вкладку, но между тем вы успешно загрузили изображение. Это означает, что вы загрузили изображение, которое нигде не назначено.

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

Другой вариант тоже прост - у вас есть CRON и т.е. каждую неделю, и вы удаляете все неназначенные изображения старше одной недели.

Ответ 2

Существует несколько решений сделать:

  • Первый о пути ресурса:

    • Моделируйте изображение как ресурс самостоятельно:

      • Вложенный в user (/user/: id/image): связь между пользователем и изображением выполняется неявно

      • В корневом пути (/image):

        • Клиент несет ответственность за установление взаимосвязи между изображением и пользователем, или

        • Если контекст безопасности предоставляется с запросом POST, используемым для создания изображения, сервер может неявно устанавливать связь между аутентифицированным пользователем и изображением.

    • Вставить изображение как часть пользователя

  • Второе решение - как представить ресурс изображения:

    • В качестве базы данных JSON, кодированной Base 64
    • Как многопользовательская полезная нагрузка

Это будет мой путь решения:

  • Я обычно предпочитаю дизайн за производительность, если для этого нет веских аргументов. Это делает систему более удобной и более понятной для интеграторов.
  • Итак, моя первая мысль - перейти к представлению ресурса Base64, потому что он позволяет вам хранить все JSON. Если вы выбрали этот вариант, вы можете смоделировать путь к ресурсу по своему усмотрению.
    • Если отношение между пользователем и изображением равно 1 к 1, я бы предпочел бы моделировать изображение как атрибут специально, если оба набора данных обновляются одновременно. В любом другом случае вы можете свободно выбирать модель изображения как атрибут, обновляя его через PUT или PATCH или в качестве отдельного ресурса.
  • Если вы выберете многостраничную полезную нагрузку, я буду вынужден моделировать образ как ресурс, собственный, так что на другие ресурсы, в нашем случае, на ресурс пользователя, не влияет решение использовать двоичное представление для изображение.

Затем возникает вопрос: Есть ли влияние производительности на выбор base64 и multipart?. Мы могли бы подумать, что обмен данными в многостраничном формате должен быть более эффективным. Но в этой статье показано, как мало отличаются оба представления с точки зрения размера.

Мой выбор Base64:

  • Согласованное конструкторское решение
  • Незначительное влияние на производительность.
  • Поскольку браузеры понимают URI данных (кодированные в base64 изображениях), нет необходимости их преобразовывать, если клиент является браузером.
  • Я не буду голосовать за то, чтобы иметь ее как атрибут или автономный ресурс, это зависит от вашей проблемной области (чего я не знаю) и ваших личных предпочтений.

Ответ 3

Ваше второе решение, вероятно, самое правильное. Вы должны использовать спецификацию HTTP и mimetypes так, как они были предназначены, и загрузить файл через multipart/form-data. Что касается обработки отношений, я бы использовал этот процесс (имея в виду, что я знаю ноль о ваших предположениях или системном дизайне):

  • POST до /users, чтобы создать пользовательский объект.
  • POST изображение /images, чтобы вернуть заголовок Location туда, где изображение может быть восстановлено по спецификации HTTP.
  • PATCH до /users/carPhoto и назначьте ему идентификатор фотографии, указанной в заголовке Location на шаге 2.

Ответ 4

Нет простого решения. Каждый путь имеет свои плюсы и минусы. Но канонический путь использует первый вариант: multipart/form-data. Как Руководство по рекомендации W3 говорит

Тип контента "multipart/form-data" должен использоваться для отправки форм, содержащих файлы, данные, отличные от ASCII, и двоичные данные.

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

Используя multipart/form-data, вы придерживаетесь философии REST/http. Вы можете просмотреть ответ на аналогичный вопрос здесь.

Другой вариант, если вы смешиваете альтернативы, вы можете использовать multipart/form-data, но вместо того, чтобы отправлять каждое значение отдельно, вы можете отправить значение с именем полезной нагрузки с полезной нагрузкой json внутри него. (Я пробовал этот подход с использованием ASP.NET WebAPI 2 и отлично работает).