- PVSM.RU - https://www.pvsm.ru -
Эта статья — одна из обещанных коротких заметок по ходу цикла статей Автоматизация Для Самых Маленьких [1].
Поскольку основным способом взаимодействия с IPAM-системой будет RESTful API, я решил рассказать о нём отдельно.
Эти интерфейсы взаимодействия обрели имя API — Application Programming Interface.
Одним из таких интерфейсов является RESTful API, который и используется для работы с NetBox.
Если очень просто, то API даёт клиенту набор инструментов, через которые тот может управлять сервером. А клиентом может выступать по сути что угодно: веб-браузер, командная консоль, разработанное производителем приложение, или вообще любое другое приложение, у которого есть доступ к API.
Например, в случае NetBox, добавить новое устройство в него можно следующими способами: через веб-браузер, отправив curl'ом запрос в консоли, использовать Postman, обратиться к библиотеке requests в питоне, воспользоваться SDK pynetbox или перейти в Swagger.
Таким образом, один раз написав единый интерфейс, производитель навсегда освобождает себя от необходимости с каждым новым клиентом договариваться как его подключать (хотя, это самую малость лукавство).
Ниже я дам очень упрощённое описание того, что такое REST.
Начнём с того, что RESTful API — это именно интерфейс взаимодействия, основанный на REST, в то время как сам REST (REpresentational State Transfer) — это набор ограничений, используемых для создания WEB-сервисов.
О каких именно ограничениях идёт речь, можно почитать в главе 5 диссертации Роя Филдинга Architectural Styles and the Design of Network-based Software Architectures [2]. Мне же позвольте привести только три наиболее значимых (с моей точки зрения) из них:
WEB-сервисы, удовлетворяющие всем принципам REST, называются RESTful WEB-services.
А API, который предоставляют RESTful WEB-сервисы, называется RESTful API.
REST — не протокол, а, так называемый, стиль архитектуры (один из). Развиваемому вместе с HTTP Роем Филдингом, REST'у было предназначено использовать HTTP 1.1, в качестве транспорта.
Адрес назначения (или иным словом — объект, или ещё иным — эндпоинт) — это привычный нам URI [3].
Формат передаваемых данных — XML или JSON.
Для этой серии статей на linkmeup развёрнута read-only (для вас, дорогие, читатели) инсталляция NetBox: netbox.linkmeup.ru [4]:45127.
На чтение права не требуются, но если хочется попробовать читать с токеном, то можно воспользоваться этим: API Token: 90a22967d0bc4bdcd8ca47ec490bbf0b0cb2d9c8.
Давайте интереса ради сделаем один запрос:
curl -X GET -H "Authorization: TOKEN 90a22967d0bc4bdcd8ca47ec490bbf0b0cb2d9c8"
-H "Accept: application/json; indent=4"
http://netbox.linkmeup.ru:45127/api/dcim/devices/1/
То есть утилитой curl мы делаем GET объекта по адресу netbox.linkmeup.ru [4]:45127/api/dcim/devices/1/ с ответом в формате JSON и отступом в 4 пробела.
Или чуть более академически: GET возвращает типизированный объект devices, являющийся параметром объекта DCIM.
Этот запрос можете выполнить и вы — просто скопируйте себе в терминал.
URL, к которому мы обращаемся в запросе, называется Endpoint. В некотором смысле это конечный объект, с которым мы будем взаимодействовать.
Например, в случае netbox'а список всех endpoint'ов можно получить тут [5].
И ещё обратите внимание на знак / в конце URL — он обязателен.
Вот что мы получим в ответ:
{
"id": 1,
"name": "mlg-host-0",
"display_name": "mlg-host-0",
"device_type": {
"id": 4,
"url": "http://netbox.linkmeup.ru/api/dcim/device-types/4/",
"manufacturer": {
"id": 4,
"url": "http://netbox.linkmeup.ru/api/dcim/manufacturers/4/",
"name": "Hypermacro",
"slug": "hypermacro"
},
"model": "Server",
"slug": "server",
"display_name": "Hypermacro Server"
},
"device_role": {
"id": 1,
"url": "http://netbox.linkmeup.ru/api/dcim/device-roles/1/",
"name": "Server",
"slug": "server"
},
"tenant": null,
"platform": null,
"serial": "",
"asset_tag": null,
"site": {
"id": 6,
"url": "http://netbox.linkmeup.ru/api/dcim/sites/6/",
"name": "Малага",
"slug": "mlg"
},
"rack": {
"id": 1,
"url": "http://netbox.linkmeup.ru/api/dcim/racks/1/",
"name": "A",
"display_name": "A"
},
"position": 41,
"face": {
"value": "front",
"label": "Front",
"id": 0
},
"parent_device": null,
"status": {
"value": "active",
"label": "Active",
"id": 1
},
"primary_ip": null,
"primary_ip4": null,
"primary_ip6": null,
"cluster": null,
"virtual_chassis": null,
"vc_position": null,
"vc_priority": null,
"comments": "",
"local_context_data": null,
"tags": [],
"custom_fields": {},
"config_context": {},
"created": "2020-01-14",
"last_updated": "2020-01-14T18:39:01.288081Z"
}
Это JSON (как мы и просили), описывающий device с ID 1: как называется, с какой ролью, какой модели, где стоит итд.
Так будет выглядеть HTTP-запрос:
GET /api/dcim/devices/1/ HTTP/1.1
Host: netbox.linkmeup.ru:45127
User-Agent: curl/7.54.0
Accept: application/json; indent=4
Так будет выглядеть ответ:
HTTP/1.1 200 OK
Server: nginx/1.14.0 (Ubuntu)
Date: Tue, 21 Jan 2020 15:14:22 GMT
Content-Type: application/json
Content-Length: 1638
Connection: keep-alive
Data
Дамп транзакции [6].
А теперь разберёмся, что же мы натворили.
HTTP-сообщение состоит из трёх частей, только первая из которых является обязательной.
Стартовые строки HTTP-запроса и ответа выглядят по-разному.
METHOD URI HTTP_VERSION
Метод определяет, какое действие клиент хочет совершить: получить данные, создать объект, обновить его, удалить.
URI — идентификатор ресурса, куда клиент обращается или иными словами путь к ресурсу/документу.
HTTP_VERSION — соответственно версия HTTP. На сегодняшний день для REST это всегда 1.1.
Пример:
GET /api/dcim/devices/1/ HTTP/1.1
HTTP_VERSION STATUS_CODE REASON_PHRASE
HTTP_VERSION — версия HTTP (1.1).
STATUS_CODE — три цифры кода состояния (200, 404, 502 итд)
REASON_PHRASE — Пояснение (OK, NOT FOUND, BAD GATEWAY итд)
Пример:
HTTP/1.1 200 OK
В заголовках передаются параметры данной HTTP-транзакции.
Например, в примере выше в HTTP-запросе это были:
Host: netbox.linkmeup.ru:45127
User-Agent: curl/7.54.0
Accept: application/json; indent=4
В них указано, что
А вот какие заголовки были в HTTP-ответе:
Server: nginx/1.14.0 (Ubuntu)
Date: Tue, 21 Jan 2020 15:14:22 GMT
Content-Type: application/json
Content-Length: 1638
Connection: keep-alive
В них указано, что
Заголовки, как вы уже заметили, выглядят как пары имя: значение, разделённые знаком ":".
Полный список возможных заголовков [7].
Тело используется для передачи собственно данных.
В HTTP-ответе это может быть HTML-страничка, или в нашем случае JSON-объект.
Между заголовками и телом должна быть как минимум одна пустая строка.
При использовании метода GET в HTTP-запросе обычно никакого тела нет, потому что передавать нечего. Но тело есть в HTTP-ответе.
А вот например, при POST уже и в запросе будет тело. Давайте о методах и поговорим теперь.
Как вы уже поняли, для работы с WEB-сервисами HTTP использует методы. То же самое касается и RESTful API.
Возможные сценарии описываются термином CRUD — Create, Read, Update, Delete.
Вот список наиболее популярных методов HTTP, реализующих CRUD:
Методы также называются глаголами, поскольку указывают на то, какое действие производится.
Давайте на примере NetBox разберёмся с каждым из них.
Это метод для получения информации.
Так, например, мы забираем список устройств:
curl -H "Accept: application/json; indent=4"
http://netbox.linkmeup.ru:45127/api/dcim/devices/
Метод GET безопасный (safe), поскольку не меняет данные, а только запрашивает.
Он идемпотентный с той точки зрения, что один и тот же запрос всегда возвращает одинаковый результат (до тех пор, пока сами данные не поменялись).
На GET сервер возвращает сообщение с HTTP-кодом и телом ответа (response code и response body).
То есть если всё OK, то код ответа — 200 (OK).
Если URL не найден — 404 (NOT FOUND).
Если что-то не так с самим сервером или компонентами, это может быть 500 (SERVER ERROR) или 502 (BAD GATEWAY).
Все возможные коды ответов [9].
Тело возвращается в формате JSON или XML.
Дамп транзакции [10].
Давайте ещё пару примеров. Теперь мы запросим информацию по конкретному устройству по его имени.
curl -X GET -H "Accept: application/json; indent=4"
"http://netbox.linkmeup.ru:45127/api/dcim/devices/?name=mlg-leaf-0"
Здесь, чтобы задать условия поиска в URI я ещё указал атритбут объекта (параметр name и его значение mlg-leaf-0). Как вы можете видеть, перед условием и после слэша идёт знак "?", а имя и значение разделяются знаком "=".
Так выглядит запрос.
GET /api/dcim/devices/?name=mlg-leaf-0 HTTP/1.1
Host: netbox.linkmeup.ru:45127
User-Agent: curl/7.54.0
Accept: application/json; indent=4
Дамп транзакции [11].
Если нужно задать пару условий, то запрос будет выглядеть так:
curl -X GET -H "Accept: application/json; indent=4"
"http://netbox.linkmeup.ru:45127/api/dcim/devices/?role=leaf&site=mlg"
Здесь мы запросили все устройства с ролью leaf, расположенные на сайте mlg.
То есть два условия отделяются друг от друга знаком "&".
Дамп транзакции [12].
Из любопытного и приятного — если через "&" задать два условия с одним именем, то между ними будет на самом деле не логическое «И», а логическое «ИЛИ».
То есть вот такой запрос и в самом деле вернёт два объекта: mlg-leaf-0 и mlg-spine-0
curl -X GET -H "Accept: application/json; indent=4"
"http://netbox.linkmeup.ru:45127/api/dcim/devices/?name=mlg-leaf-0&name=mlg-spine-0"
Дамп транзакции [13].
Попробуем обратиться к несуществующему URL.
curl -X GET -H "Accept: application/json; indent=4"
"http://netbox.linkmeup.ru:45127/api/dcim/IDGAF/"
Дамп транзакции [14].
POST используется для создания нового объекта в наборе объектов. Или более сложным языком: для создания нового подчинённого ресурса.
То есть, если есть набор devices, то POST позволяет создать новый объект device внутри devices.
Выберем тот же Endpoint и с помощью POST создадим новое устройство.
curl -X POST "http://netbox.linkmeup.ru:45127/api/dcim/devices/"
-H "accept: application/json"
-H "Content-Type: application/json"
-H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"
-d "{ "name": "just a simple russian girl", "device_type": 1, "device_role": 1, "site": 3, "rack": 3, "position": 5, "face": "front"}"
Здесь уже появляется заголовок Authorization, содержащий токен, который авторизует запрос на запись, а после директивы -d расположен JSON с параметрами создаваемого устройства:
{
"name": "just a simple russian girl",
"device_type": 1,
"device_role": 1,
"site": 3,
"rack": 3,
"position": 5,
"face": "front"}
Запрос у вас не сработает, потому что Токен уже не валиден — не пытайтесь записать в NetBox.
В ответ приходит HTTP-ответ с кодом 201 (CREATED) и JSON'ом в теле сообщения, где сервер возвращает все параметры о созданном устройстве.
HTTP/1.1 201 Created
Server: nginx/1.14.0 (Ubuntu)
Date: Sat, 18 Jan 2020 11:00:22 GMT
Content-Type: application/json
Content-Length: 1123
Connection: keep-alive
JSON
Дамп транзакции [15].
Теперь новым запросом с методом GET можно его увидеть в выдаче:
curl -X GET -H "Accept: application/json; indent=4"
"http://netbox.linkmeup.ru:45127/api/dcim/devices/?q=russian"
«q» в NetBox'е позволяет найти все объекты, содержащие в своём названии строку, идущую дальше.
POST, очевидно, не является ни безопасным, ни идемпотентным — он наверняка меняет данные, и дважды выполненный запрос приведёт или к созданию второго такого же объекта, или к ошибке.
Это метод для изменения существующего объекта. Endpoint для PUT выглядит иначе, чем для POST — в нём теперь содержится конкретный объект.
PUT может возвращать коды 201 или 200.
Важный момент с этим методом: нужно передавать все обязательные атрибуты, поскольку PUT замещает собой старый объект.
Поэтому, если например, просто попытаться добавить атрибут asset_tag нашему новому устройству, то получим ошибку:
curl -X PUT "http://netbox.linkmeup.ru:45127/api/dcim/devices/18/"
-H "accept: application/json"
-H "Content-Type: application/json"
-H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"
-d "{ "asset_tag": "12345678"}"
{"device_type":["This field is required."],"device_role":["This field is required."],"site":["This field is required."]}
Но если добавить недостающие поля, то всё сработает:
curl -X PUT "http://netbox.linkmeup.ru:45127/api/dcim/devices/18/"
-H "accept: application/json"
-H "Content-Type: application/json"
-H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"
-d "{ "name": "just a simple russian girl", "device_type": 1, "device_role": 1, "site": 3, "rack": 3, "position": 5, "face": "front", "asset_tag": "12345678"}"
Дамп транзакции [16].
Обратите внимание на URL здесь — теперь он включает ID устройства, которое мы хотим менять (18).
Этот метод используется для частичного изменения ресурса.
WAT? Спросите вы, а как же PUT?
PUT — изначально существовавший в стандарте метод, предполагающий полную замену изменяемого объекта. Соответственно в методе PUT, как я и писал выше, придётся указать даже те атрибуты объекта, которые не меняются.
А PATCH был добавлен позже и позволяет указать только те атрибуты, которые действительно меняются.
Например:
curl -X PATCH "http://netbox.linkmeup.ru:45127/api/dcim/devices/18/"
-H "accept: application/json"
-H "Content-Type: application/json"
-H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"
-d "{ "serial": "BREAKINGBAD"}"
Здесь также в URL указан ID устройства, но для изменения только один атрибут serial.
Дамп транзакции [17].
Очевидно, удаляет объект.
Пример.
curl -X DELETE "http://netbox.linkmeup.ru:45127/api/dcim/devices/21/"
-H "accept: application/json"
-H "Content-Type: application/json"
-H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"
Метод DELETE идемпотентен с той точки зрения, что повторно выполненный запрос уже ничего не меняет в списке ресурсов (но вернёт код 404 (NOT FOUND).
curl -X DELETE "http://netbox.linkmeup.ru:45127/api/dcim/devices/21/"
-H "accept: application/json"
-H "Content-Type: application/json"
-H "Authorization: TOKEN a9aae70d65c928a554f9a038b9d4703a1583594f"
{"detail":"Not found."}
Curl — это, конечно, очень удобно для доблестных воинов CLI, но есть инструменты получше.
Postman позволяет в графическом интерфейсе формировать запросы, выбирая методы, заголовки, тело, и отображает результат в удобочитаемом виде.
Кроме того запросы и URI можно сохранять и возвращаться к ним позже.
Скачать Postman на оф.сайте [18].
Так мы можем сделать GET:

Здесь указан Token в GET только для примера.
А так POST:

Postman служит только для работы с RESTful API.
Например, не пытайтесь через него отправить NETCONF XML, как это делал я на заре своей автоматизационной карьеры.
Один из приятных бонусов специфицированного API в том, что вы можете в Postman импортировать все эндпоинты и их методы как коллекцию.
Для этого в окне Import (File->Import) выберите Import From Link и вставьте в окно URL netbox.linkmeup.ru [4]:45127/api/docs/?format=openapi.

Далее, всё, что только можно, вы найдёте в коллекциях.

Но даже через Postman вы, скорее всего, не будете управлять своими Production-системами. Наверняка, у вас будут внешние приложения, которые захотят без вашего участия взаимодействовать с ними.
Например, система генерации конфигурации захочет забрать список IP-интерфейсов из NetBox.
В Python есть чудесная библиотека requests, которая реализует работу через HTTP.
Пример запроса списка всех устройств:
import requests
HEADERS = {'Content-Type': 'application/json', 'Accept': 'application/json'}
NB_URL = "http://netbox.linkmeup.ru:45127"
request_url = f"{NB_URL}/api/dcim/devices/"
devices = requests.get(request_url, headers = HEADERS)
print(devices.json())
Снова добавим новое устройство:
import requests
API_TOKEN = "a9aae70d65c928a554f9a038b9d4703a1583594f"
HEADERS = {'Authorization': f'Token {API_TOKEN}', 'Content-Type': 'application/json', 'Accept': 'application/json'}
NB_URL = "http://netbox.linkmeup.ru:45127"
request_url = f"{NB_URL}/api/dcim/devices/"
device_parameters = {
"name": "just a simple REQUESTS girl",
"device_type": 1,
"device_role": 1,
"site": 3,
}
new_device = requests.post(request_url, headers = HEADERS, json=device_parameters)
print(new_device.json())
В случае NetBox есть также Python SDK — Pynetbox [19], который представляет все Endpoint'ы NetBox в виде объекта и его атрибутов, делая за вас всю грязную работу по формированию URI и парсингу ответа, хотя и не бесплатно, конечно.
Например, сделаем то же, что и выше, использую pynetbox.
Список всех устройств:
import pynetbox
NB_URL = "http://netbox.linkmeup.ru:45127"
nb = pynetbox.api(NB_URL)
devices = nb.dcim.devices.all()
print(devices)
Добавить новое устройство:
import pynetbox
API_TOKEN = "a9aae70d65c928a554f9a038b9d4703a1583594f"
NB_URL = "http://netbox.linkmeup.ru:45127"
nb = pynetbox.api(NB_URL, token = API_TOKEN)
device_parameters = {
"name": "just a simple PYNETBOX girl",
"device_type": 1,
"device_role": 1,
"site": 3,
}
new_device = nb.dcim.devices.create(**device_parameters)
print(new_device)
Документация по Pynetbox [20].
За что ещё стоит поблагодарить ушедшее десятилетие, так это за спецификации API. Если вы перейдёте по этому пути [5], то попадёте в Swagger UI — документацию по API Netbox.

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

На этой же странице можно выполнять интерактивные запросы, кликнув на Try it out.
По какой-от причине swagger в качестве Base URL берёт имя сервера без порта, поэтому функция Try it out не работает в моих примерах со Swagger'ом. Но вы можете попробовать это на собственной инсталляции.
При нажатии на Execute Swagger UI сформирует строку curl, с помощью которой можно аналогичный запрос сделать из командной строки.
В Swagger UI можно даже создать объект:

Для этого достаточно быть авторизованным пользователем, обладающим нужными правами.
То, что мы видим на этой странице — это Swagger UI — документация, сгенерированная на основе спецификации API.
С трендами на микросервисную архитектуру всё более важным становится иметь стандартизированный API для взаимодействия между компонентами, эндпоинты и методы которого легко определить как человеку, так и приложению, не роясь в исходном коде или PDF-документации.
Поэтому разработчики сегодня всё чаще следуют парадигме API First [21], когда сначала задумываются об API, а уже потом о реализации.
В этом дизайне сначала специфицируется API, а затем из него генерируются документация, клиентское приложение, серверная часть и необходимы тесты.
Swagger — это фреймворк и язык спецификации (который ныне переименован в OpenAPI 2.0), позволяющие реализовать эту задачу.
Углубляться в него я не буду.
За бо́льшими деталями сюда:
Существует и такая, да. Не всё в том мире 2000-го года так уже радужно.
Не являясь экспертом, не берусь предметно раскрывать вопрос, но дам ссылку на небесспорную статью на Хабре [25].
Альтернативным интерфейсом взаимодействия компонентов системы сегодня является gRPC. Ему же пророчат большое будущее на ниве новых подходов к работе с сетевым оборудованием. Но о нём мы поговорим когда-то в будущем, когда придёт его черёд.
Можно также взглянуть на многообещающий GraphQL [26], но нам опять же нет нужды с ним работать пока, поэтому остаётся на самостоятельное изучение.
Важно
Токен a9aae70d65c928a554f9a038b9d4703a1583594f был использован только в демонстрационных целях и больше не работает.Прямое указание токенов в коде программы недопустимо и сделано здесь мной только в интересах упрощения примеров.
Автор: Марат
Источник [29]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sistemnoe-administrirovanie/344615
Ссылки в тексте:
[1] Автоматизация Для Самых Маленьких: https://habr.com/ru/search/?q=%5B%D0%B0%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F%20%D0%B4%D0%BB%D1%8F%20%D1%81%D0%B0%D0%BC%D1%8B%D1%85%20%D0%BC%D0%B0%D0%BB%D0%B5%D0%BD%D1%8C%D0%BA%D0%B8%D1%85%5D&target_type=posts
[2] главе 5 диссертации Роя Филдинга Architectural Styles and the Design of Network-based Software Architectures: https://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm
[3] URI: https://ru.wikipedia.org/wiki/URI
[4] netbox.linkmeup.ru: http://netbox.linkmeup.ru
[5] тут: http://netbox.linkmeup.ru:45127/api/docs/
[6] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_get_devices.pcapng
[7] Полный список возможных заголовков: https://en.wikipedia.org/wiki/List_of_HTTP_header_fields
[8] Полный список методов: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods
[9] Все возможные коды ответов: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
[10] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_get_all_devices.pcapng
[11] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_get_device_by_name.pcapng
[12] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_get_device_with_double_conditions.pcapng
[13] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_get_device_with_or_operand.pcapng
[14] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_get_not_found.pcapng
[15] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_post_new_device.pcapng
[16] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_put_asset_tag.pcapng
[17] Дамп транзакции: https://github.com/eucariot/ADSM/blob/master/docs/source/3_ipam_dcim/dumps/http_patch_serial.pcapng
[18] Скачать Postman на оф.сайте: https://www.getpostman.com/downloads/
[19] Pynetbox: https://github.com/digitalocean/pynetbox
[20] Документация по Pynetbox: https://pynetbox.readthedocs.io/en/latest/
[21] API First: https://medium.com/adobetech/three-principles-of-api-first-design-fa6666d9f694
[22] Сайт Swagger: https://swagger.io/docs/specification/
[23] Пример использования: https://justcodeit.ru/swagger-docs-dlya-api-na-laravel/
[24] Wiki проOpen API: https://en.wikipedia.org/wiki/OpenAPI_Specification
[25] статью на Хабре: https://habr.com/ru/post/265845/
[26] GraphQL: https://habr.com/ru/post/326986/
[27] HTTP-методы: https://restfulapi.net/http-methods/
[28] Принципы REST: https://restfulapi.net/rest-architectural-constraints/
[29] Источник: https://habr.com/ru/post/485618/?utm_source=habrahabr&utm_medium=rss&utm_campaign=485618
Нажмите здесь для печати.