→ Примеры post запросов к api rest. Подходы к проектированию RESTful API. Принципы разработки RESTful API

Примеры post запросов к api rest. Подходы к проектированию RESTful API. Принципы разработки RESTful API

От переводчика:
Я впервые попробовал перевести статью такого объёма и IT-тематики, с радостью прочту ваши комментарии и замечания. Что же касается самой статьи: я не согласен с автором как минимум потому, что, по сути, он заменяет REST на… REST (!!!), но немного в другом обрамлении. Однако, не смотря на то, что в статье преподносится много очевидных вещей, мне она показалась достойной обсуждения на Хабре.

Почему Вам стоит похоронить эту популярную технологию

RESTful api - это чудесно, ведь так?

Если за последние 10 лет Вы читали резюме веб-разработчиков, то Вам простительно думать, что RESTful API - это некое божественное дарование, сошедшее к нам с небес. REST API используется повсюду, даже маркетологи постоянно упоминают о нём в материалах, предназначенных сугубо для руководства или персонала.

Так на сколько всё же хороша идея REST API? Перед тем как мы разберемся с этим вопросом, давайте посмотрим откуда растут корни…

Откуда вообще взялся REST?

Данная технология стала популярной, когда она была подробно описана и представлена Роем Филдингом в его докторской диссертации под названием Architectural Styles and the Design of Network-based Software Architectures в 2000 году. Рой известен своими вкладами в развитие веба, в особенности HTTP.

Так что же такое RESTful API?

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

Таким образом, данные HTTP-запросы будут иметь различную смысловую нагрузку в REST:

  • GET /object/list
  • POST /object/list
  • PUT /object/list
Выше только некоторые виды запросов, а вот весь их список: CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE . Если вы даже не слышали о некоторых из них - не беда, так как есть методы, которые практически никогда не поддерживаются ни клиентским, ни серверным приложением.

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

Итак, одна транзакция по такому API будет состоять, как минимум, из следующего:

  • Метод запроса , например, GET
  • Путь запроса , например, /object/list
  • Тело запроса , например, форма
  • Код ответа , например, 200 ОК
  • Тело ответа , например, данные в формате JSON
Многие положительно отнеслись к такой парадигме и стали использовать её в разработке веб-сервисов с использованием HTTP. Это и есть то, что мы называем RESTful API .

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

На самом деле RESTful API довольно ужасно

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

Вскоре мы посмотрим на другое, более правильное решение для построения API, но, перед тем как сделать это, нам следует понять 5 главных проблем RESTful API, которые делают его дорогим, уязвимым к ошибкам и неудобным. Начнём!

Проблема №1: До сих пор нет общего согласования того, что такое RESTful API

Вряд ли кто-то задумывался над тем почему эта технология называется именно «RESTful», а не «RESTpure»? (прим. переводчика: pure - чёткий, понятный ) А потому что никто не может определиться с тем, что из себя представляют все методы запроса, коды ответа, тела и т.д.

Например, когда мы должны использовать код 200 ОК ? Можем ли мы использовать его для подтверждения успешного апдейта записи, или нам стоит использовать код 201 Created ? Судя по всему, нужно использовать код 250 Updated , однако его не существует. И еще, кто-нибудь может объяснить что означает код 417 Expectation failed ?! Кто-нибудь кроме Роя, конечно.

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

Если бы это было единственной проблемой, то я, наверное, смирился бы и продолжал писать RESTful API по сей день. Однако, наш список только раскрывается…

Проблема №2: Словарь REST поддерживается не полностью

Даже если бы мы решили первую проблему, то столкнулись бы со следующей, практической: большинство клиентских и серверных приложений поддерживают не все коды ответа и, собственно, глаголы, означающие HTTP-методы. Например, большинство браузеров имеют ограниченную поддержку PUT и DELETE.

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

  • Метод HTTP запроса , например, POST
  • Адрес запроса , например, /object/list
  • Метод, который мы на самом деле подразумеваем , например, DELETE
  • Тело запроса , например, данные из формы
Ситуация с кодами ответа не лучше. Разные браузеры (и серверные приложения тоже) часто понимают эти коды по-разному. Например, получив код 307 Temporary redirect , один браузер может позволить пользовательскому скрипту рассмотреть этот ответ и отменить действие до его выполнения. Другой браузер может просто напросто запретить скрипту делать что-либо. На самом деле, единственными кодами, обработки которых можно не бояться, являются 200 ОК и 500 Internal server error . В остальных же случаях поддержка ответов варьируется от «довольно хорошей» до «просто ужасной». Именно по-этому нам часто приходится дополнять тело ответа кодом, который мы на самом деле подразумевали .

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

Проблема №3: Словарь REST недостаточно насыщен

Словарь, состоящий только из HTTP методов и кодов ответа, является слишком ограниченным для эффективной передачи и приёма разнообразной информации, необходимой всем приложениям. Представьте, что мы создали приложение, из которого мы хотим отправить клиенту ответ «render complete». К сожалению, мы не можем сделать это с помощью HTTP кодов, так как, во-первых, такого кода не существует , а во-вторых мы не можем его создать, так как HTTP - не расширяемый . Минутка разочарования. Думаю нам снова придётся вставлять то, что мы подразумеваем в тело ответа.

Также проблема в том, что у нас не один словарь, у нас их три! Коды ответов - это числовые значения (200, 201, 500), которые отличаются от представления методов запроса (GET, POST, PUT и т.д.), а тело ответа и вовсе в формате JSON. Выполнение REST транзакций - это как отправка письма на английском языке в Китай и получение оттуда ответа морзянкой. Все эти сложности являются крупным источником путаницы и ошибок. Вот мы и перешли к следующей глобальной проблеме: дебаггинг.

Проблема №4: RESTful API очень трудно дебажить

Если Вы когда-то работали с REST API, то Вы наверняка в курсе, что его почти невозможно дебажить. Для того, чтобы понять то, что происходит во время транзакции, нам приходится просматривать сразу 7 мест:
  • Метод HTTP запроса , например, POST
  • Адрес запроса , например, /object/list
  • Метод, который мы на самом деле подразумеваем (в теле запроса) , например, DELETE
  • Собственно, тело запроса , например, данные из формы
  • Код ответа , например, 200 ОК
  • Код, который мы подразумевали (в теле ответа) , например, 206 Partial Content
  • Собственно, тело ответа
Так вот теперь у нас не только два сильно ограниченных словаря, так еще и 7 разных точек в которых может крыться ошибка. Единственное, что могло бы еще более усугубить ситуацию - это если бы технология REST была полностью привязана к одному протоколу и было бы невозможно использовать какой-либо другой канал связи. Собственно, так и есть, и это - наша следующая большая проблема!

Проблема №5: Как правило, RESTful API привязаны к протоколу HTTP

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

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

К счастью, есть хорошее решение, которое позволяет избежать либо минимизировать все проблемы RESTful API. Встречайте!

Шаг вперёд: JSON-pure API

JSON-pure API справляется с большинством проблем, которые мы только что рассмотрели.
  • Использует только один метод для передачи данных - обычно POST для HTTP и SEND в случае использования Web Sockets
  • Механизм передачи и содержимое запроса полностью независимы. Все ошибки, предупреждения и данные передаются в теле запроса, в формате JSON
  • Используется лишь один код ответа, чтобы подтвердить успешную передачу, обычно это 200 ОК
  • Механизм передачи и содержимое ответа полностью независимы. Все ошибки, предупреждения и данные передаются в теле ответа, в формате JSON
  • Гораздо проще дебажить, ведь все данные находятся в одном месте в легко-читаемом формате JSON
  • Легко перенести на любой канал связи, например, HTTP/S, WebSockets, XMPP, telnet, SFTP, SCP, or SSH
JSON-pure API появилось в следствии осознания разработчиками того факта, что RESTful API не особо дружелюбно к браузерам и самим разработчикам. Разделение сообщения и способа передачи делает JSON-pure API быстрым, надежным, простым в использовании, портировании и поиске ошибок. Сегодня, если нам понадобится, например, использовать API Твиттера, то мазохисты выберут RESTful API. Остальные же обратятся к JSON-pure API, или, как его еще называют, «Web API».

За последние десять лет меня не раз просили использовать RESTful вместо JSON-pure. Крайний раз, когда мне чуть было не пришлось поддерживать RESTful API, был в 2011 году. К моему счастью, бэк-енд команда согласилась параллельно с RESTful запустить JSON-pure API, просто перенеся все свои методы и коды в JSON.
Спустя несколько месяцев все мои знакомые, ранее использовавшие RESTful, перешли на JSON-pure, осознав, что это гораздо удобнее.

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

На одном из проектов нашей компании появилось необходимость спроектировать серверное приложение в стиле 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 - время, прошедшее с момента последнего получения ресурса, заголовок может быть добавлен промежуточным (между клиентом и сервером) компонентом (например, прокси сервер)

В данной заметке пример самого простого 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); } } Методы связанные с базой данных и получением данных из нее просто для примера.

Многие из вас уже наверняка знают о требованиях Джеффа Безоса к разработчикам в Amazon. Если вы не слышали об этом, ниже перечислены основные его положения:

  1. Отныне все группы разработчиков должны раскрывать свои данные и функциональные средства через сервисные интерфейсы;
  2. Группы разработчиков должны связываться друг с другом с помощью этих интерфейсов;
  3. Неважно, какую технологию используют разработчики: HTTP, Cobra, Pubsub, встроенные протоколы;
  4. Больше никаких форм межпроцессного взаимодействия не допускается: никаких прямых ссылок и считываний данных других групп, никакой модели общей памяти, никаких «лазеек». Единственная разрешенная форма связи осуществляется через служебные интерфейсы по сети;
  5. Все без исключения сервисные интерфейсы с самого начала должны проектироваться выгружаемыми. Это значит, что группа должна планировать интерфейс так, чтобы его можно было раскрыть остальным разработчикам. Исключения не допускаются;
  6. Все, кто не слушаются этих правил, будут уволены.

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

Принципы разработки RESTful API

Для разработки RESTful API нужно следовать следующим принципам:

Простота

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

/products /products/12345

Первый запрос к API - для всех продуктов, второй - для специфического продукта.

Используйте существительные, а не глаголы

Многие разработчики совершают эту ошибку. Обычно они забывают, что у нас есть HTTP методы для лучшего описания API, и в итоге используют глаголы в URL. Например, запрос для получения всех продуктов звучит так:

/products

А не так:

/getAllProducts

Так часто поступают при создании URL.

Правильные HTTP методы

В RESTful API существуют различные методы, которые описывают тип операции, которую будет осуществлять API.

  • GET - для получения ресурса или группы ресурсов;
  • POST - для создания ресурса или группы ресурсов;
  • PUT/PATCH - для обновления уже существующего ресурса или группы ресурсов;
  • DELETE - для удаления уже существующего ресурса или группы ресурсов.

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

Не забывайте о множественном числе

Эта тема еще стоит под вопросом. Некоторым нравится называть URL ресурсов во множественном числе, некоторым - в единственном. Пример:

/products /product

Мне нравится использовать множественное число, потому что в таком случае не создается путаница: работаем ли мы с одним ресурсом или с группой ресурсов? Также не нужно дополнять базовые URL, например, добавлением all: /product/all .

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

Параметры

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

  • нужно использовать /products?name="ABC" , а не /getProductsByName ;
  • нужно использовать /products?type="xyz" , а не /getProductsByType .

В таком случае URL не будут слишком длинными, а структура останется простой.

Правильные HTTP коды

Существует множество HTTP кодов. Многие из нас используют только два из них: 200 и 500 ! Это плохая методика. Ниже перечислены часто используемые HTTP коды:

  • 200 OK - самый часто используемый код, свидетельствующий об успехе операции;
  • 201 CREATED - используется, когда с помощью метода POST создается ресурс;
  • 202 ACCEPTED - используется, чтобы сообщить, что ресурс принят на сервер;
  • 400 BAD REQUEST - используется, когда со стороны клиента допущена ошибка в вводе;
  • 401 UNAUTHORIZED / 403 FORBIDDEN - используются, если для выполнения операции требуется аутентификация пользователя или системы;
  • 404 NOT FOUND - используется, если в системе отсутствуют искомые ресурсы;
  • 500 INTERNAL SERVER ERROR - это никогда не используется просто так - в таком случае произошла ошибка в системе;
  • 502 BAD GATEWAY - используется, если сервер получил некорректный ответ от предыдущего сервера.

Версии

Версии API - важная вещь. Различные компании используют версии по-разному: кто-то - как даты, кто-то - как параметры запросов. Мне нравится указывать версии до названия ресурса. Пример:

/v1/products /v2/products

Мне также кажется, что стоит избегать использования /v1.2/products , так как это подразумевает, что API часто меняется. К тому же, точки в URL не так легко заметить. Так что чем проще, тем лучше.

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

Разбиение на страницы

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

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

В таком случае стоит использовать limit и offset . Пример: /products?limit=25&offset=50 . Также стоит установить эти настройки по умолчанию.

Поддерживаемые форматы

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

Верные сообщения об ошибках

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

{ "error": { "message": "(#803) Some of the aliases you requested do not exist: products", "type": "OAuthException", "code": 803, "fbtrace_id": "FOXX2AhLh80" } }

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

Open API Specification

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

Заключение

Довольно очевидно, что для лучшей связи отлично подойдут 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

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

 

 

Это интересно: