Вызов серверного метода для ресурса в режиме RESTful


146

Имейте в виду, что у меня есть элементарное понимание REST. Допустим, у меня есть этот URL:

http://api.animals.com/v1/dogs/1/

А теперь я хочу, чтобы сервер заставлял собаку лаять. Только сервер знает, как это сделать. Допустим, я хочу, чтобы он выполнял задание CRON, которое заставляет собаку лаять каждые 10 минут до конца вечности. Как выглядит этот звонок? Я как бы хочу это сделать:

URL-запрос:

ACTION http://api.animals.com/v1/dogs/1/

В теле запроса:

{"action":"bark"}

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

РЕДАКТИРОВАТЬ ДЛЯ УТОЧНЕНИЯ

Еще несколько пояснений по поводу того, что делает метод «коры». Вот несколько вариантов, которые могут привести к вызовам API с другой структурой:

  1. bark просто отправляет электронное письмо на dog.email и ничего не записывает.
  2. bark отправляет электронное письмо на адрес dog.email, и значение dog.barkCount увеличивается на 1.
  3. bark создает новую запись "лая" с записью bark.timestamp, когда возник лай. Он также увеличивает dog.barkCount на 1.
  4. bark запускает системную команду для получения последней версии кода собаки с Github. Затем он отправляет сообщение dog.owner о том, что новый код собаки находится в разработке.

14
Интересно, что добавление награды, похоже, привело к худшим ответам, чем у вас изначально ;-) При оценке ответов помните, что: 1) Спецификации для HTTP-глаголов исключают любой выбор, кроме POST. 2) REST не имеет ничего общего со структурой URL-адресов - это общий список ограничений (без сохранения состояния, кэшируемый, многоуровневый, унифицированный интерфейс и т. Д.), Которые дают преимущества (масштабируемость, надежность, видимость и т. Д.). 3) Текущая практика (например, использование POST в спецификациях RPC) превосходит дефиниционалистов, составляющих свои собственные правила API. 4) REST требует унифицированного интерфейса (согласно спецификации HTTP).
Raymond Hettinger

@ Кирк, что ты думаешь о новых ответах? Есть ли что-нибудь, что вы все еще хотели бы узнать, но не упоминалось ни в одном из них? Я был бы более чем счастлив снова отредактировать свой ответ, если он может быть более полезным.
Jordan

@RaymondHettinger PATCHможет подойти . Я объясняю почему ближе к концу своего ответа .
Jordan

PATCH подходит только для увеличения dog.barkCount на единицу. POST - это метод отправки электронной почты, создания новой записи лая, запуска команд для загрузки из Github или запуска текстового сообщения. @Jordan, ваше чтение PATCH RFC является творческим, но несколько расходится с его намерением как вариант PUT для частичной модификации ресурсов. Я не думаю, что вы помогаете OP, предлагая нетрадиционные прочтения спецификаций HTTP, вместо того, чтобы признавать стандартную практику использования POST для удаленных вызовов процедур.
Raymond Hettinger

@RaymondHettinger, чья практика де-факто стандартизирует POST? Все стандартные интерфейсы RPC, которые я видел, идентифицируют ресурс по сущности (не RESTful) по сравнению с URI, поэтому действительный ответ, определяющий приоритет соглашения RPC, в любом случае должен быть нетрадиционным, что, я думаю, опровергает ценность обычного RPC: один является творческим или непоследовательным . Вы никогда не ошибетесь с POST, поскольку это универсальный инструмент для обработки данных, но есть более конкретные методы. REST означает присвоение имен ресурсам и описание изменений их состояния, а не именование процедур изменения состояния. И PATCH, и POST описывают изменения состояния.
Jordan

Ответы:


287

Зачем стремиться к дизайну RESTful?

Принципы RESTful привносят функции, которые упрощают работу с веб-сайтами (для случайного пользователя, который может «просматривать» их), в дизайн API веб-сервисов , поэтому программист может легко ими пользоваться. REST хорош не потому, что это REST, а потому, что он хорош. И в основном это хорошо, потому что это просто .

Простота обычного HTTP (без SOAP-конвертов и перегруженных POSTсервисов с одним URI ), что некоторые могут назвать «отсутствием функций» , на самом деле является его самой сильной стороной . HTTP сразу же требует адресуемости и отсутствия состояния : двух основных проектных решений, которые сохраняют масштабируемость HTTP до сегодняшних мегасайтов (и мегасервисов).

Но REST - не лучшая идея: иногда может быть уместен стиль RPC («Удаленный вызов процедур» - например, SOAP) , а иногда другие потребности имеют приоритет над достоинствами Интернета. Это отлично. Что нам не очень нравится, так это ненужная сложность . Слишком часто программист или компания прибегают к услугам в стиле RPC для работы, с которой простой старый HTTP справился бы отлично. В результате HTTP сокращается до транспортного протокола для огромной полезной нагрузки XML, который объясняет, что «на самом деле» происходит (не URI или метод HTTP дают подсказку об этом). Полученная в результате услуга слишком сложна, ее невозможно отладить и она не будет работать, если ваши клиенты не настроят точную настройку, как задумал разработчик.

Точно так же код Java / C # может быть не объектно-ориентированным, простое использование HTTP не делает дизайн RESTful. Кто-то может быть захвачен стремлением думать об их сервисах с точки зрения действий и удаленных методов, которые следует вызывать. Неудивительно, что это в основном заканчивается службой в стиле RPC (или гибридом REST-RPC). Первый шаг - думать иначе. RESTful-дизайн может быть реализован разными способами, один из них - думать о своем приложении с точки зрения ресурсов, а не действий:

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

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

Я приведу примеры ниже. (Другой ключевой аспект REST - это использование HATEOAS - я не прикрашиваю его здесь, но я быстро расскажу об этом в другом посте .)


Проблемы первого дизайна

Посмотрим на предлагаемую конструкцию:

ACTION http://api.animals.com/v1/dogs/1/

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

  • (1) Учитывая только URI службы, как «случайный» программист узнает о ACTIONсуществовании глагола?
  • (2) если программист знает, что он существует, как он узнает его семантику? Что означает этот глагол?
  • (3) какие свойства (безопасность, идемпотентность) следует ожидать от этого глагола?
  • (4) что, если у программиста есть очень простой клиент, который обрабатывает только стандартные HTTP-команды?
  • (5) ...

Теперь давайте рассмотрим использованиеPOST (я расскажу, почему ниже, просто поверьте мне сейчас на слово):

POST /v1/dogs/1/ HTTP/1.1
Host: api.animals.com

{"action":"bark"}

Это могло быть нормально ... но только если :

  • {"action":"bark"}был документ; а также
  • /v1/dogs/1/был URI-адресом "обработчика документов" (фабричным). «Обработчик документов» - это URI, в который вы просто «бросаете вещи» и «забываете» о них - процессор может перенаправить вас на вновь созданный ресурс после «выброса». Например, URI для отправки сообщений в службу брокера сообщений, которая после публикации перенаправит вас на URI, который показывает статус обработки сообщения.

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

  • {"action":"bark"} это не документ , это фактически метод, которым вы пытаетесь проникнуть в сервис; а также
  • /v1/dogs/1/URI представляет собой «собака» ресурс (вероятно, собака с id==1) , а не документ процессора.

Итак, все, что мы знаем сейчас, - это то, что приведенный выше дизайн не такой уж RESTful, но что это именно? Что в этом плохого? По сути, это плохо, потому что это сложный URI со сложным значением. Вы ничего не можете сделать из этого. Откуда программисту знать, что у собаки есть barkдействие, которое можно тайно вложить POSTв него?


Разработка вызовов API вашего вопроса

Итак, давайте перейдем к делу и попробуем спроектировать этот лай в режиме ОТДЫХА, думая о ресурсах . Позвольте мне процитировать книгу Restful Web Services :

POSTЗапрос является попыткой создать новый ресурс из существующего. Существующий ресурс может быть родителем нового в смысле структуры данных, как корень дерева является родителем всех его листовых узлов. Или существующий ресурс может быть специальным ресурсом «фабрики» , единственная цель которого - генерировать другие ресурсы. Представление, отправленное вместе с POSTзапросом, описывает начальное состояние нового ресурса. Как и в случае с PUT, POSTзапрос вообще не должен включать представление.

После описания выше , мы можем увидеть , что barkможет быть смоделированы как в subresource изdog (так как barkсодержится внутри собаки, то есть кора «окорененная» от собаки).

Из этого рассуждения мы уже получили:

  • Метод POST
  • Ресурс /barks, подресурс dog:, /v1/dogs/1/barksпредставляет собой bark«фабрику». Этот URI уникален для каждой собаки (так как он находится ниже /v1/dogs/{id}).

Теперь каждый случай из вашего списка имеет определенное поведение.

1. bark просто отправляет электронное письмо dog.emailи ничего не записывает.

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


1.1 bark отправляет электронное письмо dog.emailи ничего не записывает (как синхронная задача)

Это простой случай. Обращение к barksресурсу factory сразу же дает лай (отправленное электронное письмо), и сразу же выдается ответ (если все в порядке или нет):

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(entity-body is empty - or, if you require a **document**, place it here)

200 OK

Поскольку он ничего не записывает (не меняет), этого 200 OKдостаточно. Это показывает, что все прошло как положено.


1.2 bark отправляет электронное письмо dog.emailи ничего не записывает (как асинхронная задача)

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

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed;
NOTE: when possible, the response SHOULD contain a short hypertext note with a hyperlink
to the newly created resource (bark) URI, the same returned in the Location header
(also notice that, for the 202 status code, the Location header meaning is not
standardized, thus the importance of a hipertext/hyperlink response)}

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Таким образом, каждый barkможет быть отслежен. Затем клиент может выдать GETв barkURI , чтобы знать это текущее состояние. Может быть, даже использовать, DELETEчтобы отменить это.


2. bark отправляет электронное письмо на адрес, dog.emailа затем увеличивает его dog.barkCountна 1

Это может быть сложнее, если вы хотите сообщить клиенту, что dogресурс был изменен:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

{document body, if needed; when possible, containing a hipertext/hyperlink with the address
in the Location header -- says the standard}

303 See Other
Location: http://api.animals.com/v1/dogs/1

В этом случае locationцель заголовка - дать понять клиенту, что он должен взглянуть на него dog. Из HTTP RFC о303 :

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

Если задача асинхронная, barkподресурс необходим, как и в 1.2ситуации, и 303должен быть возвращен в a, GET .../barks/Yкогда задача будет завершена.


3. bark создает новую " bark" запись с bark.timestampзаписью, когда "лай" произошел. Он также увеличивается dog.barkCountна 1.

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

201 Created
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Здесь barkсоздается по запросу, поэтому применяется статус 201 Created.

Если создание асинхронно, вместо этого 202 Acceptedтребуется ( как указано в HTTP RFC ).

Сохраненная метка времени является частью barkресурса и может быть извлечена с помощью элемента GET. Обновленная собака также может быть «задокументирована» GET dogs/X/barks/Y.


4. bark запускает системную команду для получения последней версии кода собаки с Github. Затем он отправляет текстовое сообщение, чтобы dog.ownerсообщить им, что новый код собаки находится в разработке.

Формулировка этого сложная, но в значительной степени это простая асинхронная задача:

POST /v1/dogs/1/barks HTTP/1.1
Host: api.animals.com
Authorization: Basic mAUhhuE08u724bh249a2xaP=

(document body, if needed)

202 Accepted
Location: http://api.animals.com/v1/dogs/1/barks/a65h44

Затем клиент выдаст GETs, чтобы /v1/dogs/1/barks/a65h44узнать текущее состояние (если код был извлечен, электронное письмо было отправлено владельцу и т. Д.). Всякий раз, когда собака меняется, применяется 303a.


Подведение итогов

Цитата Роя Филдинга :

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

В приведенных выше примерах POSTспроектирован единообразно. Это сделает собака " bark". Это небезопасно (это означает, что лай оказывает влияние на ресурсы) или идемпотентным (каждый запрос дает новый bark), что хорошо соответствует POSTглаголу.

Программист знал бы: от a POSTдо barksa bark. Коды состояния ответа (также с телом объекта и заголовками, если необходимо) объясняют, что изменилось и как клиент может и должен действовать.

Примечание. В качестве первичных источников использовались: книга « Restful Web Services », HTTP RFC и блог Роя Филдинга .




Редактировать:

Вопрос и, следовательно, ответ немного изменились с момента их создания. Оригинальный вопрос задан вопрос о дизайне URI , как:

ACTION http://api.animals.com/v1/dogs/1/?action=bark

Ниже приводится объяснение того, почему это не лучший выбор:

Как клиенты сообщают серверу, ЧТО ДЕЛАТЬ с данными - это информация о методе .

  • Веб-службы RESTful передают информацию о методах в методе HTTP.
  • Типичные службы стиля RPC и SOAP сохраняют свои в теле объекта и заголовке HTTP.

КАКАЯ ЧАСТЬ данных [клиент хочет, чтобы сервер] оперировал, является информацией об объеме .

  • Службы RESTful используют URI. Сервисы в стиле SOAP / RPC снова используют заголовки entity-body и HTTP.

В качестве примера возьмем URI Google http://www.google.com/search?q=DOG. Там информация о методе и информация GETоб объеме /search?q=DOG.

Короче говоря:

  • В архитектурах RESTful информация о методе передается в метод HTTP.
  • В ресурсо-ориентированной архитектуре информация об области действия передается в URI.

И практическое правило:

Если метод HTTP не соответствует информации о методе, служба не является RESTful. Если информации об области нет в URI, служба не ориентирована на ресурсы.

Вы можете поместить «лай» «действие» в URL-адрес (или в тело объекта) и использовать POST. Нет проблем, это работает и, возможно, самый простой способ сделать это, но это не RESTful .

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

Я не могу говорить о ваших конкретных бизнес-потребностях, но позвольте мне привести пример: рассмотрим службу заказов RESTful, где заказы размещаются по URI, например example.com/order/123.

Теперь предположим, что мы хотим отменить заказ, как мы это сделаем? У кого-то может возникнуть соблазн подумать, что это « действие» «отмены », и спроектировать его как POST example.com/order/123?do=cancel.

Это не RESTful, как мы говорили выше. Вместо этого мы могли бы PUTсоздать новое представление orderс canceledэлементом, отправленным по адресу true:

PUT /order/123 HTTP/1.1
Content-Type: application/xml

<order id="123">
    <customer id="89987">...</customer>
    <canceled>true</canceled>
    ...
</order>

Вот и все. Если заказ не может быть отменен, может быть возвращен конкретный код статуса. (Для простоты также может быть доступен дизайн подресурсов, как и в случае POST /order/123/canceledс телом объекта true.)

В вашем конкретном сценарии вы можете попробовать нечто подобное. Таким образом, когда собака лает, например, GETat /v1/dogs/1/может включать эту информацию (например <barking>true</barking>) . Или ... если это слишком сложно, ослабьте требования RESTful и придерживайтесь их POST.

Обновить:

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

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

Переменные запроса представляют собой информацию об области действия , но не обозначают новые ресурсы ( /post?lang=enочевидно, это тот же ресурс /post?lang=jp, только другое представление). Скорее, они используются для передачи состояния клиента (например ?page=10, чтобы это состояние не сохранялось на сервере; ?lang=enэто также пример здесь) или входных параметров для алгоритмических ресурсов ( /search?q=dogs, /dogs?code=1). Опять же, не отдельные ресурсы.

Свойства HTTP-глаголов (методов):

Еще один четкий момент, который показывает, ?action=somethingчто URI не является RESTful, - это свойства HTTP-глаголов:

  • GETи HEADбезопасны (и идемпотентны);
  • PUTи DELETEявляются только идемпотентными;
  • POST ни то, ни другое.

Безопасность : запрос GETor HEAD- это запрос на чтение некоторых данных, а не запрос на изменение состояния сервера. Клиент может сделать GETили HEADзапросить 10 раз, и это то же самое, что сделать это один раз или не сделать вообще .

Идемпотентность : идемпотентная операция в одном, которая имеет одинаковый эффект независимо от того, применяете ли вы ее один или более одного раза (в математике умножение на ноль идемпотентно). Если вы DELETEиспользуете ресурс один раз, повторное удаление будет иметь тот же эффект (ресурс GONEуже есть).

POSTне является ни безопасным, ни идемпотентным. Выполнение двух идентичных POSTзапросов к «фабричному» ресурсу, вероятно, приведет к тому, что два подчиненных ресурса будут содержать одинаковую информацию. При перегруженном (метод в URI или теле объекта) POSTвсе ставки отключены.

Оба эти свойства были важны для успеха протокола HTTP (в ненадежных сетях!): Сколько раз вы обновляли ( GET) страницу, не дожидаясь ее полной загрузки?

Создание действия и его размещение в URL-адресе явно нарушает контракт методов HTTP. Еще раз: технология позволяет, вы можете это делать, но это не RESTful-дизайн.


1
@JacobStevens OP немного изменил вопрос, поэтому мне нужно обновить свой ответ, чтобы сделать его более прямым (проверьте исходный вопрос , возможно, вы поймете, что я имею в виду). Я согласен с POST«предоставлением блока данных ... процессу обработки данных», но разница в том, что блок данных , а не блок данных и процедура (действие, метод, команда) должны быть казнен тогда. Это POSTперегрузка, а POSTперегрузка - это дизайн в стиле RPC, а не RESTful.
acdcjunior 04

6
Ответ нас обновил. Это немного длинновато, потому что, казалось, требовалось подробное объяснение («Имейте в виду, что у меня элементарное понимание REST.»). Это была своего рода борьба за то, чтобы сделать его как можно более ясным. Надеюсь, это будет полезно.
acdcjunior

2
Отличное объяснение, я проголосовал, но заголовок Location не должен использоваться в ответе 202 Accepted. Кажется, это неправильная интерпретация, которую многие люди делают из RFC. Проверьте этот stackoverflow.com/questions/26199228/…
Delmo

1
Это отличный ответ, мне очень помог. Меня интересует одна вещь: иногда вы используете, /bark/а иногда и /barks/во фрагменте URI. Например. вы POSTвключены, /v1/dogs/1/barksно .../dogs/1/bark/a65h44в ответах у вас есть заголовок Location. Ресурс всегда должен быть во множественном числе, верно?
Ярослав Коньчак

1
@maximedupre Вид. Я бы перефразировал «Это только дает значение» на «Это только часть стандарта». Как заявил Рой , Locationего можно использовать 202, просто он не имеет стандартного поведения для этого кода состояния, и поэтому вы должны убедиться, что ответ понятен с использованием других средств, таких как гипертекст, содержащий гиперссылку. Другими словами: стандарт не говорит, что Locationозначает для 202, но не запрещает вам использовать его для 202. Если вы его используете, вы должны объяснить пользователю, что это означает. Я попытался указать на это в ответе ...
acdcjunior

6

Я отвечал ранее , но этот ответ противоречит моему старому ответу и следует другой стратегии для поиска решения. Он показывает, как HTTP-запрос построен на основе концепций, определяющих REST и HTTP. Он также использует PATCHвместо POSTили PUT.

Он проходит через ограничения REST, затем компоненты HTTP, а затем - возможное решение.

ОТДЫХ

REST - это набор ограничений, предназначенных для применения к распределенной гипермедийной системе, чтобы сделать ее масштабируемой. Даже чтобы понять это в контексте удаленного управления действием, вы должны думать об удаленном управлении действием как части распределенной гипермедийной системы - части системы для обнаружения, просмотра и изменения взаимосвязанной информации. Если это больше проблем, чем оно того стоит, то, вероятно, не стоит пытаться сделать его RESTful. Если вам просто нужен графический интерфейс типа «панель управления» на клиенте, который может запускать действия на сервере через порт 80, тогда вам, вероятно, понадобится простой интерфейс RPC, такой как JSON-RPC, через HTTP-запросы / ответы или WebSocket.

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

REST определяется четырьмя ограничениями интерфейса:

идентификация ресурсов; манипулирование ресурсами через представления; информативные сообщения; и гипермедиа как двигатель состояния приложения.

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

Начнем с первого ограничения: идентификация ресурса .

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

Итак, собака - это ресурс. Это нужно идентифицировать.

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

Вы моделируете собаку, беря набор идентификаторов и представлений и говоря, что все они связаны друг с другом в данный момент. А пока воспользуемся идентификатором «собака №1». Это подводит нас ко второму и третьему ограничениям: представлению ресурсов и самоописанию .

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

Ниже приводится последовательность байтов, фиксирующих предполагаемое состояние собаки, то есть представление, которое мы хотим связать с идентификатором «собака №1» (обратите внимание, что он представляет только часть состояния, так как не учитывает имя собаки, здоровье , или даже прошлый лай):

Он лает каждые 10 минут с момента изменения состояния и будет продолжаться бесконечно.

Он должен быть прикреплен к метаданным, описывающим его. Эти метаданные могут быть полезны:

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

Наконец, давайте посмотрим на четвертое ограничение: HATEOAS .

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

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

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

HTTP

HTTP реализует ограничения REST следующим образом:

идентификация ресурса: URI

представление ресурса : объект-тело

самоописание : метод или код состояния, заголовки и, возможно, части тела объекта (например, URI схемы XML)

HATEOAS : гиперссылки

Вы выбрали http://api.animals.com/v1/dogs/1URI. Предположим, клиент получил это с какой-то страницы сайта.

Давайте воспользуемся этим телом объекта (значение next- метка времени; значение 0означает «когда этот запрос получен»):

{"barks": {"next": 0, "frequency": 10}}

Теперь нам нужен метод. PATCH соответствует описанию "части предполагаемого состояния", которое мы выбрали:

Метод PATCH запрашивает, чтобы набор изменений, описанных в объекте запроса, был применен к ресурсу, идентифицированному Request-URI.

И несколько заголовков:

Чтобы указать язык тела объекта: Content-Type: application/json

Чтобы убедиться, что это происходит только один раз: If-Unmodified-Since: <date/time this was first sent>

И у нас есть просьба:

PATCH /v1/dogs/1/ HTTP/1.1
Host: api.animals.com
Content-Type: application/json
If-Unmodified-Since: <date/time this was first sent>
[other headers]

{"barks": {"next": 0, "frequency": 10}}

В случае успеха клиент должен получить 204в ответ код состояния или, 205если представление /v1/dogs/1/изменилось, чтобы отразить новый график лая.

В случае сбоя он должен получить сообщение 403и полезное сообщение, почему.

Для REST необязательно, чтобы служба отражала расписание лая в представлении в ответ на него GET /v1/dogs/1/, но было бы лучше, если бы представление JSON включало это:

"barks": {
    "previous": [x_1, x_2, ..., x_n],
    "next": x_n,
    "frequency": 10
}

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


3

Большинство людей используют для этого POST . Он подходит для выполнения «любой небезопасной или неидемпотентной операции, когда никакой другой метод HTTP не кажется подходящим».

API, такие как XMLRPC, используют POST для запуска действий, которые могут запускать произвольный код. «Действие» включается в данные POST:

POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

<?xml version="1.0"?>
<methodCall>
   <methodName>examples.getStateName</methodName>
   <params>
      <param>
         <value><i4>41</i4></value>
         </param>
      </params>
   </methodCall>

Пример RPC приведен, чтобы показать, что POST - это обычный выбор HTTP-глаголов для серверных методов. Вот мысли Роя Филдинга о POST - он в значительной степени говорит, что RESTful использовать указанные методы HTTP.

Обратите внимание, что сам по себе RPC не очень RESTful, потому что он не ориентирован на ресурсы. Но если вам нужно безгражданство, кеширование или многоуровневость, нетрудно выполнить соответствующие преобразования. См. Пример на http://blog.perfectapi.com/2012/opinionated-rpc-apis-vs-restful-apis/ .


Я думаю, вы бы закодировали параметры
URL-адреса, а

@ Кирк Да, но с одной незначительной модификацией, отбросьте последнюю косую черту: POST api.animals.com/v1/dogs1?action=bark
Раймонд Хеттингер,

если вы последуете советам в этом ответе, имейте в виду, что полученный API не будет RESTful.
Николас Шанкс

2
Это не RESTful, потому что HTTP устанавливает URL-адрес в качестве идентификатора ресурса, а URL-адрес /RPC2ничего не делает для идентификации ресурса - он определяет серверную технологию. Вместо этого он methodNameпытается «идентифицировать» «ресурс» - но даже в этом случае он не выигрывает от различия существительного и глагола; единственная вещь, похожая на глагол methodCall. Это похоже на «выполнить поиск имени-состояния» вместо «получить имя-состояния» - последнее имеет гораздо больший смысл.
Jordan

+1 за ссылки; очень информативен, а эксперимент с «самоуверенным RPC» изобретателен.
Jordan

2

POSTэто метод HTTP, разработанный для

Предоставление блока данных ... процессу обработки данных

Методы на стороне сервера, обрабатывающие действия, не связанные с CRUD, - это то, что Рой Филдинг намеревался использовать с REST, так что вы хороши в этом, и поэтому POSTбыли сделаны неидемпотентными. POSTбудет обрабатывать большую часть отправки данных в методы на стороне сервера для обработки информации.

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


1
PUT vs. POST - это все о URL. В третьем абзаце после 9.6 PUT говорится, что цель двух методов состоит в том, чтобы PUTURL-адрес относился к тому, что должно быть заменено содержимым клиента, а POSTURL-адрес ссылался на то, что должно обрабатывать контент клиента, как он хочет.
Jordan

1

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

POST http://api.animals.com/v1/dogs/1/bark

собака номер 1 лает

GET http://api.animals.com/v1/dogs/1/bark

возвращает метку времени последнего лая

DELETE http://api.animals.com/v1/dogs/1/bark

не применяется! так что игнорируйте это.


Это RESTful только в том случае, если вы рассматриваете /v1/dogs/1/barkего как ресурс как таковой и POSTкак описание того, как должно измениться внутреннее состояние этого ресурса. Я считаю, что имеет смысл просто рассматривать /v1/dogs/1/как ресурс и указывать в теле объекта, что он должен лаять.
Jordan

ммм .. ну это ресурс, состояние которого можно изменить. Поскольку в результате изменения его состояния возникает шум, это не делает его менее ресурсоемким! Вы смотрите на Bark как на глагол, поэтому вы не можете рассматривать его как ресурс. Я смотрю на него как на зависимый ресурс, состояние которого можно изменить, и, поскольку его состояние является логическим, я не вижу причин упоминать его в теле объекта. Это только моё мнение.
bolbol

1

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

Во-первых, не указывайте параметры действия в URL-адресе. URL-адрес определяет, к чему вы применяете действие, а параметры запроса являются частью URL-адреса. Его следует рассматривать исключительно как существительное. http://api.animals.com/v1/dogs/1/?action=barkэто другой ресурс - другое существительное - к http://api.animals.com/v1/dogs/1/. [nb. Аскер удалил ?action=barkURI из вопроса.] Например, сравните http://api.animals.com/v1/dogs/?id=1с http://api.animals.com/v1/dogs/?id=2. Разные ресурсы, различающиеся только строкой запроса. Таким образом, действие вашего запроса, если оно не соответствует непосредственно существующему типу метода без тела (TRACE, OPTIONS, HEAD, GET, DELETE и т. Д.), Должно быть определено в теле запроса.

Затем решите, является ли действие " идемпотентным », что означает, что его можно повторить без отрицательного эффекта (см. Следующий абзац для более подробного объяснения). Например, установка значения true может быть повторена, если клиент не уверен в достижении желаемого эффекта. Они отправляют запрос снова, и значение остается верным. Добавление 1 к числу не идемпотентно. Если клиент отправляет команду Add1, не уверен, что она работает, и отправляет ее снова, сервер добавил одну или две? Как только вы это определили, у вас будет больше возможностей выбирать между вашим методом PUTи POSTдля него.

Идемпотент означает, что запрос может быть повторен без изменения результата. Эти эффекты не включают ведение журнала и другие подобные действия администратора сервера. Используя ваш первый и второй примеры, отправка двух электронных писем одному и тому же человеку действительно приводит к другому состоянию, чем отправка одного электронного письма (у получателя есть два в своем почтовом ящике, которые они могут рассматривать как спам), поэтому я определенно использовал бы для этого POST . Если barkCount в примере 2 предназначен для просмотра пользователем вашего API или влияет на что-то видимое для клиента, то это также то, что сделает запрос неидемпотентным. Если он предназначен только для просмотра вами, он считается ведением журнала сервера и должен игнорироваться при определении идемпотентности.

Наконец, определите, можно ли ожидать немедленного успеха действия, которое вы хотите выполнить. BarkDog - это быстро завершающееся действие. RunMarathon - нет. Если ваше действие медленное, подумайте о том, чтобы вернуть202 Accepted , с URL-адресом в теле ответа, чтобы пользователь мог опросить, чтобы узнать, завершено ли действие. В качестве альтернативы, попросите пользователей выполнить POST на URL-адрес списка, например, /marathons-in-progress/а затем, когда действие будет выполнено, перенаправьте их с URL- /marathons-complete/адреса текущего идентификатора на URL- адрес.
Для конкретных случаев №1 и №2 я бы разместил на сервере очередь, а клиент отправлял бы ей пакеты адресов. Действие будет не SendEmails, а что-то вроде AddToDispatchQueue. Затем сервер может опросить очередь, чтобы узнать, есть ли ожидающие адреса электронной почты, и отправить электронные письма, если они найдены. Затем он обновляет очередь, чтобы указать, что ожидающее действие теперь выполнено. У вас будет другой URI, показывающий клиенту текущее состояние очереди. Чтобы избежать двойной отправки электронных писем, сервер также может вести журнал того, кому он отправил это электронное письмо, и сравнивать каждый адрес с ним, чтобы убедиться, что он никогда не отправляет два на один и тот же адрес, даже если вы отправляете один и тот же список дважды на очередь.

Выбирая URI для чего-либо, старайтесь думать о нем как о результате, а не о действии. Например google.com/search?q=dogsпоказывает результаты поиска по слову «собаки». Поиск необязательно выполнять.

Случаи №3 и №4 из вашего списка также не являются идемпотентными действиями. Вы предполагаете, что различные предлагаемые эффекты могут повлиять на дизайн API. Во всех четырех случаях я бы использовал один и тот же API, поскольку все четыре меняют «состояние мира».


Допустим, действие состоит в том, чтобы пройти через гигантскую очередь электронной почты и отправить сообщение группе людей. Это идемпотент? Идемпотентные действия для PUT или POST?
Kirk Ouimet

@kirk Я расширил свой ответ.
Николас Шанкс

0

См. Мой новый ответ - он противоречит этому и более четко и точно объясняет REST и HTTP.

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

POST /v1/dogs/1/bark-schedule HTTP/1.1
...
{"token": 12345, "next": 0, "frequency": 10}

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

nextуказывает время следующего лая; ценность0 означает «как можно скорее».

Всякий раз GET /v1/dogs/1/bark-schedule, когда вы должны получить что-то вроде этого, где t - время последнего лая, а u - t + 10 минут:

{"last": t, "next": u}

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

Соответствующий код состояния, вероятно, 205 . Я представляю себе клиента, который просматривает текущее расписание POSTпо тому же URL-адресу, чтобы изменить его, и получает от службы указание повторно взглянуть на расписание, чтобы доказать, что оно было изменено.

Объяснение

ОТДЫХ

Забудьте на мгновение о HTTP. Важно понимать, что ресурс - это функция, которая принимает время в качестве входных данных и возвращает набор, содержащий идентификаторы и представления . Давайте упростим это до: ресурс - это набор R идентификаторов и представлений; R может изменяться - члены могут быть добавлены, удалены или изменены. (Хотя это плохой, нестабильный дизайн для удаления или изменения идентификаторов.) Мы говорим, что идентификатор, который является элементом R, идентифицирует R , и что представление, которое является элементом R, представляет R .

Скажем, R - собака. Вы случайно идентифицировали R как /v1/dogs/1. (Значение /v1/dogs/1является членом R ). Это только один из многих способов , вы могли бы определить R . Вы также можете идентифицировать R как /v1/dogs/1/x-raysи как /v1/rufus.

Как вы представляете R ? Может быть, с фотографией. Может быть, с набором рентгеновских снимков. А может с указанием даты и времени, когда Р. в последний раз лаял. Но помните, что все это представления одного и того же ресурса . /v1/dogs/1/x-rays- это идентификатор того же ресурса, который представлен ответом на вопрос "когда R последний раз лаял?"

HTTP

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

По крайней мере, так GETоно и есть. PUTпо сути является инверсией GET: вы PUTпредставляете r в URL-адресе, если хотите, чтобы будущие GETзапросы к этому URL-адресу возвращали r , с некоторыми возможными переводами, такими как JSON в HTML.

POST- более свободный способ изменения представления. Подумайте о существовании логики отображения и логики модификации, которые дублируют друг друга - обе соответствуют одному и тому же URL-адресу. Запрос POST - это запрос логики модификации для обработки информации и изменения любых представлений (а не только представления, расположенного по тому же URL-адресу), как считает служба. Обратите внимание на третий абзац после 9.6 PUT : вы не заменяете объект по URL-адресу новым содержимым; вы просите объект по URL-адресу обработать некоторую информацию и разумно ответить в форме информативных представлений.

В нашем случае мы просим логику модификации at /v1/dogs/1/bark-schedule(которая является аналогом логики отображения, которая сообщает нам, когда он в последний раз лаял и когда он будет лаять в следующий раз) обработать нашу информацию и соответствующим образом изменить некоторые представления. В ответ на future GET-объект логика отображения, соответствующая тому же URL-адресу, сообщит нам, что собака теперь лает так, как мы хотим.

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


-1

REST - это ресурсно-ориентированный стандарт, а не действия, как RPC.

Если вы хотите, чтобы ваш сервер лаял , вам следует изучить различные идеи, такие как JSON-RPC , или обмен данными через веб-сокеты.

На мой взгляд, каждая попытка сохранить его в RESTful потерпит неудачу: вы можете выдать a POSTс actionпараметром, вы не создаете никаких новых ресурсов, но, поскольку у вас могут быть побочные эффекты, вы в большей безопасности.


POSTбыл разработан для «предоставления блока данных ... процессу обработки данных» . Кажется, что многие люди различают ресурсы от действий, но на самом деле действия - это просто тип ресурса. Вызов ресурса действия на сервере по-прежнему представляет собой единый интерфейс, кэшируемый, модульный и масштабируемый. Он также не имеет состояния, но это может быть нарушено, если клиент рассчитан на ответ. Но вызов «метода void» на сервере - это то, что Рой Филдинг намеревался использовать с REST .
Джейкоб Стивенс

Как я описываю в своем ответе , вы можете в REST неявно заставить сервер выполнить действие, попросив его сказать, что с этого момента «ваше действие было завершено», тогда как RPC основан на идее просто попросить сервер выполнить Действие. И то, и другое имеет смысл, равно как и императивное, и декларативное программирование.
Jordan
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.