RESTful API — большая ложь. REST API - что это? REST: перевод

В данной заметке пример самого простого REST API на PHP без использования какого-либо фреймворка и других средств. Целью есть предоставить общую картину - как это все работает.
Недавно я уже опубликовал , в которой описан процесс создания REST API для проекта на Yii2 .

Т.к. никакие фреймворки с маршрутизаторами в примере использоваться не будут, нужно начать с перенаправления всех запросов на "точку входа" - файл index.php. Для сервера на Apache это можно сделать в файле.htaccess который должен располагаться в корне проекта:
Options +FollowSymLinks IndexIgnore */* RewriteEngine on # Перенаправление с ДОМЕН на ДОМЕН/api RewriteCond %{REQUEST_URI} ^/$ RewriteRule ^(.*)$ /api/$1 #Если URI начинается с api/ то перенаправлять все запросы на index.php RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^api/(.*)$ /index.php
Согласно правил, ссылка должна начинаться на /api и,например, для API работающего с таблицей users должна иметь такой вид:
ДОМЕН/api/users

Пример файла index.php
run(); } catch (Exception $e) { echo json_encode(Array("error" => $e->getMessage())); }
Как видно из кода - будем работать с объектом usersApi , т.е. с пользователями (таблица users). Т.к. для простоты примера я не использую тут Composer или другой механизм для автозагрузки классов, просто подключим файл класса с помощью
require_once "UsersApi.php";
Кроме пользователей, может потребоваться сделать api и для других сущностей, поэтому все классы различных API должны иметь один общий костяк, который будет определять метод запроса, действие для выполнения и тд. Создаем файл Api.php c абстрактным классом Api :
requestUri = explode("/", trim($_SERVER["REQUEST_URI"],"/")); $this->requestParams = $_REQUEST; //Определение метода запроса $this->method = $_SERVER["REQUEST_METHOD"]; if ($this->method == "POST" && array_key_exists("HTTP_X_HTTP_METHOD", $_SERVER)) { if ($_SERVER["HTTP_X_HTTP_METHOD"] == "DELETE") { $this->method = "DELETE"; } else if ($_SERVER["HTTP_X_HTTP_METHOD"] == "PUT") { $this->method = "PUT"; } else { throw new Exception("Unexpected Header"); } } } public function run() { //Первые 2 элемента массива URI должны быть "api" и название таблицы if(array_shift($this->requestUri) !== "api" || array_shift($this->requestUri) !== $this->apiName){ throw new RuntimeException("API Not Found", 404); } //Определение действия для обработки $this->action = $this->getAction(); //Если метод(действие) определен в дочернем классе API if (method_exists($this, $this->action)) { return $this->{$this->action}(); } else { throw new RuntimeException("Invalid Method", 405); } } protected function response($data, $status = 500) { header("HTTP/1.1 " . $status . " " . $this->requestStatus($status)); return json_encode($data); } private function requestStatus($code) { $status = array(200 => "OK", 404 => "Not Found", 405 => "Method Not Allowed", 500 => "Internal Server Error",); return ($status[$code])?$status[$code]:$status; } protected function getAction() { $method = $this->method; switch ($method) { case "GET": if($this->requestUri){ return "viewAction"; } else { return "indexAction"; } break; case "POST": return "createAction"; break; case "PUT": return "updateAction"; break; case "DELETE": return "deleteAction"; break; default: return null; } } abstract protected function indexAction(); abstract protected function viewAction(); abstract protected function createAction(); abstract protected function updateAction(); abstract protected function deleteAction(); }
Осталось реализовать абстрактные методы и свойство $apiName , которое уникально для каждого отдельного API. Для этого создаем файл UsersApi.php :
getConnect(); $users = Users::getAll($db); if($users){ return $this->response($users, 200); } return $this->response("Data not found", 404); } /** * Метод GET * Просмотр отдельной записи (по id) * http://ДОМЕН/users/1 * @return string */ public function viewAction() { //id должен быть первым параметром после /users/x $id = array_shift($this->requestUri); if($id){ $db = (new Db())->getConnect(); $user = Users::getById($db, $id); if($user){ return $this->response($user, 200); } } return $this->response("Data not found", 404); } /** * Метод POST * Создание новой записи * http://ДОМЕН/users + параметры запроса name, email * @return string */ public function createAction() { $name = $this->requestParams["name"] ?? ""; $email = $this->requestParams["email"] ?? ""; if($name && $email){ $db = (new Db())->getConnect(); $user = new Users($db, [ "name" => $name, "email" => $email ]); if($user = $user->saveNew()){ return $this->response("Data saved.", 200); } } return $this->response("Saving error", 500); } /** * Метод PUT * Обновление отдельной записи (по ее id) * http://ДОМЕН/users/1 + параметры запроса name, email * @return string */ public function updateAction() { $parse_url = parse_url($this->requestUri); $userId = $parse_url["path"] ?? null; $db = (new Db())->getConnect(); if(!$userId || !Users::getById($db, $userId)){ return $this->response("User with id=$userId not found", 404); } $name = $this->requestParams["name"] ?? ""; $email = $this->requestParams["email"] ?? ""; if($name && $email){ if($user = Users::update($db, $userId, $name, $email)){ return $this->response("Data updated.", 200); } } return $this->response("Update error", 400); } /** * Метод DELETE * Удаление отдельной записи (по ее id) * http://ДОМЕН/users/1 * @return string */ public function deleteAction() { $parse_url = parse_url($this->requestUri); $userId = $parse_url["path"] ?? null; $db = (new Db())->getConnect(); if(!$userId || !Users::getById($db, $userId)){ return $this->response("User with id=$userId not found", 404); } if(Users::deleteById($db, $userId)){ return $this->response("Data deleted.", 200); } return $this->response("Delete error", 500); } } Методы связанные с базой данных и получением данных из нее просто для примера.

В данной статье я расскажу Вам о том, что такое REST API . Также мы затронем тему HTTP протокола. А также рассмотрим пример архитектурного дизайна REST API.

Немного теории

О том, что такое API, я подробно рассказывал . Напомню, что API – это некий набор правил, с помощью которых приложение или какой-либо один его компонент могут взаимодействовать, общаться, если хотите, с другим приложением или компонентом. Прикладной интерфейс программирования (API ) может возвращать данные в разных форматах, например в JSON , XML или в бинарном формате, но в REST API мы будем использовать JSON -формат, как наиболее удобный.

Давайте посмотрим на пример. Возможно, Вы уже знакомы с тем, что такое система контроля версий Git . Ее web-версия – это Github . Так вот, у Github есть собственное API , с помощью которого можно получить какую-либо полезную информацию, например о пользователях и организациях, их проектах, и т.д. Давайте взглянем на пример:

Curl https://api.github.com/orgs/Yandex

В этом примере мы используем консольную утилиту curl для того, чтобы получить данные через API . Ее можно загрузить с официального сайт проекта . Она позволяет делать все то же самое что и расширение curl в PHP , только для этого не нужно писать код, так как вся функциональность доступна посредством интерфейса командной строки. Вообще, незаменимая вещь для тестирования различных прикладных интерфейсов . Есть еще альтернатива в виде расширения для Chrome – Postman .

Данная команда вернет нам большой JSON-объект , содержащий различные данные о компании.

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

Если говорить еще проще то, REST – это архитектурный стиль, а RESTful API – это его практическое воплощение, и чем больше приложение отвечает критериям стиля REST , тем более оно RESTful .

RESTful API сводится к четырем базовым операциям:

  • получение данных в удобном для клиента формате
  • создание новых данных
  • обновление данных
  • удаление данных

REST функционирует поверх протокола HTTP , поэтому стоит упомянуть о его основных особенностях. Для каждой операции указанной выше используется свой собственный HTTP метод :

  • GET – получение
  • POST – создание
  • PUT – обновление, модификация
  • DELETE – удаление

Все эти методы в совокупности называют CRUD (create, read, update, delete) – (создать, прочитать, обновить, удалить) операциями.

Фактически в REST существует единственный, непротиворечивый общий интерфейс для запросов, например, к базам данных, что является его важнейшим преимуществом. На следующей картинке показано соответствие HTTP методов SQL операциям и концепции CRUD .

Т.е. HTTP метод POST соответствует SQL операции INSERT, метод GET – операции SELECT и т.д .

Для каждого HTTP запроса есть свой статус. И они нужны, чтобы грамотно с точки зрения REST API оформить ответ и отдать клиенту. Статусов много, поэтому их всех не перечислить, однако важно знать их группировку:

  • 100 – 199 – это статусы несущие информационный характер
  • 200 - 299 – статусы успешной операции
  • 300 – 399 – статусы перенаправления (редиректа)
  • 400 – 499 – статусы ошибок на стороне клиента
  • 500 – 599 – статусы ошибок на стороне сервера

Вообще, как делается API . Создается некая точка входа для запросов, api.php , например. Этому API , могут передаваться, например, такие запросы:

  • http://site.com/api.php?action=create.user&id=1&key=46syhy77sash
  • http://site.com/api.php?action=delete.user&id=1&key=46syhy77sash
  • http://site.com/api.php?action=get.user&id=1&key=46syhy77sash

где параметр

  • action – это действие, которое необходимо выполнить
  • id – идентификатор пользователя
  • кey – ключ доступа (фактически, временный пароль)

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

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

Таким образом, REST API призван создать четко структурированный подход в написании прикладных интерфейсов. Так, как с каждым днем становится все больше и больше данных, к которым необходимо открыть доступ.

Создавая веб-сервис, нужно учитывать многое как со стороны бизнеса, так и стороны разработки. Клиент очерчивает требования — команда разработчиков их выполняет.

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

Понять на самом базовом уровне, что такое REST и зачем он нужен, можно из статьи Райана Томайко “Как я объяснил жене, что такое REST”. Тем не менее, мы скорее подойдем к вопросу с технической стороны. Итак, приступим.

Что такое REST?

Representational state transfer (передача состояния управления) — это архитектурный стиль, применяемый при разработке веб-сервисов, и устанавливающий 6 правил для их построения.

Соблюдающие эти правила веб-сервисы называют RESTful сервисами. Помимо этого, REST также требует использование большинства возможностей протокола http.

Итак, что же это за правила?

  • Uniform Interface — единый интерфейс
  • Stateless — отсутствие состояний
  • Cacheable — кэширование
  • Client-Server — разграниченная архитектура клиент-сервер
  • Layered System — многоуровневая система
  • Code on Demand — код по запросу

Поговорим о каждом из них более подробно.

Единый интерфейс

REST архитектура подразумевает определение единого интерфейса взаимодействия всех клиентов с сервером.

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

Сам интерфейс должен разделять работу с разными ресурсами приложения по разным URL-адресам. Благодаря правильно выполненному разделению запросов, появляется возможность для лёгкого разделения одного веб-приложения на несколько, а это, в свою очередь, значительно облегчает портируемость и масштабируемость приложения.

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

Также REST требует, чтобы названия ресурсов представляли собой существительные во множественном числе. Не следует перегружать систему множеством URL. Каждый ресурс в самом простом случае должен иметь только 2 URL адреса. Например:

  • Формат для получения коллекции элементов: /buildings
  • Формат для получения элемента по id: /buildings/{id}

Информация передаваемая между клиентами и сервером должна быть конвертирована в удобный для передачи формат. Примерами таких форматов может быть JSON или XML, хотя всё же наиболее популярным на текущий момент является как раз JSON.

Тем не менее, некоторые веб-сервисы умеют поддерживать работу с несколькими форматами одновременно.

В таком случае формат возвращаемых данных сервером и формат данных, который сервер должен обработать, управляется с помощью HTTP заголовков Accept и Content-type. В заголовках могут передаваться разные типы. Например: application/json или application/xml.

Отсутствие состояний

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

Кэширование

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

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

Клиент-сервер

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

  • клиенты не должны иметь представления о хранимой на сервере информации, так как это зона ответственности сервера.
  • Сервер не должен быть разработан в привязке к UI какого-либо клиента.

Layered system

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

Code on demand

Применение данного правила опционально. Его суть заключается в том, что сервер может передавать выполняемый на стороне клиента код Javascript, который позволяет временно расширить, или модифицировать функционал клиента.

Почему именно REST?

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

  • Улучшенная производительность. REST возвращает исключительно данные, указанные в запросе. Таким образом быстрее обрабатываются запросы и выдаются ответы.
  • Масштабируемость. Приложение может работать на множестве серверов, что позволяет балансировать нагрузку между ними. Также сервис можно разделить на несколько “микросервисов”, которые смогут работать параллельно друг другу.
  • Портируемость благодаря единому интерфейсу
  • Прозрачность взаимодействия — за счёт своей стандартизации API остаётся понятным для пользователя.
  • Легкость изменений. Благодаря меньшей связанности кода, снижается вероятность “поломать” запросы при внесении изменений в другие части приложения.

Методы http

Http содержит 4 метода: GET, POST, PUT, DELETE. Каждый метод должен использоваться для разных целей и идентифицировать функционал, который запрос реализует.

  • GET. Используется для получения ресурсов (список ресурсов или один ресурс с указанием его id)
  • POST. используется для создания ресурсов
  • PUT. Ииспользуется для обновления ресурсов по id, который передается в URL
  • DELETE. Используется для удаления ресурсов по id, который передается в URL

Ниже мы привели таблицу, содержащуя возможные запросы для потенциального ресурса people. В ней использованны все возможные http-методы и приведены ответы, которые эти запросы могут возвращать.

Http статус-коды

При использовании REST для веб-сервисов необходимо правильно подобрать http статусы для соответсвуюших ответов сервера. Сам по себе http имеет несколько десятков статус-кодов, но мы приведём 10 наиболее часто используемых:

  1. 200 OK — хорошо
  2. 201 Created — создано
  3. 204 No Content — нет содержимого
  4. 304 Not modified — не изменялось
  5. 400 Bad request — неверный запрос
  6. 401 Unauthorized — не авторизован
  7. 403 Forbidden — запрещён
  8. 404 Not found — не найдено
  9. 409 Conflict — конфликт
  10. 500 Internal Server error — внутренняя ошибка сервера

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

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

Дополнительно

  • Запросы, возвращающие коллекции элементов, должны иметь возможность пагинации, сортировки, фильтрации
  • Даты и время в запросах следует передавать в формате unix timestamp в миллисекундах.
  • Версия приложения должна быть зашита в URL приложения как node. Например: api.app.com/v1/buildings

Если у вас остались какие-либо вопросы, вы можете связаться с нами для получения профессиональной технической консультации. Мы будем рады помочь вам с реализацией любых ваших идей!

Некоторые скажут:

«Это API , который использует HTTP-запросы для

GET, PUT, POST и DELETE ".

Многие также скажут:

«Речь идет об определении ресурсов с помощью URI»

Другие будут кричать:

В заключение можно сказать:

«В принципе, это просто API , который использует HTTP правильно!»

Некоторые из этих утверждений ошибочны,

Некоторые частично верны,

но это не имеет значения,

Потому что они все упускают суть .

Чтобы иметь возможность разрабатывать API RESTful,

Во-первых,

Что такое REST?

Что такое REST?

REST это:

    Определенно не HTTP

    Не протокол

    Не спецификация

Вот почему вокруг REST APIs так много споров.

И всё-таки...

REST это стиль архитектуры.

О, хорошо… но что такое стиль архитектуры?

Стиль архитектуры

Желаемая архитектура

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

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

REST выступает за представление состояния передачи

Это было записано Роем Филдингом в его докторской диссертации в 2000 году, где он описал существующую и современную веб-архитектуру как абстракцию.

Название было призвано вызывать представление о том, как ведет себя хорошо разработанное веб-приложение.

Рой описал REST на простом примере:

Рассмотрим сеть веб-страниц как виртуальную машину состояний.

Каждая страница представляет состояние:

1. Во-первых, пользователь получает первое состояние в виде индексного состояния.

2. Затем пользователь переходит через приложение, выбирая ссылку (здесь ссылка на страницу)

3. Результат передачи следующего состояния пользователю.

REST все еще не HTTP

Конечно, еще в 2000 году сеть уже работала на HTTP, и Рой со своими коллегами-инженерами много работали над этим.

Однако REST не определяет конкретные детали реализации системы и не определяет какой-либо синтаксис протокола.

Вполне возможно иметь архитектуру RESTful поверх протоколов, отличных от HTTP.

Например, CoAP (протокол ограниченного применения) является протоколом RESTful для встроенных устройств (Internet of Things) и предназначен для использования минимальных ресурсов как на устройстве, так и в сети.

Так зачем использовать REST?

Всемирная паутина основана на архитектуре REST.

Поэтому, если вы создаете API-интерфейс non-RESTful, который будет использоваться в Интернете, то вы получите неоптимальную систему.Не оптимальный в отношении оптимизированной архитектуры.

Это важно отметить, поскольку не-RESTful API может быть неоптимальным в сетевой архитектуре, но оптимальным для других проблем. Например, современные интерфейсные приложения могут иметь очень специфические потребности, следовательно, растет число библиотек сбора данных, таких как GraphQL или Falcor.

Итак, когда это API RESTful?

API является RESTful, когда он постоянно действует под ограничениями REST.

REST определяет 6 ограничений для достижения желаемой оптимизации системы:

1. Клиент-сервер

Это ограничение основано на принципе разделения интересов.

Это позволяет компонентам развиваться независимо. Создавая наш API, он действует как сервер, обслуживающий большое количество клиентов.

2. Без гражданства

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

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

Наиболее эффективным сетевым запросом является тот, который не использует сеть.

Когда мы создаем наш API, он не должен игнорировать кеширование.

4. Правильный интерфейс

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

4.1. Идентификация ресурсов

Это означает, что любая информация, которая может быть названа, может быть ресурсом (изображение, документ или даже набор других ресурсов)

4.2. Манипулирование ресурсами через представления

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

Например, HTML, XML, JSON или даже JPEG-файл.

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

4.3 В-третьих, самоописательные сообщения

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

4.4. Hypermedia должна быть двигателем состояния приложения

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

Как вы можете видеть, многие из этих правил могут быть реализованы в протоколе HTTP. Поэтому, когда API использует HTTP правильно, это огромный шаг к тому, чтобы стать RESTful.

5. Многоуровневая система

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

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

6. Код по требованию

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

Итак, как создать REST API?

Правильно использовать HTTP

Если вы строите RESTful API, используйте протокол RESTful. Для Интернета HTTP-протокол является определенным выбором. Создайте свой API, чтобы он правильно использовал HTTP.

Создайте единый интерфейс

Сопоставьте свои концепции с ресурсами и назначьте соответствующие идентификаторы для каждого из них. Простым примером может служить служба базы данных пользователей. В такой службе мы можем назвать два ресурса; Пользователей и пользователей (ресурс сбора). Эти ресурсы могут быть идентифицированы с URI / users и / user / {id} URI вашего интерфейса API.

Управляйте своим API-гиперссылками

Помните об архитектуре REST

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

Если у вас появились какие-либо вопросы, приглашаем на наши

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

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

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

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

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

Определение

Для начала нужно определиться, что же такое REST. Википедия даёт на этот вопрос следующий ответ. REST (Representational State Transfer - «передача состояния представления») - архитектурный стиль взаимодействия компонентов распределённого приложения в сети. REST представляет собой согласованный набор ограничений, учитываемых при проектировании распределённой гипермедиа-системы.

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

Также нужно понимать, что такое REST-сервис. Я бы дал определение REST-сервису, как “точка взаимодействия клиентского приложения с сервером”. Говоря Java терминологией - это сервлет, на который клиент посылает запрос.

Проблематика

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

Название сервиса

Для начала необходимо выбрать имя для REST сервиса. Под именем сервиса я подразумеваю его путь в URI запросе. Например, http://my-site.by/api/rest/service/name . Для выбора имени нам нужно понимать что такое “ресурсы” в архитектуре REST.

Представление ресурса

В терминологии REST что угодно может быть ресурсом - HTML-документ, изображение, информация о конкретном пользователе и т.д. Если ресурс представляет собой некоторый объект, его легко представить, используя некоторый стандартный формат, например, XML или JSON. Далее сервер может отправить данный ресурс, используя выбранный формат, а клиент сможет работать с полученным от сервера ресурсом, используя этот же формат.

Пример представления ресурса “профиль” в формате JSON:

    "id" :1 ,

    "name" :"Mahesh" ,

    "login" :"manesh"

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

  • Клиент и сервер должны “понимать” и иметь возможность работать с выбранным форматом.
  • Ресурс можно полностью описать, используя выбранный формат независимо от сложности ресурса.
  • Формат должен предусматривать возможность представления связей между ресурсами.

Пример представления ресурса “заказ” и его связи с ресурсом “профиль”:

    id: 11254 ,

    currency: "EUR" ,

    amount: 100 ,

    profile: {

    id: 11 ,

    uri: "http://MyService/Profiles/11"

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

Обращение к ресурсу

Каждый ресурс должен быть уникально обозначен постоянным идентификатором. «Постоянный» означает, что идентификатор не изменится за время обмена данными, и даже когда изменится состояние ресурса. Если ресурсу присваивается другой идентификатор, сервер должен сообщить клиенту, что запрос был неудачным и дать ссылку на новый адрес. Каждый ресурс однозначно определяется URL. Это значит, что URL по сути является первичным ключом для единицы данных. То есть, например, вторая книга с книжной полки будет иметь вид /books/2 , а 41 страница в этой книге - /books/2/pages/41 . Отсюда и получается строго заданный формат. Причем совершенно не имеет значения, в каком формате находятся данные по адресу /books/2/pages/41 – это может быть и HTML, и отсканированная копия в виде jpeg-файла, и документ Word.

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

Для сервисов, которые выполняют какие-то специфические действия над ресурсом, есть 2 подхода для указания действия: в имени сервиса или в его параметрах. Например, /books/3/clean или /books/3?clean . Я предпочитаю первый вариант, так как обычно такие сервисы не редко используют POST методы, которые не поддерживают передачу параметров в URl, что делает сервис, на мой взгляд, не очень читабельным. Используя определение типа действия в имени сервиса, мы делаем наш сервис более расширяемым, так как он не зависит от типа HTTP метода.

Также очень не рекомендуется использовать имена, включающие в себя несколько слов и описывающие бизнес составляющую сервиса (как это рекомендуется делать при именовании java методов). Например, вместо /getAllCars лучше сделать метод /cars . Если же метод нельзя никак описать одним словом, то необходимо применять единый стиль разделителей, я обычно использую ‘-’, что является наиболее популярным подходом. Например, /cars/3/can-sold.

Более подробно о проектировании названий REST-сервисов можно прочитать в

HTTP методы

В REST используются 4 основных HTTP метода: GET, POST, PUT, DELETE. В большинстве случаев каждый из методов служит для выполнения предопределённого ему действия из CRUD (c reate, r ead, u pdate, d elete - «создание, чтение, обновление, удаление» ).
POST - create, GET - read, PUT - update, DELETE - delete.

ВАЖНОЕ ДОПОЛНЕНИЕ: Существуют так называемые REST-Patterns, которые различаются связыванием HTTP-методов с тем, что они делают. В частности, разные паттерны по-разному рассматривают POST и PUT. Однако, PUT предназначен для создания, замены или обновления, для POST это не определено (The POST operation is very generic and no specific meaning can be attached to it). Поэтому иногда POST и PUT можно поменять местами. Но в большинстве случаев POST используют для создания, а PUT для редактирования, и чуть позже я объясню почему.

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

  • GET /books/ – получает список всех книг. Как правило, это упрощенный список, т.е. содержащий только поля идентификатора и названия объекта, без остальных данных.
  • GET /books/{id} – получает полную информацию о книге.
  • POST /books/ – создает новую книгу. Данные передаются в теле запроса.
    PUT /books/{id} – изменяет данные о книге с идентификатором {id}, возможно заменяет их. Данные также передаются в теле запроса.
  • OPTIONS /books – получает список поддерживаемых операций для указанного ресурса (практически не используется)
  • DELETE /books/{id} – удаляет данные с идентификатором {id}.

Безопасность и идемпотентность

Очень помогут в выборе HTTP метода знания о безопасности и идемпотентности этих методов.

Безопасный запрос - это запрос, который не меняет состояние приложения.

Идемпотентный запрос - это запрос, эффект которого от многократного выполнения равен эффекту от однократного выполнения.

Судя по данной таблице, GET-запрос не должен менять состояние ресурса, к которому применяется. PUT и DELETE запросы могут менять состояние ресурса, но их можно спокойно повторять, если нет уверенности, что предыдущий запрос выполнился. В принципе, это логично: если многократно повторять запрос удаления или замены определенного ресурса, то результатом будет удаление или замена ресурса. Но POST запрос, как мы видим из таблицы, небезопасный и неидемпотентный. То есть мало того, что он меняет состояние ресурса, так и многократное его повторение будет производить эффект, зависимый от количества повторений. Ему по смыслу соответствует операция добавления новых элементов в БД: выполнили запрос Х раз, и в БД добавилось Х элементов.

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

HTTP коды

В стандарте HTTP описано более 70 статус кодов. Хорошим тоном является использование хотя бы основных.

  • 200 – OK – успешный запрос. Если клиентом были запрошены какие-либо данные, то они находятся в заголовке и/или теле сообщения.
  • 201 – OK – в результате успешного выполнения запроса был создан новый ресурс.
  • 204 – OK – ресурс успешно удалён.
  • 304 – Not Modified – клиент может использовать данные из кэша.
  • 400 – Bad Request – запрос невалидный или не может быть обработан.
  • 401 – Unauthorized – запрос требует аутентификации пользователя.
  • 403 – Forbidden – сервер понял запрос, но отказывается его обработать или доступ запрещен.
  • 404 – Not found – ресурс не найден.
  • 500 – Internal Server Error – разработчики API должны стараться избежать таких ошибок.

Эти ошибки должны быть отловлены в глобальном catch-блоке, залогированы, но они не должны быть возвращены в ответе.

Чем обширнее набор кодов, который мы будем использовать, тем более понятный будет API, который мы создаём. Однако нужно учесть, что некоторые коды браузеры обрабатывают по-разному. Например, некоторые браузеры получив код ответа 307 сразу же выполняют редирект, а некоторые позволяют обработать такую ситуацию и отменить действие. Прежде чем использовать тот или иной код, необходимо полностью понимать, как он будет обрабатываться на клиентской стороне!

Headers

  • Content-Type - формат запроса;
  • Accept - список форматов ответа.

Параметры поиска ресурсов

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

Фильтрация

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

Например, чтобы вывести все красные книги необходимо выполнить запрос:

GET /books?color=red

Сортировка

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

GET /books?sort=-year,+name

Пагинация

Для того, чтобы поддержать возможность загрузки списка ресурсов, которые должны отображаться на определённой странице приложения, в REST API должен быть предусмотрен функционал пагинации. Реализуется он с помощью знакомых нам по SQL параметрам limit и offset. Например:

GET /books?offset=10&limit=5

Помимо того хорошим тоном является вывод ссылок на предыдущую, следующую, первую и последнюю страницы в хидере Link. Например:

Link: ; rel="next",
; rel="last",
; rel="first",
; rel="prev"

Выбор полей ресурса

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

GET /books?fields=id,color

Хранение состояния

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

Пример сервиса, не хранящего состояние:
Request1:
Request2: GET http://MyService/Persons/2 HTTP/1.1

Каждый из этих запросов может быть обработан независимо от другого.

Пример сервиса, хранящего состояние:
Request1: GET http://MyService/Persons/1 HTTP/1.1
Request2: GET http://MyService/NextPerson HTTP/1.1

Чтобы обработать второй запрос, серверу потребуется “запомнить” id последнего человека, который был запрошен клиентом. Т.е. сервер должен “запомнить” свое текущее состояние, иначе второй запрос не может быть обработан. При проектировании сервиса, следует избегать необходимости в хранении состояния, так как это имеет ряд преимуществ.

Преимущества сервиса, не хранящего состояние:

  • сервис обрабатывает запросы независимо друг от друга;
  • архитектура сервиса упрощается;
  • не требуется дополнительных усилий для реализации сервисов с использованием протокола HTTP, который также не хранит состояния.

Недостатки сервиса, не хранящего состояние:

  • клиент сам должен отвечать за передачу необходимого контекста сервису.

Версионность

Хорошим тоном является поддержка версионности REST API. Это позволит в дальнейшем легко расширять API, без обязательного внесения изменений в клиенты, которые уже пользуются им.
Имеются несколько подходов реализации версионности:

  • С использованием Accept хидера. В данном случае версия API указывается в Accept - Accept:text/v2+json
  • С использованием URI. В таком подходе версия API указывается прямо в URI - http://localhost/api/v2/books
  • Использование кастомного хидера. Можно использовать собственный хидер, который будет отвечать только за передачу версии API - API-Version:v2
  • Использование параметра запроса. Можно использовать параметр запроса для передачи версии API - /books?v=2

Каждый из представленных способов имеет право на существование, у каждого есть свои плюсы и минусы. Однако только Вам решать, какой способ реализации версионности подойдёт Вашему проекту.

Документация

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

Swagger - это технология, которая позволяет документировать REST-сервисы. Swagger поддерживает множество языков программирования и фреймворков. Плюс, Swagger предоставляет UI для просмотра документации.

Получить более подробную информацию о Swagger можно по данной .

Архивирование

Кэширование

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

Кэшированием можно управлять используя следующие HTTP заголовки:

  • Date - дата и время создания ресурса.
  • Last Modified - дата и время последнего изменения ресурса на сервере.
  • Cache-Control - заголовок HTTP 1.1 используемый для управления кэшированием.
  • Age - время, прошедшее с момента последнего получения ресурса, заголовок может быть добавлен промежуточным (между клиентом и сервером) компонентом (например, прокси сервер)