Все что необходимо знать про веб службы и REST

Введение

Вот не люблю я изобретать велосипед. Про REST сказано уже довольно много. Многие поставщики веб служб готовы клясться, что их службы являются RESTful. Во время собеседования вы точно услышите хотя бы несколько вопросов про REST, независимо от того это собеседования для бэкенд, или фронтенд разработчика. Я вот помню как-то во время одного собеседования мне задали такой вопрос: «Вот вы написали в своем резюме, что знайте REST․ Ответьте пожалуйста, какой HTTP код вы получите, если при запросе к RESTful сервису ресурс не найден?». Ответ 404 был принят единогласно. Если честно, я так и не понял, как этот вопрос помог понять знаю ли я REST или нет, но одно могу уверенно сказать: REST понимают далеко не все. Вот некоторые вопросы, которые мучали меня долгое время:

Если вы не можете дать исчерпывающего ответа хотя бы на один из этих вопросов, то продолжайте чтение. Если вы можете однозначно ответить на все эти вопросы, можете привести формат правильного URL, считайте, что GET, POST, PUT, DELETE обязательно должны соответствовать CRUD операциям с ресурсами, то вам обязательно надо продолжать чтение.

Чтобы найти ответы на приведенные вопросы и представить общую картину пришлось прочитать кучу спецификаций, диссертацию Роя Филдинга и книгу Леонарда Ричардсона, поскольку оказалось что есть большая путаница в интернете и в частности на Stack Overflow. Найденная информацию показалось мне довольно полезной вот и решил им с вами поделиться.

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

Ну что же, начнем путешествие в мир веб-сервисов.

SOA & Web Services

SOA, расширяемая как сервис ориентированная архитектура (Service Oriented Architecture), является парадигмой для организации и использования распределенных систем, которые могут находиться под контролем различных областей собственности. В SOA, под service подразумевается функциональная возможность, которая удовлетворяет следующим критериям։

Service Description – это информация о сервисе, необходимая для взаимодействия со службой (WSDL).

Service Provider – это люди или организации, которые предоставляют сервисы.

Service Consumer – это клиенты, потребители служб.

Web Service, согласно определению, является системой, предназначенная для взаимодействия машин / программ по сети. Веб-сервис должен иметь интерфейс, описанной в машинно-обрабатываемом формате (service description). Другие системы должны взаимодействовать с веб-сервисом посредством сообщений.

Взаимосвязь между веб-сервисом и SOA является то, что SOA может быть реализован посредством веб-сервисов.

Если вас заинтересует какие еще методы существуют или существовали для реализации SOA, можете посмотреть COM и CORBA.

Протоколы веб служб

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

XML-RPC

Протокол XML-RPC (XML Remote Procedure Call) был в первые опубликован в 1999 году. Все сообщение XML-RPC являются HTTP-POST запросами. Для шифрования сообщений используется XML. Параметры процедуры могут быть скалярные значения, числа, строки, даты, массивы и структуры. Ответ веб службы может хранить либо значение, возвращаемой процедурой, либо код и сообщение ошибки.

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

JSON-RPC

Протокол JSON-RPC, опубликованный в 2009 году, по своим принципам работы очень похож на XML-RPC. Главными отличиями являются способ шифрования данных, независимость от транспортного уровня, способность передачи извещений (notification request) и возможность идентификации ответов при отправке нескольких запросов одновременно.

Для шифрования данных в JSON-RPC используется JSON. Кроме имени процедуры и параметров, в запросе указывается также значение id, который используется для идентификации ответа на стороне клиента. Другими словами если вы отправили запрос с id=12345, то ответ этого запроса обязательно должен возвращать сообщение с id=12345.

Извещение – это специальные запросы, на которые сервер может не отвечать. Для отметки запроса, как извещение, значения параметра id указывается = null.

Независимость от транспортного уровня можно обусловить только тем, что в спецификации JSON-RPC  HTTP не указывается обязательным протоколом. При использовании JSON-RPC над HTTP, необходимо использовать POST запросы.

Недостатком JSON-RPC, как и в случае XML-RPC является отсутствие языка описания веб-сервиса (аналог WSDL).

SOAP

Протокол SOAP (Simple Object Access Protocol) является наследником XML-RPC. Основными характеристиками SOAP являются:

При использовании HTTP, поддерживается как метод GET так и POST. GET допускается использовать только для получения данных, то есть на стороне сервера не должно ничего меняться. POST можно использовать для всех случаев. На практике обычно используется только POST.

Главным недостатком SOAP является его сложность по причине гибкости. Другой немало важным недостатком является поддержка шифрование только в XML.

REST

REST является на сегодняшний день наверное самым популярным веб-сервис протоколом.

На самом деле REST вовсе не является протоколом и оно вообще не новое. REST является архитектурой который был предложен в 2000-ие годы Роем Филдингом в его диссертации «Architectural Styles and the Design of Network-based Software Architectures» [8]. До этого REST архитектура была использована в многих проектах в рамках IETF и W3C. В самой диссертации вы не сумейте найти терминов «веб служба» или SOA. REST архитектура была предложена для правильного конструирования распределенных гипермедиа систем, по другим словом того, что на сегодня называется World Wide Web. Чтобы диссертацию Филдинга вы приняли всерьёз, скажу, что Рой является архитектором HTTP 1.1, соавтором интернет стандартов для HTTP и URI. В общем мужик он серьезный и известный.

Чтобы распределенная система считалась сконструированной по REST-архитектуре, необходимо, чтобы она удовлетворяла следующим критериям:

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

GET /account/12345 HTTP/1.1


HTTP/1.1 200 OK

<?xml version="1.0"?>

<account>

  <account_number>12345</account_number>

  <balance currency="usd">100.00</balance>

  <link rel="deposit" href="/account/12345/deposit" />

  <link rel="withdraw" href="/account/12345/withdraw" />

  <link rel="transfer" href="/account/12345/transfer" />

  <link rel="close" href="/account/12345/close" />

</account>

Рассмотрим этот же пример при негативном балансе

GET /account/12345 HTTP/1.1


HTTP/1.1 200 OK

<?xml version="1.0"?>

<account>

    <account_number>12345</account_number>

    <balance currency="usd">-25.00</balance>

    <link rel="deposit" href="/account/12345/deposit" />

</account>

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

Продолжим определения условия, которым должна REST-архитектура

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

Как вы уже заметили в этих пунктах ничего не сказано про GET, PUT, POST и DELETE запросы, шифрование JSON, HTTP и т.д.

REST – это просто архитектура, и никак не привязана к каким либо протоколом.

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

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

REST & Richardson Maturity Model

И так, как же связан REST с веб службами? Какие службы можно считать RESTful, а какие нет? Что вы получите, если ваша служба будет удовлетворять всем пунктам Филдинга? Отвечу на эти вопросы по очереди:

REST provides a set of architectural constraints that, when applied as a whole, emphasizes scalability of component interactions, generality of interfaces, independent deployment of components, and intermediary components to reduce interaction latency, enforce security, and encapsulate legacy systems.

Звучит конечно все очень круто, но надо ли чтобы ваш веб-сервис был сконструирован по REST или это просто тренд? Давайте рассмотрим модель RMM (Richardson Maturity Model) Леонарда Ричардсона.

После анализа нескольких сотен веб-сервисов была предложена модель RMM для оценки качества, или как Ричардсон назвал это, зрелости веб-сервиса. Модель RMM состоит из четырёх уровней. Если ваш сервис соответствует последнему уровню, то можно считать его RESTful. Ниже я приведу примеры и рисунки из, которые по словам автора были проверены Аароном Шварцем, Леонардом Ричардсоном и другими известными лицами. Все примеры основаны на следующей истории: Я хочу записаться на прием у врача. От веб службы мне нужно получить свободные часы приема на конкретную дату и после записаться на прием. И так, рассмотрим все эти четыре уровня на данном примере.

Уровень 0: Один URI, один HTTP метод

Здесь HTTP используется только для взаимодействия компонентов распределенной системы. Из методов используется только один, например POST. Как вы уже догадались, такими веб-сервисами являются протоколы XML-RPC и SOAP.

Получение свободных часов доктора mjones на дату 2010-01-04

POST /appointmentService HTTP/1.1

[various other headers]

<openSlotRequest date = "2010-01-04" doctor = "mjones"/>


HTTP/1.1 200 OK

[various headers]


<openSlotList>

  <slot start = "1400" end = "1450">

    <doctor id = "mjones"/>

  </slot>

  <slot start = "1600" end = "1650">

    <doctor id = "mjones"/>

  </slot>

</openSlotList>

Регистрация на прием к доктору mjones с 14:00 до 14:50. (тут в запросе наверно нужно было бы еще дату указать, но оригинальный пример не буду менять)

POST /appointmentService HTTP/1.1

[various other headers]


<appointmentRequest>

  <slot doctor = "mjones" start = "1400" end = "1450"/>

  <patient id = "jsmith"/>

</appointmentRequest>


HTTP/1.1 200 OK

[various headers]


<appointment>

  <slot doctor = "mjones" start = "1400" end = "1450"/>

  <patient id = "jsmith"/>

</appointment>

XML используется тут только для примера, в его месте мог был быть JSON, HTML и т.д. Веб-сервис имеет только один URL (относительный URL): /appointmentService. Запрашиваемая функция указывается в теле запроса.

Если ваш сервис соответствует уровню 0, то он еще ребенок.

Теперь рассмотрим этот же пример при работе с веб-сервисом с уровнем 1.

Уровень 1: Несколько URI, один HTTP метод

Службы этого уровня используют понятие «разделяй и властвуй». В службе вводится понятие ресурсов и для действия с конкретным ресурсом используется URL этого ресурса.

Получение свободных часов доктора mjones на дату 2010-01-04

POST /doctors/mjones HTTP/1.1

[various other headers]


<openSlotRequest date = "2010-01-04"/>


HTTP/1.1 200 OK

[various headers]


<openSlotList>

  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>

  <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>

</openSlotList>

Регистрация на прием к доктору mjones с 14:00 до 14:50

POST /slots/1234 HTTP/1.1

[various other headers]


<appointmentRequest>

  <patient id = "jsmith"/>

</appointmentRequest>


HTTP/1.1 200 OK

[various headers]


<appointment>

  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>

  <patient id = "jsmith"/>

</appointment>

И так, если вы хотите выполнить какое-то действие с доктором, то надо использовать относительный URL /doctors, а если ваше действие связано с посещением, то URL /slots. Если сравнить службу первого уровня со службой нулевого уровня, то в последнем случае есть только один ресурс, и этот ресурс сама веб служба.

Если ваш сервис соответствует уровню 1, то он является подростком.

Уровень 2: Несколько URI, каждый поддерживает разные HTTP методы (Правильное использование HTTP)

И так, на уровне 1 все методы веб службы были отделены с помощью ресурсов, но с ресурсом можно выполнить кучу действий. Чтобы эти действия тоже были как-то логически разделены, используется возможность HTTP отправлять и принимать операции с разными методами: GET, HEAD, POST, PUT, DELETE, TRACE, CONECT. Например, если метод является чтением, можно использовать GET, если для создания POST или PUT․ В принципе, можно и наоборот, но поскольку в HTTP эти методы имеют какие-то понятия и характеристики, лучше, чтобы эти понятия были те же, что и в веб службе, в противном случае вы можете получить кэшируемый метод создания ресурса. Другими словами, какой метод вы будете использовать для каких операций, уже вопрос правильного использования протокола HTTP․

Получение свободных часов доктора mjones на дату 2010-01-04

GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1

Host: royalhope.nhs.uk


HTTP/1.1 200 OK

[various headers]


<openSlotList>

  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>

  <slot id = "5678" doctor = "mjones" start = "1600" end = "1650"/>

</openSlotList>

Регистрация на прием к доктору mjones с 14:00 до 14:50

POST /slots/1234 HTTP/1.1

[various other headers]


<appointmentRequest>

  <patient id = "jsmith"/>

</appointmentRequest>


HTTP/1.1 201 Created

Location: slots/1234/appointment

[various headers]


<appointment>

  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>

  <patient id = "jsmith"/>

</appointment>

С вводом разных HTTP методов вводится также необходимость возвращения правильных HTTP статус кодов. Например, на запрос создания встречи, если она создана, то должен быть возвращен код 201. Если в течении ваших действий кто-то уже регистрировался на выбранный день и час, должен быть возвращен код 409 conflict и т.д. Опять же, тут дело в правильном использовании протокола HTTP․

Если ваш сервис соответствует уровню 2, то он является уже взрослой мужчиной.

Уровень 3: HATEOAS․ Ресурсы сами описывают свои возможности и взаимосвязи

HATEOAS (Hypertext as the Engine of Application State), требование которое на мой взгляд обязателен для гипермедиа, но насколько это актуален для веб служб — не знаю. Все же, HATEOAS – это характеристика веб-сервиса возвращать действия в виде URL, которые могут быть выполнены с интересующим вам ресурсом.

Поскольку HATEOAS был уже рассмотрен выше, тут я приведу только примеры.

Получение свободных часов доктора mjones на дату 2010-01-04

GET /doctors/mjones/slots?date=20100104&status=open HTTP/1.1

Host: royalhope.nhs.uk


HTTP/1.1 200 OK

[various headers]


<openSlotList>

  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450">

     <link rel = "/linkrels/slot/book" uri = "/slots/1234"/>

  </slot>

  <slot id = "5678" doctor = "mjones" start = "1600" end = "1650">

     <link rel = "/linkrels/slot/book" uri = "/slots/5678"/>

  </slot>

</openSlotList>

Каждый свободный час имеет URL, для выполнения действий над ним, в этом случае регистрация на этот час.

Регистрация на прием к доктору mjones с 14:00 до 14:50.

POST /slots/1234 HTTP/1.1

[various other headers]


<appointmentRequest>

  <patient id = "jsmith"/>

</appointmentRequest>


HTTP/1.1 201 Created

Location: http://royalhope.nhs.uk/slots/1234/appointment

[various headers]


<appointment>

  <slot id = "1234" doctor = "mjones" start = "1400" end = "1450"/>

  <patient id = "jsmith"/>

  <link rel = "/linkrels/appointment/cancel" uri = "/slots/1234/appointment"/>

  <link rel = "/linkrels/appointment/addTest" uri = "/slots/1234/appointment/tests"/>

  <link rel = "self" uri = "/slots/1234/appointment"/>

  <link rel = "/linkrels/appointment/changeTime" uri = "/doctors/mjones/slots?date=20100104@status=open"/>

  <link rel = "/linkrels/appointment/updateContactInfo" uri = "/patients/jsmith/contactInfo"/>

  <link rel = "/linkrels/help" uri = "/help/appointment"/>

</appointment>

Преимуществом HATEOAS является то, что оно дает возможность разработчикам веб служб менять URI независимо от клиентов. Кроме этого, веб-сервис сам описывает себя без каких-либо WSDL. Чтобы правильно представить HATEOAS, представьте веб сайт из статических страниц. Вы открывайте главную страницу, а там уже ссылки на все остальное. Чтобы зайти на веб сайт и что-то найти там, нет необходимости прочитать какой-то документ по данному веб сайту, что-то вроде документа по API. Опять же, мое субъективное мнение по необходимости веб-сервиса поддерживать HATEOAS довольно пессимистично.

Если ваш сервис соответствует уровню 3, то его можно уже называть RESTful ну и конечно стариком с хорошим оптом.

Заключение

Если вы читайте эти строки значит либо вы прочитали всё и дошли до заключения, либо по причине недостатка во времени пролистали и читайте только заключение.

Поскольку второй случай более распространенный чем первый, приведу основные концепции:

Будет ли веб служба ребенком или стариком – ваш выбор. Если вы или ваша компания разрабатывайте и серверную часть приложения и клиентскую, то думаю нет необходимости, чтобы ваша служба удовлетворяла всем пунктам RMM, особенно HATEOAS. «Мужчина» или «подросток» с этой задачей легко справятся. А если вы разрабатывайте что-то вроде amazon, у вас должны быть несколько тысяч клиентов, о которых вы понятия не имейте, то с этим наверное наилучшим образом справиться «старик». Если же ваша служба имеет очень мало функций, то дайте эту работу «ребенку», он знает, как все слить в одну кучу (в один ресурс).