От автора: что такое REST API? REST – это аббревиатура Representational State Transfer или по-русски «передача репрезентативного состояния» — вообще непонятное описание самой используемой технологии в веб-сервисах! REST API – это способ для двух компьютеров взаимодействовать через HTTP в браузерах и серверах.
Для разработки ПО передача данных между двумя системами или более всегда была фундаментальным требованием. Приведем пример с покупкой автостраховки. Ваш страхователь должен получить информацию о вас, о вашем авто. Для этого он запрашивает данные у Госавтоинспекции, кредитных бюро, банков и других систем. Все эти запросы проходят прозрачно в реальном времени и позволяют страхователю определить, может ли он сделать конкурентное предложение.
API (программный интерфейс приложения) помогает связать разные системы с помощью интерфейса, через который эти системы могут общаться. REST же является лишь широко применяемым сводом правил для API, чтобы коммуникация с внутренними и внешними сервисами была консистентной и предсказуемой. Можно провести аналогию с отправкой письма: для этого нам необходима марка, адрес и конверт. При соблюдении требований мы точно знаем, что письмо будет доставлено и прочитано.
REST повсеместно используется в веб-системах для взаимодействия. Например, получение и обновление данных профиля в социальной сети.
Пример REST API
Перейдите по следующий ссылке, в ответ вы получите случайный вопрос по компьютерам с портала Open Trivia Database.
Это публичный API, реализованный как RESTful веб-сервис (следует правилам REST). В браузере отобразится один вопрос с ответом в формате JSON, как на примере ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ "response_code": 0, "results": [ { "category": "Science: Computers", "type": "multiple", "difficulty": "easy", "question": "What does GHz stand for?", "correct_answer": "Gigahertz", "incorrect_answers": [ "Gigahotz", "Gigahetz", "Gigahatz" ] } ] } |
Вы можете выполнить этот запрос и получить ответ в любом HTTP-клиенте, например curl:
1 |
curl "https://opentdb.com/api.php?amount=1&category=18" |
Библиотеки HTTP-клиентов доступны на всех популярных языках программирования и рантаймах, в том числе Fetch для JS, Node.js и Deno, а также file_get_contents() для PHP. JSON ответ машиночитаемый, т.е. его можно распарсить перед выводом в HTML или другой формат.
REST API и Rest
За последние годы развилось множество стандартов передачи данных. Вы могли слышать про CORBA, SOAP или XML-RPC. Большинство стандартов устанавливают строгие правила передачи сообщений.
REST придумал Roy Fielding в 2000 году. Его подход значительно проще остальных. Это не стандарт, а набор рекомендаций и ограничений для RESTful сервисов. Выделяют следующие правила:
Клиент-сервер: система А делает HTTP запрос на URL, по которому развернута система B. Система B отвечает. Принцип идентичен работе браузера. Браузер посылает запрос по URL. Запрос направляется на веб-сервер, который обычно отвечает HTML-страницей. Страница может содержать ссылки на изображения, стили и JS. Все это ведет к последующим запросам и ответам.
Нет состояния: REST не хранит состояний: запрос от клиента должен содержать всю информацию, необходимую для ответа. Другими словами, должна быть возможность посылать 2 и более HTTP запросов в любом порядке, а в ответ должен приходить одинаковый ответ (только если API специально не спроектировано отдавать случайные респонсы, как в примере с вопросами выше).
Кэшируемость: респонс должен помечаться как кэшируемый или нет. Кэширование улучшает производительность, позволяя не генерировать повторно ответ на такой же URL. Однако приватные данные пользователей в определенные моменты лучше не кэшировать.
Слои: клиенту не нужно знать, с кем он общается: с настоящим сервером, прокси или любым другим промежуточным сервером.
Создание RESTful веб-сервиса
Запрос RESTful сервиса содержит:
Эндпоинт URL. Приложение, реализующее RESTful API, определяет один или более URL эндпоинтов с доменом, портом, самим эндпоинтом и/или строкой параметров. Например, https://mydomain/user/123?format=json
HTTP метод. Любой эндпоинт может использовать различные HTTP методы, которые ведут в приложении к операциям создания, чтения, обновления и удаления данных (CRUD):
Примеры:
GET запрос к /user/ вернет список зарегистрированных пользователей в системе
POST запрос к /user/ создаст нового юзера с ID 123 на основе переданных данных (см пункт 4 далее). В ответ мы получим ID
PUT запрос к /user/123 обновит юзера 123 данными, которые мы передали (см пункт 4 далее)
GET запрос к /user/123 вернет информацию по юзеру 123
DELETE запрос к /user/123 удалит юзера 123
Также запрос RESTful сервиса содержит:
HTTP заголовки. Информация типа токенов авторизации или куки может храниться в HTTP заголовках запроса.
Тело запроса. Данные обычно передаются в HTTP теле аналогично передаче данных через HTML form или передаче данных через JSON.
REST API ответ
Ответ может содержать все что угодно: данные, HTML, изображений, аудио файл итд. Обычно ответ кодируют в JSON, но бывают ответы в XML, CSV, в виде простых строк итд. Возвращаемый формат можно указать в самом запросе. Например, /user/123?format=json или /user/123?format=xml.
В хедерах ответа также необходимо указать подходящий HTTP статус код. 200 OK используется для успешных запросов, 201 Created можно вернуть при создании записи. У ошибок свои коды, например, 400 Bad Request, 404 Not Found, 401 Unauthorized и так далее.
Другие хедеры можно указать через директивы Cache-Control или Expires. С их помощью можно указать время кэширования.
Тем не менее, нет строгих правил. Эндпоинты, HTTP методы, тело и типы ответов можно реализовывать по-своему. Например, POST, PUT и PATCH часто используют так, что они становятся взаимозаменяемыми и через любой из них можно создать или обновить запись.
Пример Hello World REST API
Node.js код ниже создает RESTful сервис на фреймворке Express. У этого приложения есть один эндпоинт GET /hello/. Установите Node.js, создайте папку restapi. Внутри папки создайте package.json:
1 2 3 4 5 6 7 8 9 10 11 |
{ "name": "restapi", "version": "1.0.0", "description": "REST test", "scripts": { "start": "node ./index.js" }, "dependencies": { "express": "4.18.1" } } |
Выполните команду npm install в командной строке, чтобы загрузить зависимости. Далее создайте файл index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// simple Express.js RESTful API 'use strict'; // initialize const port = 8888, express = require('express'), app = express(); // /hello/ GET request app.get('/hello/:name?', (req, res) => res.json( { message: `Hello ${req.params.name || 'world'}!` } ) ); // start server app.listen(port, () => console.log(`Server started on port ${port}`); ); |
Запустите приложение через командную строку npm start. Откройте в браузере http://localhost:8888/hello/. На экране отобразится JSON:
1 2 3 |
{ "message": "Hello world!" } |
API позволяет передавать имя. То есть запрос на http://localhost:8888/hello/everyone вернет:
1 2 3 |
{ "message": "Hello everyone!" } |
Клиентские REST запросы и CORS
Рассмотрим HTML-страницу ниже, запущенную в браузере по URL http://localhost:8888:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>REST test</title> </head> <body> <script> fetch('http://localhost:8888/hello/') .then((response) => { return response.json(); }) .then((json) => { console.log(json); }); </script> </body> </html> |
Вызов fetch делает такой же API запрос, а в консоли можно увидеть ответ Object { message: «Hello world!» }.
Предположим, теперь наш RESTful сервис выложили в интернет на реальный домен http://mydomain.com/hello/. URL для fetch изменился. Но если открыть http://localhost:8888 в браузере, мы получим ошибку в консоли Cross-Origin Request Blocked.
По соображениям безопасности браузеры разрешают клиентам выполнять XMLHttpRequest и Fetch API запросы только на тот же домен, откуда идет сам запрос.
К счастью, Cross-origin Resource Sharing (CORS) позволяет нам обойти это ограничение. Заголовок ответа Access-Control-Allow-Origin скажет браузеру, куда он может посылать запросы. В значении можно указать конкретный домен или * для любого домена (как сделано в API из примера выше).
Код API можно изменить, чтобы сервис мог отвечать любому клиенту на любом домене:
1 2 3 4 5 6 7 8 |
// /hello/ GET request app.get('/hello/:name?', (req, res) => res .append('Access-Control-Allow-Origin', '*') .json( { message: `Hello ${req.params.name || 'world'}!` } ) ); |
Или можно через Express.js middleware функцию добавить наш хедер во все запросы:
1 2 3 4 5 6 7 8 |
// enable CORS app.use((req, res, next) => { res.append('Access-Control-Allow-Origin', '*'); next(); }); // /hello/ GET request // ... |
Обратите внимание, что браузеры делают два запроса к REST API:
Запрос OPTIONS на тот же адрес, чтобы определить правильность хедера Access-Control-Allow-Origin
Сам REST запрос
Чтобы не дублировать код на запрос OPTIONS можно возвращать хедер Access-Control-Allow-Origin с пустым ответом.
Сложности в REST API
Успех REST в его простоте. Разработчики сами пишут RESTful API, как хочется им. Однако это может привести к некоторым сложностям. Для углубленного изучения стратегий архитектуры API ознакомьтесь с нашим уроком «13 лучших практик по созданию RESTful API».
Консенсус по эндпоинтам
Разберем следующие эндпоинты:
/user/123
/user/id/123
/user/?id=123
Все варианты подходят для получения данных для юзера 123. Количество комбинаций будет расти вместе со сложностью операций. Например, вернуть 10 юзеров, чьи фамилии начинаются на А, и кто работает на компанию Х, начиная с записи 51 с сортировкой по дате рождения в обратном от хронологического порядке.
Абсолютно неважно, как вы будете форматировать URL, однако консистентность во всем API очень важна. В большом проекте, где много разработчиков, это может вызвать некоторые сложности.
Версирование REST API
Изменения в API неминуемы, но нельзя ломать существующие URL. Иначе это приведет к поломке приложения.
Чтобы избежать проблем совместимости, API зачастую устанавливают версию. Например, /2.0/user/123 заменяет /user/123. Оба эндпоинта могут оставаться активными. Такой подход заставляет поддерживать несколько версий API. В будущем старые версии можно удалить, но этот процесс нужно тщательно спланировать.
REST API аутентификация
API из примера в начале статьи открытый: любая система может получить от него шутку без авторизации. Для API с доступом к приватным данным с возможностью обновлять и удалять эти данные такой подход не применим.
Клиентские приложения в том же домене что и RESTful API будут отправлять и получать куки, как любой другой HTTP запрос. (обратите внимание, что Fetch() в старых браузерах требует credentials init опцию) API запрос можно провалидировать на авторизацию юзера и на наличие подходящих прав пользователя.
Сторонние приложения должны использовать альтернативные способы авторизации. Среди часто используемых способов аутентификации выделяют:
HTTP basic аутентификацию. HTTP заголовок Authorization со строкой username:password, закодированной в base64.
API ключи. Сторонним приложениям разрешается использовать API с помощью выпущенного ключа, у которого могут быть определенные права и ограничения к отдельным доменам. Ключ передается во всех запросах в заголовках или в строке запроса.
OAuth. Перед работой с API требуется получить токен. Для этого отправляется запрос с ID клиента и возможно с секретным ключем на OAuth сервер. Далее токен отправляется в каждом API запросе до тех пор, пока он не прекратит действие.
JSON веб токены (JWT). Токены аутентификации подписаны цифровой подписью и безопасно передаются в хедерах запроса и ответа. JWT позволяет серверу кодировать права доступа, из-за этого не требуются дополнительные обращения в базу данных или системы авторизации.
API аутентификация выбирается исходя из контекста:
В каких-то случаях сторонние приложения распознаются как любые другие пользователи с определенным набором прав и разрешений. Например, API карты может возвращать вызывающему приложению направления между двух точек. API должен подтвердить, что приложение является валидным клиентом, но ему не нужно проверять данные авторизации юзера.
В других случаях стороннее приложение может запрашивать приватные данные юзера, например, электронную почту. REST API должно определить юзера и его права, и ему будет безразлично, какое приложение его вызывает.
Безопасность REST API
RESTful API предоставляет еще один способ получения доступа и управления вашим приложением. Даже если ваш сервис не интересен высокоспециализированным хакерам, нестандартно ведущий себя клиент может отправлять тысячи запросов в секунду, что положит ваш сервер.
Безопасность выходит за рамки нашей статьи, но можно перечислить общепринятые лучшие практики:
Используйте HTTPS
Используйте надежный метод аутентификации
Ограничивайте количество клиентских запросов к определенным доменам через CORS
Предоставляйте минимум функциональности – не создавайте DELETE метод, если он не нужен
Валидируйте все эндпоинты и тело запросов
Не храните API токены в клиентском JS
Блокируйте доступ с неизвестных доменов и ip-адресов
Блокируйте неожиданно большие нагрузки
Подумайте о лимите на запросы – ограничить запросы под одним API токеном или IP до N количества в минуту
Отвечайте подходящими статус кодами и кэшируйте хедеры
Логируйте запросы и исследуйте проблемы
Множественные запросы и ненужные данные
RESTful APIs ограничены лишь реализацией. В ответе может быть больше данных, чем необходимо. Или же для доступа ко всем данным необходимо посылать дополнительные запросы.
Рассмотрим RESTful API, предоставляющим доступ к данным автора и его книг. Для получения данных о топ-10 продаваемых книг клиент может:
Запросить первые 10 /book/ отсортированные по количеству продаж (самая продаваемая сверху). В ответе может содержаться список книг с ID авторов.
Выполнить 10 запросов /author/{id} для получения данных по каждому автору.
Это известная проблема, ее называют проблемой N+1. Для родительского запроса нужно выполнить N API запросов для каждого результата.
Если это распространенный пример, то RESTful API можно изменить таким образом, чтобы в каждой возвращаемой книге была вся информация по автору: имя, возраст, страна, биография итд. В ответе даже может храниться информация по его другим книгам – но это увеличит респонс!
Без необходимости не раздувайте респонсы. API можно настроить так, чтобы детали по автору были необязательным параметром. Например, ?author_details=full. Количество опций, которые нужно учесть автору API, может сбить с толку.
GraphQL фиксит REST API?
Сложные задачки с REST навели на мысль создать GraphQL – язык запросов веб-сервисов. Представьте, что это SQL для веб-сервисов: строка запроса определяет возвращаемые данные и их формат.
GraphQL решает некоторые проблемы RESTful APIs, но и вносит новые сложности. Например, сложнее кэшировать GraphQL респонсы.
Возможно, стоит подумать о GraphQL, когда ваш RESTful API выходит за пределы своего практического применения.
Ссылки по REST API и инструмент для разработки
Существует множество инструментов для разработки RESTful API на всех языках. Выделить можно:
Swagger: различные инструменты для проектирования, документирования, написания заглушек и мониторинга REST API
Postman: приложение для тестирования RESTful API
Hoppscotch: open-source веб альтернатива Postman
Также есть много публичных REST API для генерации шуток, конвертации валют, геокодирования, получения гос. данных и еще много всего, что сложно представить. Множество из них бесплатны, часть требует авторизации для получения API ключа или для авторизации через другие методы. Списки по категориям:
Перед написанием своего веб-сервиса попробуйте использовать парочку RESTful API в своем проекте. Или последуйте примеру GitHub, Google и других гигантов индустрии и постройте свой RESTful API.
Автор: Craig Buckler
Источник: www.sitepoint.com
Редакция: Команда webformyself.
Читайте нас в Telegram, VK, Яндекс.Дзен