Який код статусу HTTP повернути, якщо кілька дій закінчуються різними статусами?


72

Я будую API, де користувач може попросити сервер виконати кілька дій в одному HTTP-запиті. Результат повертається у вигляді масиву JSON з одним записом на дію.

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

Якби був один запит на дію, я б повертав коди статусу 200, 422 та 500 відповідно. Але тепер, коли є лише один запит, який код статусу я повинен повернути?

Деякі варіанти:

  • Завжди повертайте 200 і дайте більш детальну інформацію в організмі.
  • Можливо, дотримуйтесь наведеного правила лише тоді, коли в запиті є кілька дій?
  • Може повернути 200, якщо всі запити будуть успішними, інакше 500 (або якийсь інший код)?
  • Просто використовуйте один запит на дію та приймайте додаткові накладні витрати.
  • Щось зовсім інше?

3
Ваше запитання змусило мене задуматися про інше: programmers.stackexchange.com/questions/309147/…
AilurusFulgens

7
Трохи пов'язані також: programmers.stackexchange.com/questions/305250/… (див.

4
Якої переваги ви досягаєте, групуючи ці запити разом? Це стосується бізнес-логіки, як транзакції за декількома ресурсами, чи це про ефективність? Або щось інше?
Люк Франкен

5
Гаразд, у цьому випадку я настійно пропоную покращити результати в інших сферах. Спробуйте такі речі, як оптимістичний інтерфейс, запит на створення, кешування тощо, перш ніж втілити цю складність у ваш бізнес-рівень. Чи маєте чітке уявлення про те, де втрачаєте найбільше часу?
Люк Франкен

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

Відповіді:


21

Коротка, пряма відповідь

Оскільки запит говорить про виконання списку завдань (завдання - це той ресурс, про який ми говоримо тут), то якщо група завдань була перенесена до виконання (тобто незалежно від результату виконання), то було б розумним що статус відповіді буде 200 OK. В іншому випадку, якщо виникла проблема, яка перешкоджала б виконанню групи завдань, наприклад, невдача перевірки об'єктів завдання , або якась необхідна служба, наприклад, недоступна, то статус відповіді повинен позначати цю помилку. Раніше, коли виконання завдань починається, розглядаючи, як завдання, які потрібно виконати, перераховані в органі запиту, то я б очікував, що результати виконання будуть вказані в органі відповіді.


Довга філософська відповідь

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

З урахуванням вищесказаного, і без сміливості перетворити цю відповідь на довгий сумнівний посібник, наступна схема URI, яка відповідає підходу до управління ресурсами:

  • /tasks
    • GET перелічує всі завдання, що страждають на сторінку
    • POST додає єдине завдання
  • /tasks/task/[id]
    • GET відповідає на об’єкт стану одного завдання
    • DELETE скасовує / видаляє завдання
  • /tasks/groups
    • GET перелічує всі групи завдань, що страждають на сторінки
    • POST додає групу завдань
  • /tasks/groups/group/[id]
    • GET відповідає станом групи завдань
    • DELETE скасовує / видаляє групу завдань

Ця структура говорить про ресурси, а не про те, що з ними робити. Те, що робиться з ресурсами, - це турбота про іншу службу.

Ще одним важливим моментом, який слід зробити, є те, що бажано не блокувати дуже довго в обробці запиту HTTP. Подібно до UI, HTTP-інтерфейс повинен бути чуйним - у часовому масштабі, який на кілька порядків повільніше (тому що цей шар стосується IO).

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


Деякі приклади використання такої схеми URI

Виконання одного завдання та відстеження прогресу:

  • POST /tasks із завданням виконати
    • GET /tasks/task/[id]поки об'єкт відповіді не completedотримає позитивного значення під час показу поточного стану / прогресу

Виконання одного завдання та очікування його завершення:

  • POST /tasks із завданням виконати
    • GET /tasks/task/[id]?awaitCompletion=trueпоки не completedотримає позитивного значення (швидше за все, має таймаут, через що це слід циклічно)

Виконання групи завдань та відстеження прогресу:

  • POST /tasks/groups з групою завдань для виконання
    • GET /tasks/groups/group/[groupId]поки completedвластивість об'єкта відповіді не має значення, показуючи стан індивідуального завдання (наприклад, 3 завдання, виконані з 5, наприклад)

Запит на виконання групи завдань та очікування її завершення:

  • POST /tasks/groups з групою завдань для виконання
    • GET /tasks/groups/group/[groupId]?awaitCompletion=true до тих пір, поки не відповідає з результатом, який позначає завершення (ймовірно, має час очікування, тому його слід петляти)

Я думаю, що говорити про те, що має сенс семантично - це правильний спосіб підходити до цього. Дякую!
Андерс

2
Я збирався запропонувати цю відповідь, якщо її ще там не було. Це неможливо зробити кілька запитів в одному запиті HTTP. З іншого боку, цілком можливо зробити єдиний запит HTTP, який говорить "виконайте наступні дії, і дайте мені знати, які результати". І ось що тут відбувається.
Мартін Кочанський

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

87

Моє голосування було б розділити ці завдання на окремі запити. Однак якщо турбота занадто велика, то я зіткнувся з кодом відповіді HTTP 207 - Multi-Status

Скопіюйте / вставте з цього посилання:

Відповідь з декількома статусами передає інформацію про кілька ресурсів у ситуаціях, коли може бути доречним кілька кодів статусу. Типовим тілом відповіді за замовчуванням є статус HTTP тексту / xml або програми / xml з кореневим елементом 'мультистатус'. Подальші елементи містять коди статусу серії 200, 300, 400 та 500, сформовані під час виклику методу. Коди статусу 100 серій НЕ БУДЬТЕ записуватися в XML-елемент 'відповіді'.

Хоча «207» використовується як загальний код статусу відповіді, одержувачеві необхідно проконсультуватися зі змістом органу відповіді з багатоступеневою інформацією для отримання додаткової інформації про успішність або невдачу виконання методу. Відповідь МОЖЕ бути використана для успіху, часткового успіху, а також у ситуаціях невдач.


22
207здається, те, чого хоче ОП, але я дуже хочу наголосити, що, мабуть, погана ідея мати такий підхід "багато запитів в одному". Якщо проблема викликає ефективність, то вам слід створити архітектуру горизонтально масштабованих систем у хмарному стилі (це те, на що чудово підходять системи на основі HTTP)
Девід Грінберг,

44
@DavidGrinberg Я більше не могла погодитися. Якщо окремі дії дешеві, то накладні витрати на обробку запиту можуть бути значно дорожчими, ніж сама дія. Ваша пропозиція може призвести до сценаріїв, коли оновлення декількох рядків у базі даних проводиться за допомогою окремої транзакції на рядок, оскільки кожен рядок надсилається як окремий запит. Це не тільки жахливо неефективно, але також означає, що неможливо буде оновити кілька рядків атомним шляхом, якщо це буде потрібно. Горизонтальне масштабування важливе, але воно не замінює ефективні конструкції.
kasperd

4
Добре сказано і вказується на типову проблему впровадження REST API, що робиться людьми, необізнаними щодо реальних потреб бізнесу, таких як продуктивність та / або атомність. Ось чому, наприклад, специфікація OData REST має пакетний формат для декількох операцій за один виклик - у цьому є реальна потреба.
TomTom

8
@TomTom, ОП не хоче атомності. Це було б набагато простіше розробити, оскільки існує лише один стан атомної операції. Крім того, специфікація HTTP дозволяє проводити пакетні операції для продуктивності за допомогою мультиплексування HTTP / 2 (природно, підтримка HTTP / 2 - інша справа, але специфікація дозволяє це).
Пол Дрейпер

2
@David Раніше працював над деякими проблемами HPC, на мій досвід, вартість відправлення одного байту майже однакова, ніж відправка тисячі (різні носії передачі, безумовно, мають різні накладні витрати, але це рідко краще, ніж це). Тож якщо продуктивність викликає занепокоєння, я не бачу, як надсилання декількох запитів не матиме великих витрат. Тепер, якщо ви могли б мультиплексувати кілька запитів через одне і те ж з'єднання, ця проблема зникне, але, як я розумію, це лише варіант з HTTP / 2, а підтримка для нього досить обмежена. Або я щось пропускаю?
Voo

24

Хоча багатостандартність - це варіант, я б повернув 200 (Все добре), якщо всі запити були успішними та помилка (500 чи, можливо, 207) в іншому випадку.

Стандартний корпус зазвичай повинен бути 200 - все працює. І клієнти повинні лише перевірити це. І лише якщо стався випадок помилки, ви можете повернути 500 (або 207). Я думаю, що 207 є правильним вибором у випадку принаймні однієї помилки, але якщо ви бачите весь пакет як одну транзакцію, ви також можете надіслати 500. - Клієнт захоче інтерпретувати повідомлення про помилку в будь-якому випадку.

Чому б не завжди надсилати 207? - Тому що стандартні корпуси повинні бути простими та стандартними. Хоча виняткові випадки можуть бути винятковими. Клієнт повинен лише прочитати орган реагування та прийняти подальші складні рішення, якщо цього вимагає виняткова ситуація.


6
Я не зовсім згоден. Якщо підзапроси 1 і 3 вдалися, ви отримуєте комбінований ресурс і в будь-якому випадку доведеться перевірити комбіновану відповідь. У вас є ще один випадок, який слід розглянути. Якщо відповідь = 200 або підвідповідь 1 = 200, тоді запит 1 вдався. Якщо відповідь = 200 або субвідповідь 2 = 200, тоді запит 2 вдасться і так далі, а не просто тестувати додатковий відповідь.
gnasher729

1
@ gnasher729 це дійсно залежить від програми. Я уявляю керовану користувачем дію, яка просто перейде до наступного кроку з (все нормально), коли всі запити будуть успішними. - Якщо щось пішло не так (глобальний стан <= 200), вам доведеться відображати детальні помилки та змінювати робочий процес, і вам потрібна лише одна перевірка для кожного підзапити, оскільки ви перебуваєте у функції "handleMixedState", а не у функції "handleAllOk" .
Фалько

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

Я також, швидше за все, надішлю 500, якщо виникла загальна проблема (наприклад, база даних вниз), тому сервер навіть не пробує окремі запити, а може просто повернути загальну помилку. - Оскільки для користувача є 3 різні результати 1. все добре, 2. загальна проблема, нічого не працює, 3. деякі запити не вдалося. -> Що зазвичай призводить до зовсім іншого програмного потоку.
Фалько

1
Гаразд, такий підхід був би: 207 = індивідуальний статус для кожного запиту. Все інше: статус, що повертається, стосується кожного запиту. Має сенс для 200, 401, ≥ 500.
gnasher729

13

Одним із варіантів було б завжди повертати код статусу 200, а потім повертати конкретні помилки у вашому документі JSON. Саме так розроблені деякі API (вони завжди повертають код статусу 200 і розсилають помилку в тілі). Докладніше про різні підходи див. У розділі http://archive.oreilly.com/pub/post/restful_error_handling.html


2
У цьому випадку мені подобається ідея використання 200вказувати на все добре, запит отримано та був дійсним , а потім скористайтеся JSON, щоб надати детальну інформацію про те, що сталося в цьому запиті (тобто результат транзакцій).
rickcnagy

4

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

Проблема з спробою надіслати кілька дій в одному запиті полягає саме в тому, що кожна дія повинна мати власний "статус"

Дивлячись на імпорт CSV (я не дуже знаю, про що йдеться в ОП, але це проста версія). Розмістіть CSV та отримайте 206. Потім пізніше CSV можна імпортувати, і ви можете отримати статус імпорту за допомогою GET (200) проти URL-адреси, що показує помилки в рядку.

POST /imports/ -> 206
GET  /imports/1 -> 200
GET  /imports/1/errors -> 200 -> Has a list of errors

Цей же візерунок може бути застосований до багатьох операцій партії

POST /operations/ -> 206
GET  /operations/1 -> 200
GET  /operations/1/errors -> 200 - > Has a list of errors.

Код, який обробляє POST, повинен лише перевірити, чи формат даних операцій є дійсним. Тоді в якийсь пізній час операції можуть бути виконані. Назад, наземний робітник, так що ви можете масштабувати простіше, наприклад. Тоді ви можете перевірити стан операцій коли завгодно. Ви можете використовувати опитування, зворотний дзвінок, потоки чи будь-що інше, щоб вирішити необхідність знати, коли набір операцій завершено.


2

Тут уже багато хороших відповідей, але один аспект відсутній:

Який контракт очікують ваші клієнти?

HTTP-коди повернення повинні вказувати на принаймні відмінність успіху / невдачі і, таким чином, грати роль "винятку бідної людини". Тоді 200 означає "договір повністю виконаний", а 4xx або 5xx вказують на невиконання.

Наївно, я б очікував, що у договорі вашого запиту на кілька дій буде "виконувати всі мої завдання", і якщо одна з них не вдасться, то запит не був (повністю) успішним. Як правило, я як клієнт розумів 200 як "все нормально", а коди з сімей 400 і 500 змушують мене думати про наслідки (часткової) відмови. Отже, використовуйте 200 для "всіх виконаних завдань" і 500 плюс описову відповідь у разі часткової відмови.

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

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

Кінцевий важливий аспект: як ви повідомляєте про своє контрактне рішення своїм клієнтам? Наприклад, у Java, я б використовував назви методів типу "doAll ()" або "tryToDoAll ()". У HTTP ви можете відповідно називати URL-адреси кінцевих точок, сподіваючись, що ваші клієнтські розробники побачать, прочитали та зрозуміли іменування (я не став би на це ставити). Ще одна причина вибору договору як мінімум сюрпризу.


0

Відповідь:

Просто використовуйте один запит на дію та приймайте додаткові накладні витрати.

Код статусу описує стан однієї операції. Тому має сенс проводити одну операцію на запит.

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

HTTP / 1.1 та HTTP / 2 значно знизили накладні витрати HTTP-запитів. Я вважаю, що існує дуже мало ситуацій, коли доцільне отримання незалежних запитів доцільно.


Це сказав:

(1) Ви можете зробити кілька модифікацій за допомогою запиту PATCH ( RFC 5789 ). Однак це вимагає, щоб зміни не були незалежними; їх застосовують атомно (все або нічого).

(2) Інші вказали на код 207 багатозначного статусу. Однак це визначено лише для WebDAV ( RFC 4918 ), розширення HTTP.

Код статусу 207 (Multi-Status) забезпечує статус для декількох незалежних операцій (для отримання додаткової інформації див. Розділ 13).

...

Відповідь з декількома статусами передає інформацію про кілька ресурсів у ситуаціях, коли може бути доречним кілька кодів статусу. Елемент кореня [XML] 'мультистатус' містить нульовий або більше елементів 'відповіді' у будь-якому порядку, кожен з інформацією про окремий ресурс.

Відповідь XML 207 WebDAV була б такою ж дивною, як і качка в API не WebDAV. Не робіть цього.


1
Ви по суті стверджуєте, що у @Anders є проблема XY . Ви можете мати рацію, але, на жаль, це означає, що ви насправді не відповіли на запитання, яке він задав (який код статусу використовувати для запиту на багато дій).
Азуарон

2
@Azuaron, який пояс найкраще працює для побиття дітей? Я думаю, що "Ні / А" - це відповідь, яку можна допустити. Крім того, Андрес включив у свій список ідей кілька запитів. Я щиро підтримував цей варіант.
Пол Дрейпер

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

1
@Azuaron Я абсолютно думаю, що це правильна відповідь. Якщо я роблю це все неправильно, я хочу, щоб хтось так сказав, а не давав мені інструкцій, як найкраще з’їхати зі скелі.
Андерс

1
Ніщо не забороняє надсилати JSON у відповіді 207, якщо заголовок Content-Type встановлений правильно і відповідає тому, що запитував клієнт (Accept header).
долмен

0

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

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


2
Я припускаю, що якщо запит має бути атомним, він не став би це питання.
Енді

@Andy Можливо, але ви не можете припустити, що він вважає всі наслідки такого дизайну.
Дін

Запит не повинен бути атомним - наприклад, якщо номер 2 не вдається, зміни, внесені №1, повинні зберігатись. Тож упакувати все в одну транзакцію - це не варіант.
Андерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.