Содержание


Web-сервисы RESTful: основы

Comments

Основы

REST определяет ряд архитектурных принципов проектирования Web-сервисов, ориентированных на системные ресурсы, включая способы обработки и передачи состояний ресурсов по HTTP разнообразными клиентскими приложениями, написанными на различных языках программирования. За последние несколько лет REST стала преобладающей моделью проектирования Web-сервисов. Фактически REST оказала настолько большое влияние на Web, что практически вытеснила дизайн интерфейса, основанный на SOAP и WSDL, из-за значительного более простого стиля проектирования.

Технология REST не привлекла большого внимания в 2000 году, когда Рой Филдинг (Roy Fielding) впервые представил ее в Калифорнийском университете в Ирвайне в своей диссертации "Архитектурные стили и дизайн сетевых архитектур программного обеспечения", где анализировался набор принципов архитектуры программного обеспечения, использующей Web в качестве платформы распределенных вычислений (ссылка на эту диссертацию приведена в разделе Ресурсы). Однако сегодня, по прошествии многих лет, возникли и продолжают развиваться многочисленные инфраструктуры для REST, которую, среди прочего, планируется интегрировать в стандарт Java™ 6 JSR-311.

В данной статье предполагается, что в чистом виде (в каком она привлекает столь пристальное внимание) конкретная реализация Web-сервисов REST следует четырем базовым принципам проектирования:

  • Явное использование HTTP-методов.
  • Несохранение состояния.
  • Предоставление URI, аналогичных структуре каталогов.
  • Передача данных в XML, JavaScript Object Notation (JSON) или в обоих форматах.

В последующих разделах рассматриваются эти четыре принципа и приводится техническое обоснование их важности для разработчиков Web-сервисов REST.

Явное использование HTTP-методов

Одной из ключевых характеристик Web-сервиса RESTful является явное использование HTTP-методов согласно протоколу, определенному в RFC 2616. Например, HTTP GET определяется как метод генерирования данных, используемый клиентским приложением для извлечения ресурса, получения данных с Web-сервера или выполнения запроса в надежде на то, что Web-сервер найдет и возвратит набор соответствующих ресурсов.

REST предлагает разработчикам использовать HTTP-методы явно в соответствии с определением протокола. Этот основной принцип проектирования REST устанавливает однозначное соответствие между операциями create, read, update и delete (CRUD) и HTTP-методами. Согласно этому соответствию:

  • Для создания ресурса на сервере используется POST.
  • Для извлечения ресурса используется GET.
  • Для изменения состояния ресурса или его обновления используется PUT.
  • Для удаления ресурса используется DELETE.

Недостатком проектирования многих Web API является использование HTTP-методов не по прямому назначению. Например, URI запроса в HTTP GET обычно определяет один конкретный ресурс, либо же строка запроса в URI запроса содержит ряд параметров, определяющих критерии поиска сервером набора соответствующих ресурсов. По крайней мере именно так описан метод GET в HTTP/1.1 RFC. Однако часто встречаются непривлекательные Web API, использующие HTTP GET для выполнения разного рода транзакций на сервере (например, для добавления записей в базу данных). В таких случаях URI запроса GET используется некорректно или, по крайней мере, не используется в REST-стиле (RESTfully). Если Web API использует GET для запуска удаленных процедур, запрос может выглядеть примерно так:

GET /adduser?name=Robert HTTP/1.1

Это неудачный дизайн, поскольку вышеупомянутый Web-метод поддерживает меняющую состояние операцию посредством HTTP-запроса GET. Иначе говоря, HTTP-запрос GET имеет побочные эффекты. В случае успешного выполнения запроса в хранилище данных будет добавлен новый пользователь (в нашем примере – Robert). Проблема здесь в основном семантическая. Web-серверы предназначены для ответов на HTTP-запросы GET путем извлечения ресурсов согласно URI запроса (или критерию запроса) и возврата их или их представления в ответе, а не для добавления записи в базу данных. С точки зрения предполагаемого использования и с точки зрения HTTP/1.1-совместимых Web-серверов такое использование GET является ненадлежащим.

Кроме семантики еще одной проблемой является то, что для удаления, изменения или добавления записи в базу данных или для изменения каким-либо образом состояния на стороне сервера GET привлекает различные средства Web-кэширования (роботы) и поисковые механизмы, которые могут выполнять непреднамеренные изменения на сервере путем простого обхода ссылки. Простым способом решения этой общей проблемы является помещение имен и значений параметров URI запроса в XML-теги. Эти теги (XML-представление создаваемого объекта) можно отправить в теле HTTP-запроса POST, URI которого является родителем объекта (см. листинги 1 и 2).

Листинг 1. До
GET /adduser?name=Robert HTTP/1.1
Листинг 2. После
POST /users HTTP/1.1
Host: myserver
Content-Type: application/xml
<?xml version="1.0"?>
<user>
  <name>Robert</name>
</user>

Это образец RESTful-запроса: HTTP-запрос POST используется корректно, а тело запроса содержит полезную нагрузку. На принимающей стороне в запрос может быть добавлен содержащийся в теле ресурс, подчиненный ресурсу, определенному в URI запроса; в данном случае новый ресурс должен добавляться как потомок /users. Такое отношение включения (containment) между новым логическим объектом и его родителем, указанное в запросе POST, аналогично отношению подчинения между файлом и родительским каталогом. Клиентское приложение устанавливает отношение между логическим объектом и его родителем и определяет URI нового объекта в запросе POST.

Затем клиентское приложение может получить представление ресурса, используя новый URI, указывающий, что по крайней мере логически ресурс расположен в /users (см. листинг 3).

Листинг 3. HTTP-запрос GET
GET /users/Robert HTTP/1.1
Host: myserver
Accept: application/xml

Это правильное применение запроса GET, поскольку он служит только для извлечения данных. GET – это операция, которая должна быть свободной от побочных эффектов. Данное свойство известно также под названием идемпотентность.

Аналогичный рефакторинг Web-метода необходимо выполнить в тех ситуациях, когда HTTP-запрос GET поддерживает операцию update (см. листинг 4).

Листинг 4. Операция update в HTTP-запросе GET
GET /updateuser?name=Robert&newname=Bob HTTP/1.1

Это запрос меняет атрибут (или свойство) name ресурса. Хотя для такой операции можно использовать строку запроса и в листинге 4 приведена самая простая из них, модель "строка запроса как сигнатура метода" не работает для более сложных операций. Поскольку нашей целью является явное использование HTTP-методов, по указанным выше причинам более RESTful-совместимым является подход, при котором для обновления ресурса применяется HTTP-запрос PUT вместо HTTP-запроса GET (см. листинг 5).

Листинг 5. HTTP-запрос PUT
PUT /users/Robert HTTP/1.1
Host: myserver
Content-Type: application/xml
<?xml version="1.0"?>
<user>
  <name>Bob</name>
</user>

Использование запроса PUT для замены исходного ресурса обеспечивает гораздо более прозрачный интерфейс, совместимый с принципами REST и с определением HTTP-методов. Запрос PUT в листинге 5 является явным в том смысле, что он указывает на обновляемый ресурс, определяя его в URI запроса, и передает новое представление ресурса от клиента на сервер в теле запроса PUT, вместо того чтобы передавать атрибуты ресурса в виде слабо связанного набора имен и значений параметров в URI запроса. Запрос в листинге 5 переименовывает ресурс с Robert на Bob и меняет его URI на /users/Bob. В Web-сервисе REST использование старого URI в последующих запросах ресурса приведет к возникновению стандартной ошибки 404 Not Found.

Общепринятым подходом, соответствующим рекомендациям REST по явному применению HTTP-методов, является использование в URI имен существительных вместо глаголов. В Web-сервисе RESTful глаголы POST, GET, PUT и DELETE уже определены протоколом. В идеале для реализации обобщенного интерфейса и явного вызова операций клиентскими приложениями Web-сервис не должен определять дополнительные команды или удаленные процедуры, например /adduser или /updateuser. Этот общий принцип применим также к телу HTTP-запроса, которое предназначено для передачи состояния ресурса, а не имени вызываемых удаленного метода или удаленной процедуры.

Несохранение состояния

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

При обработке полного самодостаточного запроса серверу не нужно извлекать состояние или контекст приложения. Приложение (или клиент) Web-сервиса REST включает в HTTP-заголовки и в тело запроса все параметры, контекст и данные, необходимые серверному компоненту для генерирования ответа. В этом смысле несохранение состояния (statelessness) улучшает производительность Web-сервиса и упрощает дизайн и реализацию серверных компонентов, поскольку отсутствие состояния на сервере устраняет необходимость синхронизации сеансовых данных с внешним приложением.

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

Рисунок 1. Модель с сохранением состояния
Рисунок 1. Модель с сохранением состояния
Рисунок 1. Модель с сохранением состояния

Подобные сохраняющие состояние сервисы получаются сложными. На платформе Java EE (Java Platform, Enterprise Edition) сохраняющие состояние сервисы требуют большого количества предварительных соглашений по эффективному хранению и синхронизации сеансовых данных в кластере Java EE-контейнеров. В средах такого типа разработчики сервлетов/страниц JSP (JavaServer Pages) и EJB-компонентов (Enterprise JavaBeans) сталкиваются с типичной проблемой поиска причин возникновения исключительной ситуации java.io.NotSerializableException при репликации сеанса. Независимо от источника (контейнер сервлетов при репликации HttpSession или EJB-контейнер при репликации сохраняющего состояние EJB-компонента) такая проблема может стоить разработчикам нескольких дней поисков в сложном графе объектов, определяющих состояние сервера, того единственного объекта, в котором реализуется Serializable. Кроме того, синхронизация сеансов увеличивает накладные расходы, что отрицательно сказывается на производительности сервера.

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

Рисунок 2. Модель без сохранения состояние
Рисунок 2. Модель без сохранения состояние
Рисунок 2. Модель без сохранения состояние

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

Сервер

  • Генерирует ответы, содержащие ссылки на другие ресурсы для навигации приложений по связанным ресурсам. Такие ответы содержат встроенные ссылки. Аналогичным образом при запросе родительского или контейнерного ресурса типичный RESTful-ответ может содержать ссылки на потомков родительского элемента или на подчиненные ресурсы, чтобы сохранять связь с ними.
  • Генерирует ответы, содержащие информацию о том, подлежат ли они кэшированию с целью повышения производительности за счет уменьшения количества запросов дублирующихся ресурсов и полного отказа от некоторых запросов. Для этого сервер включает в ответ HTTP-заголовки Cache-Control и Last-Modified (значение данных).

Клиентское приложение

  • По заголовку Cache-Control ответа определяет возможность кэширования ресурса (его локального копирования). Также клиентское приложение читает заголовок Last-Modified ответа и возвращает значение даты в заголовке If-Modified-Since для отправки на сервер запроса об изменении ресурса. Такой запрос, т.н. Conditional GET, использует оба заголовка. Если ресурс с указанного времени не изменился, ответом сервера является стандартный код 304 (Not Modified) и запрошенный ресурс не отправляется. Код ответа 304 HTTP означает, что клиентское приложение может спокойно использовать кэшированную локальную копию представления ресурса в качестве самой последней его версии, фактически опуская последующие запросы GET до тех пор, пока ресурс не будет изменен.
  • Отправляет полные запросы, которые могут обрабатываться независимо от других запросов. Это требует от клиентского приложения использования в полном объеме HTTP-заголовков, определенных интерфейсом Web-сервиса, и отправки полных представлений ресурсов в теле запроса. Клиентское приложение отправляет запросы, которые практически ничего не знают о предшествующих запросах, о существовании сеанса на сервере, о способности сервера добавлять контекст в запрос и о состоянии приложения, сохраняющемся между запросами.

Такая совместная работа клиентского приложения и сервиса очень важна для отказа от сохранения состояния в Web-сервисах RESTful. Результатом ее является рост производительности за счет уменьшения трафика и минимизации состояния серверного приложения.

Отображение URI, аналогичных структуре каталогов

С точки зрения обращения к ресурсам из клиентского приложения предоставляемые URI определяют, насколько интуитивным будет Web-сервис REST и будет ли он использоваться так, как предполагал разработчик. Третья характеристика Web-сервиса RESTful полностью посвящена URI.

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

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

http://www.myservice.org/discussion/topics/{topic}

Корень /discussion имеет нижестоящий узел /topics. Ниже располагаются названия тем (например, gossip (слухи), technology (технология) и т.д.), каждая из которых указывает на свою ветку обсуждения. В рамках данной структуры можно легко вызвать ветки обсуждения простым вводом чего-нибудь после /topics/.

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

Следующий пример интуитивно понятен, поскольку основан на правилах:

http://www.myservice.org/discussion/2008/12/10/{topic}

Первый фрагмент пути – четыре цифры года, второй – две цифры дня и третий – две цифры месяца. Подобное объяснение может показаться несколько упрощенным, но это именно тот уровень простоты, который нам нужен. Люди и компьютеры могут легко генерировать подобные структурированные URI, поскольку они основаны на правилах. Указание фрагментов пути в соответствующих позициях согласно синтаксису делает URI унифицированными, поскольку существует закономерность их создания:

http://www.myservice.org/discussion/{year}/{day}/{month}/{topic}

Приведем некоторые дополнительные рекомендации, на которые следует обратить внимание при обдумывании структуры URI для Web-сервисов RESTful:

  • Скрывайте расширения файлов серверных сценариев (.jsp, .php, .asp), если таковые используются, чтобы можно было выполнить портирование приложений на другую технологию без изменения URI.
  • Используйте только строчные буквы.
  • Заменяйте пробелы дефисами или знаками подчеркивания (чем-то одним).
  • Старайтесь максимально избегать использования строк запросов.
  • Вместо использования кода 404 Not Found для URI, указывающих неполный путь, всегда предоставляйте в качестве ответа ресурс или страницу по умолчанию.

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

Передача XML, JSON или обоих

Представление ресурса, как правило, отражает текущее состояние ресурса (и его атрибутов) на момент его запроса клиентским приложением. Представления ресурсов в этом смысле являются просто снимками состояния в конкретные моменты времени. Эти представления должны быть такими же простыми, как представление записи в базе данных, состоящее из отображения между именами столбцов и XML-тегами, где значения элементов в XML содержат значения строк. Если система имеет модель данных, то согласно этому определению представление ресурса является снимком состояния атрибутов одного из объектов модели данных системы. Это те объекты, которые будет обслуживать Web-сервис REST.

Последний набор ограничений, тесно связанный с дизайном Web-сервисов RESTful, относится к формату данных, которыми обмениваются приложение и сервис при работе в режиме запрос/ответ или в теле HTTP-запроса. Здесь особенно важны простота, читабельность и связанность.

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

Листинг 6. XML-представление ветки обсуждения
<?xml version="1.0"?>
<discussion date="{date}" topic="{topic}">
  <comment>{comment}</comment>
  <replies>
    <reply from="joe@mail.com" href="/discussion/topics/{topic}/joe"/>
    <reply from="bob@mail.com" href="/discussion/topics/{topic}/bob"/>
  </replies>
</discussion>

Наконец, чтобы предоставить клиентским приложениям возможность запрашивать конкретный наиболее подходящий им тип содержимого, проектируйте сервис так, чтобы он использовал встроенный HTTP-заголовок Accept, значение которого является MIME-типом. Некоторые общеупотребительные MIME-типы, используемые RESTful-сервисами, перечислены в таблице 1.

Таблица 1. Общеупотребительные MIME-типы, используемые RESTful-сервисами
MIME-типТип содержимого
JSONapplication/json
XMLapplication/xml
XHTMLapplication/xhtml+xml

Это позволит использовать сервис клиентским приложениям, написанным на разных языках и работающим на различных платформах и устройствах. Использование MIME-типов и HTTP-заголовка Accept представляет собой механизм согласования содержимого (content negotiation), позволяющий клиентским приложениям выбирать подходящий для них формат данных и минимизировать связность данных между сервисом и приложениями, его использующими.

Заключение

Технология REST – не панацея. Это способ проектирования Web-сервисов, менее зависимый от закрытого промежуточного программного обеспечения (например, сервер приложений), чем модели SOAP и WSDL. В некотором смысле REST, благодаря акценту на ранние интернет-стандарты URI и HTTP, является возвратом к Web до появления больших серверов приложений. Как вы увидели из рассмотрения принципов проектирования RESTful-интерфейса, XML поверх HTTP является мощным интерфейсом, позволяющим внутренним приложениям (например, пользовательским интерфейсам, основанным на технологии Ajax (Asynchronous JavaScript + XML)), легко подключаться и обращаться к ресурсам и потреблять их. Фактически именно хорошая совместимость Ajax и REST стала причиной сегодняшней популярности REST.

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=SOA и web-сервисы
ArticleID=1015191
ArticleTitle=Web-сервисы RESTful: основы
publish-date=02092015