post api что это

Принципы построения REST JSON API

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что этоЭта памятка писалась для внутренних нужд (открыть глаза менее опытным в вебе коллегам). Но, т.к. я насмотрелся велосипедов от довольно уважаемых, казалось бы, контор, — выкладываю на хабр. Мне кажется, многим будет полезно.

Зачем

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

Структура запросов и ответов

Любой http-запрос начинается со строки

где METHOD — это метод доступа (GET, PUT и т.д.), а URI — адрес запрашиваемого ресурса.

В начале запроса идут заголовки — просто текстовые строки вида key: value
Затем передаётся пустая строка, означающая конец секции заголовков, и затем — тело запроса, если оно есть.

В ответе сначала передаётся строка с версией http, кодом и строковым статусом ответа (например HTTP/1.1 200 OK ), далее текстовые заголовки ответа, потом пустая строка, потом тело ответа.

Тут вроде всё просто.

Кодирование запросов и ответов

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

Использование кодов символов и html-сущностей не допускается, т.е. режим JSON_UNESCAPED_UNICODE обязателен. Не все клиенты знают всю таблицу html сущностей (типа каких-нибудь ù ), да и при чём тут html. Не все клиенты готовы/хотят заниматься перекодированием \uXXXX; и &#XX;. Плюс возможны «весёлые» ситуации с избыточным экранированием или пропаданием слэшей и амперсандов.

Все данные, кроме URI и двоичных файлов, передаются в формате JSON. Обратите внимание, что далеко не всякий валидный javascript код является валидным JSON.
В частности, для строк используются только двойные кавычки. Одинарные кавычки в json-данных, хотя и допустимы в «обычном» javascript, могут вызвать непредсказуемые плохо отлавливаемые баги.

В запросах обязательно указывается заголовок

Вызовы к API отличаются от прочих вызовов (например, обычной загрузки html страницы по данному URI) именно по наличию application/json в Accept.

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

При наличии тела запроса также обязателен заголовок запроса

либо, при загрузке файлов,

и далее, в первой части

после чего для каждого файла

Если вы используете защиту от CSRF (а лучше бы вам её использовать), то удобнее передавать CSRF-токен в отдельном заголовке (типа X-CSRF-Token) для всех запросов, а не внедрять вручную в каждый запрос. Хранить CSRF токен в куках плохо по той причине, что куки можно украсть, в чём собственно и состоит суть CSRF атаки.

Структура URI

Нагородить можно всякое, но лучшая практика — чтобы все URI имели вид

ну, или если у вас api лежит в какой-то папке,

Для разбора части URI до знака вопроса можно использовать регулярку

Ведущий слэш обязателен, т.к. неизвестно, с какого URL будет осуществлён запрос.

Методы HTTP

GET /:entity/:id — getById

В случае успеха сервер возвращает 200 OK с полями объекта в формате JSON в теле ответа (без дополнительного оборачивания в какой-либо объект)

В случае, если объект с такими id не существует, сервер возвращает 404 Not Found

В ответе обязательно должны быть заголовки, касающиеся политики кэширования, т.к. браузеры активно кешируют GET и HEAD запросы. При остутствии какой-либо политики управления кэшем должно быть:

GET /:entity[?param1=. &param2=. ] — списочный get

Простой случай: в случае успеха сервер возвращает 200 OK с массивом объектов в формате JSON в теле ответа (т.е. ответ начинается с [ и заканчивается ] ).

Если массив получился пустой, всё равно вовзращается 200 OK с пустым масивом [] в теле ответа.

Более сложный вариант: возвращается объект, в одном из полей которого — искомый массив. В остальных полях — данные о пагинации, фильтры, счётчики и пр. Только держите это консистентным по всем api.

HEAD /:entity[/:id] — запрос заголовков

Полный аналог GET с таким же URI, но не возвращает тело ответа, а только HTTP-заголовки.

Реализация поддержки HEAD запросов веб-сервером обязательна.

Активно используется браузерами в качестве автоматических pre-flight запросов перед выполнением потенциально опасных, по их мнению, операций. Например, браузер Chrome активно кидается head-запросами для получения политик CORS при кросс-доменных операциях (виджеты и пр). При этом ошибка обработки такого head-запроса приведёт к тому, что основной запрос вообще не будет выполнен браузером.

Может использоваться для проверки существования объекта без его передачи (например, для больших объектов типа мультимедиа-файлов).

POST /:entity — создаёт новый объект типа :entity

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

В случае успеха сервер должен возвращать 201 Created с пустым телом, но с дополнительным заголовком

указывающим на месторасположение созданного объекта.

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

Также метод POST используется для удалённого вызова процедур (RPC), в этом случае ответ будет иметь статус 200 OK и результаты в теле. Вообще смешивать REST и RPC в одном api — идея сомнительная, но всякое бывает.

Единственный неидемпотентный некешируемый метод, т.е. повтор двух одинаковых POST запросов создаст два одинаковых объекта.

PUT /:entity/:id — изменяет объект целиком

В запросе должны содержаться все поля изменяемого объекта в формате JSON.

В случае успеха должен возвращать 204 No Data с пустым телом, т.к. у клиента есть все необходимые данные.

Идемпотентный запрос, т.е. повторный PUT с таким же телом не приводит к каким-либо изменениям в БД.

PATCH /:entity/:id — изменяет отдельные поля объекта

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

В случае успеха возвращает 200 OK с телом, аналогичным запросу getById, со всеми полями изменённого объекта.

Используется с осторожностью, т.к. два параллельных PATCH от двух разных клиентов могут привести объект в невалидное состояние.

DELETE /:entity/:id — удаляет объект, если он существует.

В случае успеха возвращает 204 No Data с пустым телом, т.к. возвращать уже нечего.

Идемпотентный запрос, т.е. повторный DELETE с таким же адресом не приводит к ошибке 404.

OPTIONS /:entity[/:id]

Получает список методов, доступных по данному URI.

Сервер должен ответить 200 OK с дополнительным заголовком

Некешиуремый необязательный метод.

Обработка ошибок

Возвращаемые ошибки передаются с сервера на клиент как ответы со статусами 4хх (ошибка клиента) или 5хх (ошибка сервера). При этом описание ошибки, если оно есть, приводится в теле ответа в формате text/plain (без всякого JSON). Соответственно, передаётся заголовок ответа

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

При выборе конкретных кодов ошибок не следует слишком увлекаться и пытаться применить существующие коды только потому, что название кажется подходящим. У многих кодов есть дополнительные требования к наличию определённых заголовков и специальная обработка браузерами. Например, код 401 запускает HTTP-аутентификацию, которая будет странно смотреться в каком-нибудь приложении на react или electron.

UPD по мотивам комментариев. Клиенты у вас будут разные: не только веб и мобильные приложения, но и такие штуки, как запускалка интеграционных тестов (CI), балансировщик нагрузки или система мониторинга у админов. Использование или неиспользование того или иного статуса ошибки определяется тем, будет ли он полезен хоть какому-то клиенту (т.е. этот клиент сможет предпринять какие-то действия конкретно по этому коду) и, наоборот, не будет ли проблем у какого-то из клиентов из-за неиспользования вами этого кода. Придумать реальный use-case, когда реакция клиента будет различаться в зависимости от 404 или 410, довольно сложно. При этом отличий 404 от 200 или 500 — вагон и телега.

400 Bad Request

Универсальный код ошибки, если серверу непонятен запрос от клиента.

403 Forbidden

Возвращается, если операция запрещена для текущего пользователя. Если у оператора есть учётка с более высокими правами, он должен перелогиниться самостоятельно. См. также 419

404 Not Found

Возвращается, если в запросе был указан неизвестный entity или id несуществующего объекта.

Списочные методы get не должны возвращать этот код при верном entity (см. выше).

Если запрос вообще не удалось разобрать, следует возвращать 418.

415 Unsupported Media Type

Возвращается при загрузке файлов на сервер, если фактический формат переданного файла не поддерживается. Также может возвращаться, если не удалось распарсить JSON запроса, или сам запрос пришёл не в формате JSON.

418 I’m a Teapot

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

419 Authentication Timeout

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

422 Unprocessable Entity

Запрос корректно разобран, но содержание запроса не прошло серверную валидацию.

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

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

500 Internal Server Error

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

Всё, что может сделать клиент в этом случае — это уведомить пользователя и сделать console.error(err) для более продвинутых товарищей (админов, разработчиков и тестировщиков).

501 Not Implemented

Возвращается, если текущий метод неприменим (не реализован) к объекту запроса.

Ну вот, в общем-то, и всё. Спасибо за внимание!

Источник

Автоматизация Для Самых Маленьких. Заметки. RESTful API

Эта статья — одна из обещанных коротких заметок по ходу цикла статей Автоматизация Для Самых Маленьких.
Поскольку основным способом взаимодействия с IPAM-системой будет RESTful API, я решил рассказать о нём отдельно.

Воздаю хвалы архитекторам современного мира — у нас есть стандартизированные интерфейсы. Да их много — это минус, но они есть — это плюс.

Эти интерфейсы взаимодействия обрели имя API — Application Programming Interface.

Одним из таких интерфейсов является RESTful API, который и используется для работы с NetBox.

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что это

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

Например, в случае NetBox, добавить новое устройство в него можно следующими способами: через веб-браузер, отправив curl’ом запрос в консоли, использовать Postman, обратиться к библиотеке requests в питоне, воспользоваться SDK pynetbox или перейти в Swagger.

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

Содержание

REST, RESTful, API

Ниже я дам очень упрощённое описание того, что такое REST.

Начнём с того, что RESTful API — это именно интерфейс взаимодействия, основанный на REST, в то время как сам REST (REpresentational State Transfer) — это набор ограничений, используемых для создания WEB-сервисов.

О каких именно ограничениях идёт речь, можно почитать в главе 5 диссертации Роя Филдинга Architectural Styles and the Design of Network-based Software Architectures. Мне же позвольте привести только три наиболее значимых (с моей точки зрения) из них:

А API, который предоставляют RESTful WEB-сервисы, называется RESTful API.

REST — не протокол, а, так называемый, стиль архитектуры (один из). Развиваемому вместе с HTTP Роем Филдингом, REST’у было предназначено использовать HTTP 1.1, в качестве транспорта.

Адрес назначения (или иным словом — объект, или ещё иным — эндпоинт) — это привычный нам URI.

Формат передаваемых данных — XML или JSON.

Для этой серии статей на linkmeup развёрнута read-only (для вас, дорогие, читатели) инсталляция NetBox: netbox.linkmeup.ru:45127.

На чтение права не требуются, но если хочется попробовать читать с токеном, то можно воспользоваться этим: API Token: 90a22967d0bc4bdcd8ca47ec490bbf0b0cb2d9c8.

Давайте интереса ради сделаем один запрос:

То есть утилитой curl мы делаем GET объекта по адресу netbox.linkmeup.ru:45127/api/dcim/devices/1/ с ответом в формате JSON и отступом в 4 пробела.

Или чуть более академически: GET возвращает типизированный объект devices, являющийся параметром объекта DCIM.

Этот запрос можете выполнить и вы — просто скопируйте себе в терминал.

URL, к которому мы обращаемся в запросе, называется Endpoint. В некотором смысле это конечный объект, с которым мы будем взаимодействовать.
Например, в случае netbox’а список всех endpoint’ов можно получить тут.

И ещё обратите внимание на знак / в конце URL — он обязателен.

Вот что мы получим в ответ:

Это JSON (как мы и просили), описывающий device с ID 1: как называется, с какой ролью, какой модели, где стоит итд.

Так будет выглядеть HTTP-запрос:

Так будет выглядеть ответ:

А теперь разберёмся, что же мы натворили.

Структура сообщений HTTP

HTTP-сообщение состоит из трёх частей, только первая из которых является обязательной.

Стартовая строка

Стартовые строки HTTP-запроса и ответа выглядят по-разному.

HTTP-Запрос

Метод определяет, какое действие клиент хочет совершить: получить данные, создать объект, обновить его, удалить.
URI — идентификатор ресурса, куда клиент обращается или иными словами путь к ресурсу/документу.
HTTP_VERSION — соответственно версия HTTP. На сегодняшний день для REST это всегда 1.1.

HTTP-Ответ

HTTP_VERSION — версия HTTP (1.1).
STATUS_CODE — три цифры кода состояния (200, 404, 502 итд)
REASON_PHRASE — Пояснение (OK, NOT FOUND, BAD GATEWAY итд)

Заголовки

В заголовках передаются параметры данной HTTP-транзакции.

Например, в примере выше в HTTP-запросе это были:

Тело HTTP-сообщения

Тело используется для передачи собственно данных.

В HTTP-ответе это может быть HTML-страничка, или в нашем случае JSON-объект.

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

При использовании метода GET в HTTP-запросе обычно никакого тела нет, потому что передавать нечего. Но тело есть в HTTP-ответе.

А вот например, при POST уже и в запросе будет тело. Давайте о методах и поговорим теперь.

Методы

Как вы уже поняли, для работы с WEB-сервисами HTTP использует методы. То же самое касается и RESTful API.

Возможные сценарии в реальной жизни описываются термином CRUD — Create, Read, Update, Delete.
Вот список наиболее популярных методов HTTP, реализующих CRUD:

Давайте на примере NetBox разберёмся с каждым из них.

HTTP GET

Это метод для получения информации.

Так, например, мы забираем список устройств:

Метод GET безопасный (safe), поскольку не меняет данные, а только запрашивает.

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

На GET сервер возвращает сообщение с HTTP-кодом и телом ответа (response code и response body).

То есть если всё OK, то код ответа — 200 (OK).
Если URL не найден — 404 (NOT FOUND).
Если что-то не так с самим сервером или компонентами, это может быть 500 (SERVER ERROR) или 502 (BAD GATEWAY).
Все возможные коды ответов.

Тело возвращается в формате JSON или XML.

Давайте ещё пару примеров. Теперь мы запросим информацию по конкретному устройству по его имени.

Здесь, чтобы задать условия поиска в URI я ещё указал атритбут объекта (параметр name и его значение mlg-leaf-0). Как вы можете видеть, перед условием и после слэша идёт знак «?», а имя и значение разделяются знаком «=».

Так выглядит запрос.

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

Здесь мы запросили все устройства с ролью leaf, расположенные на сайте mlg.
То есть два условия отделяются друг от друга знаком «&».

Из любопытного и приятного — если через «&» задать два условия с одним именем, то между ними будет на самом деле не логическое «И», а логическое «ИЛИ».

То есть вот такой запрос и в самом деле вернёт два объекта: mlg-leaf-0 и mlg-spine-0

Попробуем обратиться к несуществующему URL.

HTTP POST

POST используется для создания нового объекта в наборе объектов. Или более сложным языком: для создания нового подчинённого ресурса.

Уточнение от arthuriantech:
Включая, но не ограничиваясь. Метод POST предназначен для передачи данных на сервер с целью дальнейшей обработки — он используется для любых действий, которые не нужно стандартизировать в рамках HTTP. До RFC 5789 он был единственным легальным способом вносить частичные изменения.
roy.gbiv.com/untangled/2009/it-is-okay-to-use-post
tools.ietf.org/html/rfc7231#section-4.3.3

То есть, если есть набор devices, то POST позволяет создать новый объект device внутри devices.

Выберем тот же Endpoint и с помощью POST создадим новое устройство.

Здесь уже появляется заголовок Authorization, содержащий токен, который авторизует запрос на запись, а после директивы -d расположен JSON с параметрами создаваемого устройства:

Запрос у вас не сработает, потому что Токен уже не валиден — не пытайтесь записать в NetBox.

В ответ приходит HTTP-ответ с кодом 201 (CREATED) и JSON’ом в теле сообщения, где сервер возвращает все параметры о созданном устройстве.

Теперь новым запросом с методом GET можно его увидеть в выдаче:

«q» в NetBox’е позволяет найти все объекты, содержащие в своём названии строку, идущую дальше.

POST, очевидно, не является ни безопасным, ни идемпотентным — он наверняка меняет данные, и дважды выполненный запрос приведёт или к созданию второго такого же объекта, или к ошибке.

HTTP PUT

Это метод для изменения существующего объекта. Endpoint для PUT выглядит иначе, чем для POST — в нём теперь содержится конкретный объект.

PUT может возвращать коды 201 или 200.

Важный момент с этим методом: нужно передавать все обязательные атрибуты, поскольку PUT замещает собой старый объект.

Поэтому, если например, просто попытаться добавить атрибут asset_tag нашему новому устройству, то получим ошибку:

Но если добавить недостающие поля, то всё сработает:

Обратите внимание на URL здесь — теперь он включает ID устройства, которое мы хотим менять (18).

HTTP PATCH

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

PUT — изначально существовавший в стандарте метод, предполагающий полную замену изменяемого объекта. Соответственно в методе PUT, как я и писал выше, придётся указать даже те атрибуты объекта, которые не меняются.

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

Здесь также в URL указан ID устройства, но для изменения только один атрибут serial.

HTTP DELETE

Очевидно, удаляет объект.

Метод DELETE идемпотентен с той точки зрения, что повторно выполненный запрос уже ничего не меняет в списке ресурсов (но вернёт код 404 (NOT FOUND).

Способы работы с RESTful API

Curl — это, конечно, очень удобно для доблестных воинов CLI, но есть инструменты получше.

Postman

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

Кроме того запросы и URI можно сохранять и возвращаться к ним позже.

Так мы можем сделать GET:

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что это
Здесь указан Token в GET только для примера.

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что это

Postman служит только для работы с RESTful API.

Например, не пытайтесь через него отправить NETCONF XML, как это делал я на заре своей автоматизационной карьеры.

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

Для этого в окне Import (File->Import) выберите Import From Link и вставьте в окно URL netbox.linkmeup.ru:45127/api/docs/?format=openapi.

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что это

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

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что это

Python+requests

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

Например, система генерации конфигурации захочет забрать список IP-интерфейсов из NetBox.
В Python есть чудесная библиотека requests, которая реализует работу через HTTP.
Пример запроса списка всех устройств:

Снова добавим новое устройство:

Python+NetBox SDK

В случае NetBox есть также Python SDK — Pynetbox, который представляет все Endpoint’ы NetBox в виде объекта и его атрибутов, делая за вас всю грязную работу по формированию URI и парсингу ответа, хотя и не бесплатно, конечно.

Например, сделаем то же, что и выше, использую pynetbox.
Список всех устройств:

Добавить новое устройство:

SWAGGER

За что ещё стоит поблагодарить ушедшее десятилетие, так это за спецификации API. Если вы перейдёте по этому пути, то попадёте в Swagger UI — документацию по API Netbox.

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что это

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

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что это

На этой же странице можно выполнять интерактивные запросы, кликнув на Try it out.

По какой-от причине swagger в качестве Base URL берёт имя сервера без порта, поэтому функция Try it out не работает в моих примерах со Swagger’ом. Но вы можете попробовать это на собственной инсталляции.

При нажатии на Execute Swagger UI сформирует строку curl, с помощью которой можно аналогичный запрос сделать из командной строки.

В Swagger UI можно даже создать объект:

post api что это. Смотреть фото post api что это. Смотреть картинку post api что это. Картинка про post api что это. Фото post api что это

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

То, что мы видим на этой странице — это Swagger UI — документация, сгенерированная на основе спецификации API.

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

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

Swagger — это фреймворк и язык спецификации (который ныне переименован в OpenAPI 2.0), позволяющие реализовать эту задачу.
Углубляться в него я не буду.

За бо́льшими деталями сюда:

Критика REST и альтернативы

Существует и такая, да. Не всё в том мире 2000-го года так уже радужно.

Не являясь экспертом, не берусь предметно раскрывать вопрос, но дам ссылку на небесспорную статью на Хабре.

Альтернативным интерфейсом взаимодействия компонентов системы сегодня является gRPC. Ему же пророчат большое будущее на ниве новых подходов к работе с сетевым оборудованием. Но о нём мы поговорим когда-то в будущем, когда придёт его черёд.

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

Важно
Токен a9aae70d65c928a554f9a038b9d4703a1583594f был использован только в демонстрационных целях и больше не работает.

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *