Код состояния HTTP для «Обработки еще»

47

Я создаю RESTful API, который поддерживает постановку в очередь длительных задач для возможной обработки.

Типичный рабочий процесс для этого API:

  1. Пользователь заполняет форму
  2. Клиент отправляет данные в API
  3. API возвращает 202 Принято
  4. Клиент перенаправляет пользователя на уникальный URL для этого запроса ( /results/{request_id})
  5. ~ ~ В конце концов,
  6. Клиент снова заходит на URL и видит результаты на этой странице.

Моя проблема в шаге 6. Каждый раз, когда пользователь посещает страницу, я отправляю запрос в мой API ( GET /api/results/{request_id}). В идеале, задача будет выполнена к настоящему времени, и я бы вернул 200 OK с результатами их задачи.

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

Какой мой лучший вариант для кода состояния, чтобы указать, что:

  • этот запрос существует,
  • это еще не сделано,
  • но это также не подвело.

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

Возможно, имеет смысл вернуть 202, поскольку здесь это не имело бы никакого другого значения: это GETзапрос, так что ничто не может быть «принято». Это был бы разумный выбор?

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

200 OK

{
    status: "complete",
    data: {
        foo: "123"
    }
}

...или же...

200 OK

{
    status: "pending"
}

Затем на стороне клиента, я бы (вздыхаю) switchна , response.data.statusчтобы определить , был ли завершен запрос.

Это то, что я должен делать? Или есть лучшая альтернатива? Мне так кажется, что Web 1.0.

Мэтью Хауген
источник
1
Разве коды 1xx не созданы именно для этой цели?
Энди
@ Энди, я смотрел на 102, но это для материала WebDAV. Кроме того, нет ... Они в основном для транзитных коммуникаций. Полезно при переходе на веб-сокеты и тому подобное.
Мэтью Хауген
О какой задержке ты говоришь? 10 секунд? Или 6 часов? Если задержки невелики и обычно связаны с одним и тем же посещением браузера, вы можете выполнять длинные опросы или веб-сокеты, а не периодические опросы.
GrandmasterB
@GrandmasterB Потенциально, часы. Я не отвечаю за саму обработку задания, поэтому у меня нет действительно хорошей оценки, но это будет какое-то время. В противном случае я бы просто оставил первый POSTзапрос открытым. Основная проблема с длинными опросами или веб-сокетами заключается в том, что пользователь может закрыть браузер и вернуться обратно. Я мог бы открыть их снова в это время (и это то, что я делаю), но кажется более чистым иметь один API-интерфейс для вызова, прежде чем я открою эти сокеты, так как возникает крайний случай возникновения этой проблемы.
Мэтью Хауген

Ответы:

48

HTTP 202 Принят (HTTP / 1.1)

Вы ищете HTTP 202 Acceptedстатус. См. RFC 2616 :

Запрос был принят к обработке, но обработка не была завершена.

Обработка HTTP 102 (WebDAV)

RFC 2518 предлагает использовать HTTP 102 Processing:

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

но у этого есть предостережение:

Сервер ДОЛЖЕН отправить окончательный ответ после завершения запроса.

Я не уверен, как интерпретировать последнее предложение. Должен ли сервер избегать отправки чего-либо во время обработки и отвечать только после завершения? Или это только заставляет завершить ответ только тогда, когда обработка прекращается? Это может быть полезно, если вы хотите сообщить о прогрессе. Отправьте HTTP 102 и очистите ответный байт за байтом (или построчно).

Например, для длинного, но линейного процесса, вы можете отправить сотню точек после каждого символа. Если клиентская сторона (например, приложение JavaScript) знает, что она должна ожидать ровно 100 символов, она может сопоставить ее с индикатором выполнения, который будет показан пользователю.

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

Проблемы с прогрессивной промывкой

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

Лучшим подходом является ответить HTTP 202 Acceptedи либо позволить пользователю вернуться к вам позже, чтобы определить, завершилась ли обработка (например, путем многократного вызова определенного URI, например, /process/resultкоторый будет отвечать HTTP 404 Not Found или HTTP 409 Conflict до процесса). завершается и результат готов), или уведомите пользователя, когда обработка будет завершена, если вы сможете, например, перезвонить клиенту через службу очереди сообщений ( пример ) или WebSockets.

Практический пример

Представьте себе веб-сервис, который конвертирует видео. Точка входа:

POST /video/convert

который берет видеофайл из HTTP-запроса и делает с ним магию. Давайте представим, что магия требует много ресурсов процессора, поэтому ее нельзя сделать в режиме реального времени во время передачи запроса. Это означает, что после передачи файла сервер ответит сообщением HTTP 202 AcceptedJSON с некоторым содержанием, что означает: «Да, я получил ваше видео, и я над ним работаю; он будет готов где-то в будущем и будет доступен через ID 123. »

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

GET /video/download/123

что приводит к HTTP 200.

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

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

В этом случае вы можете использовать другую конечную точку:

GET /video/status/123

который привел бы ответ, подобный этому:

HTTP 200
{
    "id": 123,
    "status": "queued",
    "priority": 2,
    "progress-percent": 0,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

Повторное выполнение запроса покажет прогресс до тех пор, пока:

HTTP 200
{
    "id": 123,
    "status": "done",
    "progress-percent": 100,
    "submitted-utc-time": "2016-04-19T13:59:22"
}

Крайне важно провести различие между этими тремя типами запросов:

  • POST /video/convertставит задачу в очередь Его следует вызывать только один раз: повторный вызов поставит в очередь дополнительную задачу.
  • GET /video/download/123касается результата операции: ресурсом является видео. Обработка, то есть то, что происходило под капотом для подготовки фактического результата до запроса и независимо от запроса, здесь не имеет значения. Его можно вызвать один или несколько раз.
  • GET /video/status/123касается обработки как таковой . Это ничего не ставит в очередь. Это не заботится о получающемся видео. Ресурс является самой обработкой. Его можно вызвать один или несколько раз.
Арсений Мурзенко
источник
1
Имеет ли 202 смысл в ответ на GET, хотя? Это, безусловно, правильный выбор для начального POST, поэтому я использую его. Но это кажется семантически подозрительным для того, GETчтобы сказать «принято», когда он не принял ничего из этого конкретного запроса.
Мэтью Хауген
2
@MainMa Как я уже сказал, я POSTподнимаю задание на очередь, затем я получаю GETрезультаты, возможно, после того, как клиент закрыл сеанс. 404 является то , что я рассмотрел , как хорошо, но это кажется неправильным, так как запрос будет найден, он просто не был завершен. Это указывало бы на то, что работа в очереди не была найдена, что является совсем другой ситуацией.
Мэтью Хауген
1
@ MatthewHaugen: когда вы делаете GETчасть, не думайте о ней как о неполном запросе , а как о запросе на получение результата операции . Например, если я скажу вам преобразовать видео, и это займет у вас пять минут, запрос на преобразованное видео через две минуты должен привести к HTTP 404, потому что видео просто еще нет. С другой стороны, запрос на выполнение самой операции, вероятно, приведет к HTTP 200, содержащему количество преобразованных байтов, скорость и т. Д.
Арсений Мурзенко,
5
Код состояния HTTP для ресурса, который еще не доступен, предлагает вернуть ответ на конфликт 409 («Запрос не может быть выполнен из-за конфликта с текущим состоянием ресурса.»), А не ответ 404, если ресурс не не существует, потому что он находится в процессе создания.
Брайан,
1
@ Брайан Ваш комментарий даст разумный ответ на этот вопрос. Хотя я бы тогда ответил: «[t] его код разрешен только в ситуациях, когда ожидается, что пользователь сможет разрешить конфликт и повторно отправить запрос», что не совсем верно в моем случае, но это кажется менее неправильно, чем "не найдено". Часть меня склоняется к 409 с прикрепленным заголовком Retry-After. Единственная проблема заключается в том, что кажется странным возвращать 409 за GET, но я могу жить с этой странностью - вряд ли это будет определено иначе в будущем.
Мэтью Хауген
5

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

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

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

Помните, что коды состояния HTTP соответствуют передаче состояния между клиентом и сервером рассматриваемого ресурса независимо от каких-либо подробностей этого ресурса. Когда вы GETресурс, ваш клиент запрашивает у сервера представление ресурса в текущем состоянии, в котором он находится. Это может быть изображение птицы, это может быть документ Word, это может быть текущая внешняя температура. Протокол HTTP не волнует. Код состояния HTTP соответствует результату этого запроса. Передал ли POSTклиент с сервера ресурс на сервер, где сервер дал ему URL-адрес, который клиент может просмотреть? Да? Тогда это 201 Createdответ.

Ресурсом может быть бронирование авиабилетов, которое в настоящее время находится в состоянии «подлежит проверке». Или это может быть заказ на покупку продукта, который находится в «утвержденном» состоянии. Эти состояния зависят от домена, а не от протокола HTTP. Протокол HTTP имеет дело с передачей ресурсов между клиентом и сервером.

Суть REST и HTTP заключается в том, что протоколы не касаются деталей ресурсов. Это сделано специально, оно не касается проблем, связанных с доменом, поэтому его можно использовать, не зная ничего о проблемах, связанных с доменом. Вы не интерпретируете, что означают коды статуса HTTP в каждом отдельном контексте (система бронирования авиабилетов, система обработки изображений, система видеонаблюдения и т. Д.).

Специфичные для домена вещи предназначены для клиента и сервера, чтобы выяснить между собой на основе Content Typeресурса. Протокол HTTP не зависит от этого.

Что касается того, как клиент узнает, что ресурс запроса изменил состояние, то опрос - это ваша лучшая ставка, поскольку он сохраняет контроль над клиентом и не предполагает непрерывного соединения. Особенно, если это будет потенциально часами, пока состояние не изменится. Даже если вы сказали, что, черт возьми, с REST, вы просто будете держать соединение открытым, держать его открытым в течение нескольких часов и предполагать, что ничего не пойдет не так, было бы плохой идеей. Что делать, если пользователь закрывает клиента или сеть отключается. Если гранулярность составляет часы, клиент может просто запрашивать состояние каждые несколько минут, пока Запрос не изменится с «ожидающий» на «выполненный».

Надеюсь, что это помогает уточнить вещи

Кормак Мулхолл
источник
«Передал ли POST от клиента к серверу ресурс на сервер, где сервер затем дал ему URL-адрес, который клиент может просмотреть? Да? Тогда это ответ 201 Создан». 202 Принято также приемлемо в качестве ответа на это, если сервер не может действовать немедленно, чтобы обработать ресурс, что и делает OP.
Энди
1
Дело в том, что сервер действует немедленно. Он сразу создает ресурс с URL. Это просто состояние ресурса "Ожидание" (или что-то). Это состояние бизнес-домена. Что касается протокола HTTP, сервер действовал, как только он создал ресурс и дал клиенту URL-адрес ресурса. Вы можете получить этот ресурс. Сам запрос POST не ожидается. Это то, что я имею в виду, разделяя две разные концептуальные области. Если бы клиент отправлял пожар и забыл, что запрос POST не выполнялся в течение нескольких часов, тогда будет применяться 202.
Кормак Малхолл,
Никого не волнует, существует ли URL, но вы не можете получить данные, которые представляет ресурс, потому что он все еще обрабатывается. Также можно НЕ создавать URL, пока он не будет использован для получения видео.
Энди
Ресурс создан, он просто находится в состоянии «в ожидании». Это само по себе соответствующие данные. В какой-то момент в будущем сервер может изменить состояние ресурсов на «завершено» (или «не выполнено»), но это понятие отличается от конкретной задачи HTTP-домена «создать ресурс». Ожидание может быть совершенно допустимым состоянием для ресурса «Запрос», и клиент, очевидно, хочет знать, что сервер создал ресурс в этом состоянии, поскольку он переходит от запроса к серверу о создании ресурса, чтобы узнать, опрашивает ли его если состояние изменилось.
Кормак Малхолл
4

Я нашел предложения из этого блога разумными: ОТДЫХ и длительные работы .

Обобщить:

  1. Сервер возвращает код «202 Принят» с заголовком «Местоположение», установленным в URI для клиента, чтобы проверить состояние, например «/ queue / 12345».
  2. До завершения обработки сервер отвечает на запросы о состоянии «200 OK» и некоторые данные ответа показывают состояние задания.
  3. После завершения обработки сервер отвечает на запросы статуса с «303 См. Другое» и «Местоположение», содержащим URI для окончательного результата.
Сяньмин Ху
источник
2

Код состояния HTTP для ресурса, который еще не доступен, предлагает вернуть ответ на конфликт 409, а не ответ 404, в случае, если ресурс не существует, поскольку он находится в процессе генерации.

Из спецификации w3 :

10.4.10 409 Конфликт

Запрос не может быть выполнен из-за конфликта с текущим состоянием ресурса. Этот код разрешен только в ситуациях, когда ожидается, что пользователь сможет разрешить конфликт и повторно отправить запрос. Тело ответа ДОЛЖНО включать достаточно

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

Конфликты чаще всего возникают в ответ на запрос PUT. Например, если использовалось управление версиями, а объект PUT включал изменения в ресурсе, которые конфликтуют с ресурсами, сделанными ранее (сторонним) запросом, сервер может использовать ответ 409, чтобы указать, что он не может выполнить запрос , В этом случае объект ответа, скорее всего, будет содержать список различий между двумя версиями в формате, определяемом типом содержимого ответа.

Это немного неловко, поскольку код 409 «разрешен только в ситуациях, когда ожидается, что пользователь сможет разрешить конфликт и повторно отправить запрос». Я предлагаю, чтобы тело ответа содержало сообщение (возможно, в каком-то формате ответа, соответствующем остальной части вашего API), например: «Этот ресурс в настоящее время создается. Он был инициирован в [ВРЕМЯ] и, по оценкам, завершается в [ВРЕМЯ]. Пожалуйста, Попробуйте позже."

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

Брайан
источник
Похоже на отрезок f, для которого действительно предназначен 409, что является ответом на пут.
Энди
@ Энди: Правда, но так же, как и любая другая альтернатива. Например, 202 действительно должен быть ответом на запрос, который инициировал обработку, а не запросом, который запросил результаты обработки. На самом деле, самый специфицированный ответ - 404, так как ресурс не был найден (потому что он еще не существовал). Ничто не мешает API предоставить соответствующие данные API в ответе 404. Напоминаю, что ответы 4хх / 5хх обычно раздражают; некоторые языки будут вызывать исключение, а не просто предоставлять другой код состояния.
Брайан,
2
Нет, особенно последние несколько абзацев ответа MainMa. Отдельные конечные точки, чтобы проверить статус запроса и получить само видео. Статус не совпадает с ресурсом видео и должен быть адресуемым сам по себе.
Энди