REST API - обробка файлів (тобто зображень) - найкращі практики


198

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

Примітка: а також я говорю про випадок використання, коли суб'єкт (користувач) може мати декілька файлів (carPhoto, licencePhoto), а також мати інші властивості (ім'я, електронна пошта ...), але коли ви створюєте нового користувача, ви не Не надсилаю ці зображення, вони додаються після реєстрації.


Мені відомі рішення, але кожне з них має деякі вади

1. Використовуйте дані з кількох частин / форм замість JSON

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

Мінуси : Це вже не JSON, що набагато простіше тестувати, налагоджувати і т.д.

2. Дозволити оновлювати окремі файли

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

добре : Все (крім самого завантаження файлу) залишається в JSON, його легко перевірити і налагодити (ви можете ввійти в систему повних запитів JSON, не боячись їх довжини)

Мінуси : це не інтуїтивно зрозуміло, ви не /users/4/carPhotoможете надсилати POST або PUT всі змінні сутності одразу, а також ця адреса може розглядатися як збірка (стандартний випадок використання для API REST виглядає таким чином /users/4/shipments). Зазвичай ви не можете (і не бажаєте) GET / PUT кожної змінної сутності, наприклад, users / 4 / name. Ви можете отримати ім'я за допомогою GET та змінити його на PUT у користувачів / 4. Якщо після ідентифікатора є щось, зазвичай це інша колекція, наприклад користувачі / 4 / огляди

3. Використовуйте Base64

Відправте його як JSON, але кодуйте файли за допомогою Base64.

добре : Те саме, що і перше рішення, це якнайшвидше обслуговування.

мінуси : Ще раз тестування та налагодження набагато гірше (тіло може мати мегабайти даних), збільшується розмір, а також час обробки обох - клієнта та сервера


Я дуже хотів би використовувати рішення «ні». 2, але це має свої мінуси ... Хто-небудь може мені краще зрозуміти рішення "що найкраще"?

Моя мета - забезпечити RESTful сервіси з якомога більшою кількістю стандартів, в той час як я хочу зробити це максимально просто.


Ви можете також знайти цей: stackoverflow.com/questions/4083702 / ...
Маркон

5
Я знаю, що ця тема стара, але останнім часом ми стикалися з цим питанням. Найкращий підхід, який ми маємо, схожий на ваш номер 2. Ми завантажуємо файли прямо в API і потім додаємо ці файли в модель. За допомогою цього сценарію ви можете створювати зображення для завантаження до, після або на тій же сторінці, що і форма, насправді не має значення. Гарна дискусія!
Тіаго Матос

2
@TiagoMatos - так, я описав це в одній відповіді, яку я нещодавно прийняв
libik

6
Дякуємо, що задали це запитання.
Зухайер Тахір

1
"також ця адреса / користувачів / 4 / carPhoto може розглядатися як колекція" - ні, це не схожа на колекцію і не обов'язково вважатиметься такою. Зовсім прекрасно мати відношення до ресурсу, який є не колекцією, а єдиним ресурсом.
B12Toaster

Відповіді:


156

ОП тут (я відповідаю на це запитання через два роки. Пост, зроблений Даніелем Серекедо, був не поганий у той час, але веб-служби розвиваються дуже швидко)

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

Якщо у вас є спеціальна кінцева точка для зображень, вона дає вам набагато більше влади над обробкою цих зображень.

У нас однаковий API REST (Node.js) для обох - мобільних додатків (iOS / android) та frontend (за допомогою React). Це 2017 рік, тому ви не хочете зберігати зображення на локальному рівні, ви хочете завантажувати їх у якийсь хмарний сховище (хмара Google, s3, хмаринка, ...), тому ви хочете загальну обробку над ними.

Наш типовий потік полягає в тому, що як тільки ви вибираєте зображення, воно починає завантажуватися на фоні (зазвичай POST на / кінцевій точці зображень), повертаючи вам ідентифікатор після завантаження. Це дійсно зручно для користувача, оскільки користувач вибирає зображення, а потім продовжує виконувати деякі інші поля (наприклад, адресу, ім'я, ...), тому, коли натискає кнопку "відправити", зображення зазвичай вже завантажується. Він не чекає і дивиться на екран, кажучи "завантажуючи ...".

Те саме стосується отримання зображень. Особливо завдяки мобільним телефонам та обмеженим мобільним даним, ви не хочете надсилати оригінальні зображення, ви хочете надсилати зміни розмірів, щоб вони не займали стільки пропускної здатності (а щоб зробити ваші мобільні додатки швидшими, ви часто не хочете щоб змінити його розмір, ви хочете, щоб зображення ідеально вписувалося у ваш погляд). З цієї причини хороші програми використовують щось на зразок хмарного (або у нас є власний сервер зображень для зміни розміру).

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


Мінуси: Єдині «мінуси», про які слід подумати, - це «не призначені зображення». Користувач вибирає зображення та продовжує заповнювати інші поля, але потім він каже "не" та вимкніть додаток або вкладку, але тим часом ви успішно завантажили зображення. Це означає, що ви завантажили зображення, яке ніде не призначено.

Існує кілька способів вирішити це. Найпростіший з них - "Мені все одно", що є релевантним, якщо це відбувається не дуже часто, або у вас навіть є бажання зберігати кожен надісланий вами образ (з будь-якої причини), і ви не хочете жодного видалення.

Ще один дуже простий - у вас CRON, тобто щотижня, і ви видаляєте всі непризначені зображення, старші ніж на тиждень.


Що станеться, якщо [як тільки ви виберете зображення, воно почне завантажуватись у фоновому режимі (як правило, POST на / кінцевій точці зображень), повертаючи вам ідентифікатор після завантаження], коли запит не вдався через підключення до Інтернету? Чи підкажете користувачеві, поки він продовжує діяти з деякими іншими полями (наприклад, адресою, ім'ям, ...)? Б'юсь об заклад, ви все одно будете чекати, поки користувач натисне кнопку "надіслати" і повторіть ваш запит, змушуйте їх чекати під час перегляду екрана із записом "завантаження ...".
Адроміль Балай

5
@AdromilBalais - API RESTful не має статусу, тому він нічого не робить (Сервер не відстежує стан споживача). Споживач послуги (тобто веб-сторінка або мобільний пристрій) несе відповідальність за обробку невдалих запитів, тому споживач повинен вирішити, чи викликає він негайно той самий запит після того, як цей не вдався або що робити (тобто показати "Не вдалось завантажити завантаження зображення - хочете спробувати ще раз ")
libik

2
Дуже інформативна і освічуюча відповідь. Дякую за відповідь.
Зухайер Тахір

Це насправді не вирішує початкову проблему. Це просто говорить про "використання хмарної служби"
Мартін Музатко

3
@MartinMuzatko - це робить, він вибирає другий варіант і розповідає, як слід ним користуватися і чому. Якщо ви маєте на увазі «але це не ідеальний варіант, який дозволяє надсилати все за один запит і без наслідків» - так, такого рішення, на жаль, немає.
libik

104

Існує кілька рішень :

  1. Перший про шлях до ресурсу :

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

      • Вкладене в user (/ user /: id / image): взаємозв'язок між користувачем та зображенням робиться неявно

      • У кореневому шляху (/ зображення):

        • Клієнт несе відповідальність за встановлення взаємозв'язку між зображенням і користувачем, або;

        • Якщо контекст безпеки надається запитом POST, який використовується для створення зображення, сервер може неявно встановити зв’язок між автентифікованим користувачем та зображенням.

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

  2. Друге рішення - про те, як представити ресурс зображення :

    • Так як Base 64 кодує корисну навантаження JSON
    • Як багаточастинне корисне навантаження

Це було б моїм рішенням:

  • Я, як правило, віддаю перевагу дизайну над продуктивністю, якщо для цього немає вагомих підстав. Це робить систему більш рентабельною і інтегратори можуть бути легше зрозуміти.
  • Тож моя перша думка полягає у пошуку Base64 подання зображувального ресурсу, оскільки він дозволяє зберігати все JSON. Якщо ви вибрали цю опцію, ви зможете моделювати шлях до ресурсу так, як вам подобається.
    • Якщо взаємозв'язок між користувачем та зображенням становить 1 до 1, я б вважав за краще моделювати зображення як атрибут, якщо обидва набори даних оновлюються одночасно. У будь-якому іншому випадку ви можете вільно вибрати модель моделювання або як атрибут, оновивши його за допомогою PUT або PATCH, або як окремий ресурс.
  • Якщо ви вибрали багаточастинні корисні навантаження, я б змушений моделювати зображення як ресурс як власний, так що рішення про використання двійкового представлення для зображення не впливає на інші ресурси, у нашому випадку - користувацький ресурс.

Тоді виникає питання: чи впливає на вибір продуктивність base64 vs multipart? . Ми можемо подумати, що обмін даними у багатопартійному форматі має бути ефективнішим. Але ця стаття показує, наскільки мало різняться обидва уявлення за розміром.

Мій вибір Base64:

  • Послідовне дизайнерське рішення
  • Незначний вплив на продуктивність
  • Оскільки веб-переглядачі розуміють URI даних (кодовані base64 зображення), немає необхідності їх перетворювати, якщо клієнт - браузер
  • Я не буду голосувати за те, чи потрібно мати його як атрибут чи окремий ресурс, це залежить від вашої проблемної області (яку я не знаю) та ваших особистих переваг.

3
Чи не можемо ми кодувати дані за допомогою інших протоколів серіалізації, таких як protobuf тощо? В основному я намагаюся зрозуміти, чи існують інші простіші способи вирішити питання щодо збільшення розміру та часу обробки, що поставляється з кодуванням base64.
Енді Дуфресне

1
Дуже захоплива відповідь. дякую за покроковий підхід. Це змусило мене зрозуміти ваші моменти набагато краще.
Зухайер Тахір

13

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

  1. POSTщоб /usersстворити сутність користувача.
  2. POSTзображення /images, переконуючись повернути Locationзаголовок туди, де зображення можна отримати за специфікацією HTTP.
  3. PATCHщоб /users/carPhotoі привласнити йому ідентифікатор фотографії , наведеної в Locationзаголовку кроку 2.

1
У мене немає прямого контролю над тим, "як клієнт буде використовувати API" ... Проблема в тому, що "мертві" фотографії, які не зафіксовані на деяких ресурсах ...
libik

4
Зазвичай, коли ви обираєте другий варіант, бажано завантажувати спочатку медіа-елемент і повертати медіа-ідентифікатор клієнтові, тоді клієнт може надсилати дані сутності, включаючи ідентифікатор медіа, такий підхід дозволяє уникнути розбиття сутностей про невідповідність інформації.
Kellerman Rivero

2

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

Тип вмісту "багаточастинні / форма-дані" слід використовувати для подання форм, що містять файли, не-ASCII дані та двійкові дані.

Насправді ми не надсилаємо форми, але неявний принцип все ще діє. Використовувати base64 як двійкове представлення, невірно, оскільки ви використовуєте неправильний інструмент для досягнення своєї мети, з іншого боку, другий варіант змушує ваших клієнтів API робити більше роботи, щоб споживати вашу послугу API. Вам слід зробити важку роботу на сервері, щоб поставити простий у споживанні API. Перший варіант налагодити непросто, але коли ви це зробите, він, ймовірно, ніколи не змінюється.

Використовуючи, multipart/form-dataви дотримуєтесь філософії REST / http. Відповідь на подібне запитання ви можете переглянути тут .

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


2
Посібник з рекомендацій щодо W3 тут не має значення, оскільки знаходиться в контексті специфікації HTML 4.
Йоганн

1
Дуже вірно .... "не ASCII-дані" вимагають багаточастинних? У двадцять першому столітті? У світі UTF-8? Звичайно, це смішна рекомендація на сьогоднішній день. Я навіть здивований, що існував у HTML 4 дні, але іноді світ Інтернет-інфраструктури рухається дуже повільно.
Рей Тоал
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.