WordPress давно перестал быть просто платформой для блогов. Сегодня это полноценный инструмент для создания сайтов любого уровня сложности. Но что, если нам нужно взаимодействовать с контентом WordPress из другого приложения, например, мобильного или веб-сервиса? Вот тут на сцену выходит WordPress REST API. В этом гайде я разберу все от основ до продвинутых фич. Приготовьтесь к увлекательному путешествию по миру API!
Часть 1 — Введение и базовые принципы
Что такое REST API и зачем он нужен?
REST API — это интерфейс, позволяющий получать доступ к данным и функционалу вашего WordPress-сайта через HTTP-запросы. Проще говоря, он делает WordPress более открытым и интегрируемым с другими системами.
Основные плюсы REST API:
- Гибкость: Доступ к данным WordPress из любых приложений и сервисов.
- Масштабируемость: Легко расширить функционал или подключить внешние системы.
- Совместимость: Работает с любыми языками программирования (PHP, Python, JavaScript и др.).
Как включить REST API на сайте WordPress?
Чтобы проверить, работает ли API на вашем сайте, достаточно обратиться к следующему URL:
https://ваш-сайт.com/wp-json/wp/v2/posts
Если все правильно, вы увидите JSON-ответ с постами сайта. Если нет — проверьте, нет ли плагинов, ограничивающих API.
Архитектура и основные компоненты WordPress REST API
- Endpoint (конечная точка): Это конкретный URL, который обрабатывает запросы и возвращает данные.
- Routes (маршруты): Пути, ведущие к endpoint’ам. Например, wp/v2/posts — маршрут для получения постов.
- HTTP-методы: GET — получить данные (например, список постов); POST — создать новый ресурс; PUT/PATCH — обновить существующий ресурс; DELETE — удалить ресурс.
- JSON: Формат данных, используемый для обмена информацией. Читается легко как человеком, так и машиной.
Простой пример запроса на получение постов
Допустим, у нас есть сайт с доменом example.com. Чтобы получить все посты:
curl https://example.com/wp-json/wp/v2/posts
Ответ будет выглядеть примерно так:
[
{
"id": 1,
"date": "2024-09-21T10:00:00",
"title": {
"rendered": "Привет, мир!"
},
"content": {
"rendered": "<p>Добро пожаловать в WordPress!</p>"
}
},
...
]
Настройка аутентификации
Открытые данные доступны без авторизации, но для создания или обновления контента нужна аутентификация. В WordPress REST API поддерживаются несколько методов:
- Basic Auth (для тестирования на локалке — не использовать на продакшене!).
- JWT (JSON Web Tokens) — более безопасный вариант.
- OAuth 2.0 — для сложных сценариев.
Часть 2 — Создание и настройка собственных endpoint’ов
Мы уже разобрались с основами WordPress REST API и даже сделали первый запрос для получения постов. Теперь перейдём к самому интересному — созданию собственных endpoint’ов. Почему это важно? Стандартный API хорош, но иногда нужно расширить его функционал или создать маршруты для уникальных данных. Готовы? Поехали!
Что такое endpoint и зачем создавать свои?
Endpoint — это конечная точка, через которую приложение взаимодействует с сервером. Стандартные маршруты WordPress API охватывают посты, страницы, пользователей и медиафайлы. Но что, если у нас есть кастомный тип записей (CPT) или нужно добавить свои данные? Тогда и приходит время для создания пользовательских endpoint’ов.
Базовая структура пользовательского endpoint’а
Создавать свои маршруты можно в файле functions.php или через создание плагина. Для примера создадим маршрут, который возвращает случайную цитату.
add_action('rest_api_init', function () {
register_rest_route('custom/v1', '/quote/', array(
'methods' => 'GET',
'callback' => 'get_random_quote',
));
});
function get_random_quote() {
$quotes = array(
"Код — это поэзия.",
"Сайт жив, пока есть API.",
"Ошибка — это шаг к пониманию.",
);
$random_index = array_rand($quotes);
return array('quote' => $quotes[$random_index]);
}
Разбор кода
- rest_api_init: Хук для инициализации REST API. Здесь мы регистрируем новый маршрут.
- register_rest_route(): Первый аргумент (custom/v1) — namespace. Лучше всего использовать что-то уникальное для вашего проекта. Второй аргумент (/quote/) — сам маршрут.
- methods — HTTP-метод (в данном случае GET).
- callback — функция, которая выполняется при обращении к этому маршруту.
- Функция get_random_quote() возвращает массив с рандомной цитатой.
Как протестировать новый маршрут?
После добавления кода откройте браузер или используйте curl для теста:
curl https://example.com/wp-json/custom/v1/quote
Получим что-то вроде:
{
"quote": "Код — это поэзия."
}
Добавление параметров запроса
Что, если мы хотим вернуть не одну цитату, а несколько? Для этого можно добавить параметр запроса.
add_action('rest_api_init', function () {
register_rest_route('custom/v1', '/quotes/', array(
'methods' => 'GET',
'callback' => 'get_random_quotes',
'args' => array(
'count' => array(
'required' => false,
'default' => 1,
'validate_callback' => function($param) {
return is_numeric($param) && $param > 0;
}
)
)
));
});
function get_random_quotes($request) {
$quotes = array(
"Код — это поэзия.",
"Сайт жив, пока есть API.",
"Ошибка — это шаг к пониманию.",
"Чем больше кода, тем больше багов.",
"REST API — мост между мирами."
);
$count = $request->get_param('count');
$random_quotes = array_rand(array_flip($quotes), min($count, count($quotes)));
return array('quotes' => (array) $random_quotes);
}
Как это работает
- args — массив параметров маршрута.
- Параметр count не обязателен, по умолчанию возвращает одну цитату.
- В validate_callback проверяем, что параметр — положительное число.
- Функция get_random_quotes() обрабатывает параметр и возвращает указанное количество цитат.
Тестируем новый маршрут с параметром:
curl "https://example.com/wp-json/custom/v1/quotes?count=3"
Ответ:
{
"quotes": [
"Ошибка — это шаг к пониманию.",
"REST API — мост между мирами.",
"Код — это поэзия."
]
}
Что дальше?
Теперь вы умеете создавать пользовательские endpoint’ы и добавлять параметры. В следующей части рассмотрим аутентификацию и способы защиты REST API. Ведь открытый доступ к админке — это, мягко говоря, не лучшая идея.
Часть 3 — Аутентификация и безопасность
В предыдущей части мы научились создавать пользовательские endpoint’ы и обрабатывать параметры запросов. Теперь перейдём к одной из самых важных тем — аутентификация и безопасность. Ведь открывать доступ к редактированию контента или управлению сайтом без защиты — это как оставить дверь дома нараспашку.
Почему аутентификация важна?
REST API позволяет не только читать данные, но и выполнять действия, такие как создание, обновление или удаление записей. Чтобы защитить ваш сайт от несанкционированного доступа, необходимо убедиться, что только авторизованные пользователи могут выполнять такие операции.
Способы аутентификации в WordPress REST API
Basic Authentication
Самый простой способ, но подходит только для тестирования на локальной машине. На продакшене использовать нельзя — данные передаются в открытом виде.
JWT (JSON Web Tokens)
Один из самых популярных и безопасных методов для аутентификации в WordPress. Мы рассмотрим настройку именно этого метода.
OAuth 2.0
Более сложный, но мощный способ для интеграции с другими сервисами. Требует дополнительных настроек и знаний.
Установка JWT-аутентификации
Шаг 1: Установка плагина JWT Authentication
Скачайте и установите плагин JWT Authentication for WP REST API. Активируйте его через админ-панель WordPress.
Шаг 2: Настройка плагина
Откройте файл wp-config.php и добавьте следующие строки:
define('JWT_AUTH_SECRET_KEY', 'your-secret-key');
define('JWT_AUTH_CORS_ENABLE', true);
Получение токена JWT
Чтобы получить токен, отправьте POST-запрос на маршрут /wp-json/jwt-auth/v1/token с данными пользователя:
curl -X POST https://example.com/wp-json/jwt-auth/v1/token \
-H "Content-Type: application/json" \
-d '{"username": "your_username", "password": "your_password"}'
Если данные верны, сервер вернёт токен:
{
"token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"user_email": "user@example.com",
"user_nicename": "user",
"user_display_name": "User Name"
}
Использование токена для аутентификации
Теперь, когда у нас есть токен, его нужно добавить в заголовок всех запросов, требующих авторизации:
curl https://example.com/wp-json/wp/v2/posts \
-H "Authorization: Bearer your_token_here"
Если токен валиден, сервер вернёт защищённые данные. Если нет — получим ошибку 401 Unauthorized.
Как ограничить доступ к кастомному endpoint’у?
Допустим, у нас есть маршрут, который создаёт новый пост, и мы хотим ограничить доступ только авторизованным пользователям.
add_action('rest_api_init', function () {
register_rest_route('custom/v1', '/create-post/', array(
'methods' => 'POST',
'callback' => 'create_custom_post',
'permission_callback' => function () {
return current_user_can('edit_posts');
}
));
});
function create_custom_post($request) {
$title = $request->get_param('title');
$content = $request->get_param('content');
$post_id = wp_insert_post(array(
'post_title' => $title,
'post_content' => $content,
'post_status' => 'publish'
));
if (is_wp_error($post_id)) {
return new WP_Error('post_creation_failed', 'Не удалось создать пост', array('status' => 500));
}
return array('message' => 'Пост успешно создан', 'post_id' => $post_id);
}
Разбор кода:
- permission_callback — функция, проверяющая права пользователя перед выполнением запроса. В данном случае мы проверяем, может ли пользователь редактировать посты (edit_posts).
- current_user_can() — стандартная WordPress-функция для проверки прав доступа.
- Если пользователь не авторизован или у него недостаточно прав, запрос будет отклонён.
Как защитить API от злоумышленников?
- Используйте HTTPS — шифруйте данные, особенно токены и пароли.
- Ограничьте доступ к нужным endpoint’ам — не давайте лишних привилегий.
- Логи и мониторинг — отслеживайте подозрительную активность.
- Rate limiting — ограничьте количество запросов от одного пользователя за определённый период.
Что дальше?
Теперь вы умеете настраивать аутентификацию и защищать свой API. В следующей части мы разберём работу с кастомными типами записей (CPT) через REST API.
Часть 4 — Работа с кастомными типами записей (CPT)
Мы уже освоили основы API, создали свои endpoint’ы и настроили аутентификацию. Теперь пришло время погрузиться в более продвинутую тему — работу с кастомными типами записей (Custom Post Types, или CPT). Это позволит расширить возможности вашего сайта и взаимодействовать с нестандартными данными через REST API.
Что такое кастомные типы записей?
В WordPress по умолчанию есть стандартные типы записей: посты (posts), страницы (pages), медиафайлы (media) и несколько других. Однако иногда требуется создать свои типы — например, для портфолио, отзывов или продуктов. Такие записи называются кастомными типами.
Регистрация кастомного типа записей
Давайте создадим кастомный тип записи для отзывов клиентов. Добавим следующий код в functions.php или создадим плагин:
function register_reviews_cpt() {
$args = array(
'public' => true,
'label' => 'Отзывы',
'show_in_rest' => true, // Важно! Делаем CPT доступным через REST API
'supports' => array('title', 'editor', 'custom-fields')
);
register_post_type('reviews', $args);
}
add_action('init', 'register_reviews_cpt');
Что здесь происходит?
- register_post_type() регистрирует новый тип записи с названием reviews.
- public => true делает тип записи видимым на сайте.
- show_in_rest => true включает поддержку REST API для этого типа.
- supports указывает, какие функции будут поддерживаться (заголовок, текст и кастомные поля).
Доступ к CPT через REST API
После регистрации CPT можно получить доступ к записям через стандартный маршрут REST API:
GET https://example.com/wp-json/wp/v2/reviews
Этот запрос вернёт список всех отзывов. Аналогично, можно работать с отдельной записью:
GET https://example.com/wp-json/wp/v2/reviews/{id}
Создание записи через API
Теперь попробуем создать новый отзыв с помощью POST-запроса. Нам понадобится токен авторизации (см. предыдущую часть про JWT).
curl -X POST https://example.com/wp-json/wp/v2/reviews \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Отличный сервис!",
"content": "Очень доволен качеством обслуживания.",
"status": "publish"
}'
Ответ сервера:
{
"id": 123,
"title": {
"rendered": "Отличный сервис!"
},
"content": {
"rendered": "<p>Очень доволен качеством обслуживания.</p>"
},
"status": "publish"
}
Добавление кастомных полей
Допустим, у каждого отзыва есть рейтинг. Добавим его как кастомное поле и отобразим через API.
Добавляем кастомное поле в админке WordPress: В редакторе отзыва создайте кастомное поле rating и задайте значение (например, 5).
Получение кастомных полей через REST API:
function add_rating_to_rest($response, $post) {
$rating = get_post_meta($post->ID, 'rating', true);
if ($rating) {
$response->data['rating'] = $rating;
}
return $response;
}
add_filter('rest_prepare_reviews', 'add_rating_to_rest', 10, 2);
Запрос:
GET https://example.com/wp-json/wp/v2/reviews
Ответ:
{
"id": 123,
"title": {
"rendered": "Отличный сервис!"
},
"content": {
"rendered": "<p>Очень доволен качеством обслуживания.</p>"
},
"rating": "5"
}
Редактирование и удаление записей
Редактирование отзыва:
curl -X POST https://example.com/wp-json/wp/v2/reviews/123 \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"content": "Обслуживание на высшем уровне!",
"rating": "4"
}'
Удаление записи:
curl -X DELETE https://example.com/wp-json/wp/v2/reviews/123 \
-H "Authorization: Bearer your_token_here"
Итог
Теперь вы знаете, как работать с кастомными типами записей через REST API. Это открывает огромные возможности для создания динамичных приложений на основе WordPress — от SPA до мобильных приложений. В следующей части разберём, как работать с пользователями и их правами через API.
Часть 5 — Работа с пользователями и их правами
После освоения кастомных типов записей, настал черед погрузиться в ещё одну важную тему — работу с пользователями и управление правами через REST API. Это особенно важно, если вы разрабатываете фронтенд или мобильное приложение, которое взаимодействует с пользователями. В этой части мы разберем, как получать данные пользователей, управлять их аккаунтами и настраивать доступы через API.
Основные маршруты для работы с пользователями
WordPress REST API предоставляет несколько стандартных маршрутов для работы с пользователями. Вот основные из них:
Получение списка пользователей
GET https://example.com/wp-json/wp/v2/users
Получение информации о конкретном пользователе
GET https://example.com/wp-json/wp/v2/users/{id}
Создание нового пользователя
POST https://example.com/wp-json/wp/v2/users
Обновление данных пользователя
PUT https://example.com/wp-json/wp/v2/users/{id}
Удаление пользователя
DELETE https://example.com/wp-json/wp/v2/users/{id}
Доступ к данным пользователей и безопасность
Пример запроса для получения списка пользователей с авторизацией:
curl -X GET https://example.com/wp-json/wp/v2/users \
-H "Authorization: Bearer your_token_here"
Ответ:
[
{
"id": 1,
"name": "admin",
"email": "admin@example.com",
"roles": ["administrator"]
},
{
"id": 2,
"name": "editor",
"email": "editor@example.com",
"roles": ["editor"]
}
]
Создание пользователя через REST API
Чтобы создать пользователя, отправьте POST-запрос с данными нового пользователя:
curl -X POST https://example.com/wp-json/wp/v2/users \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"username": "newuser",
"email": "newuser@example.com",
"password": "securepassword",
"roles": ["subscriber"]
}'
Ответ сервера:
{
"id": 3,
"username": "newuser",
"email": "newuser@example.com",
"roles": ["subscriber"]
}
Обновление данных пользователя
Допустим, мы хотим обновить роль пользователя или его email:
curl -X PUT https://example.com/wp-json/wp/v2/users/3 \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"email": "updateduser@example.com",
"roles": ["editor"]
}'
Удаление пользователя
Удаление пользователя требует подтверждения передачи force=true, иначе запись будет просто помечена как удалённая:
curl -X DELETE https://example.com/wp-json/wp/v2/users/3?force=true \
-H "Authorization: Bearer your_token_here"
Ответ:
{
"deleted": true,
"previous": {
"id": 3,
"username": "newuser",
"email": "updateduser@example.com"
}
}
Получение информации о текущем пользователе
Если вам нужно получить данные о пользователе, чей токен используется в запросе, можно воспользоваться следующим маршрутом:
GET https://example.com/wp-json/wp/v2/users/me
Этот запрос вернёт информацию о текущем пользователе без необходимости указывать его ID.
Ограничение доступа по ролям
Часто возникает необходимость ограничить доступ к определённым маршрутам в зависимости от роли пользователя. Это можно сделать с помощью permission_callback в кастомных маршрутах:
register_rest_route('custom/v1', '/restricted-data/', array(
'methods' => 'GET',
'callback' => 'get_restricted_data',
'permission_callback' => function () {
return current_user_can('administrator'); // Только администраторы
}
));
Итог
Теперь вы умеете управлять пользователями через WordPress REST API: создавать, обновлять и удалять аккаунты, а также настраивать доступ по ролям. В следующей части мы разберём работу с медиаконтентом — загрузку файлов и управление библиотекой.
Часть 6 — Работа с медиаконтентом
Теперь, когда мы разобрались с пользователями и правами, давайте перейдём к ещё одной важной теме — медиаконтент. В WordPress медиафайлы (изображения, видео, аудио и документы) играют центральную роль. REST API позволяет загружать и управлять файлами прямо из внешних приложений. Давайте разберёмся, как это работает!
Основные маршруты для работы с медиафайлами
WordPress предоставляет следующие стандартные маршруты для взаимодействия с медиафайлами:
Получение списка медиафайлов
GET https://example.com/wp-json/wp/v2/media
Получение конкретного файла
GET https://example.com/wp-json/wp/v2/media/{id}
Загрузка нового файла
POST https://example.com/wp-json/wp/v2/media
Обновление данных файла
POST https://example.com/wp-json/wp/v2/media/{id}
Удаление файла
DELETE https://example.com/wp-json/wp/v2/media/{id}?force=true
Получение списка медиафайлов
Чтобы получить список всех загруженных файлов, отправьте GET-запрос:
curl -X GET https://example.com/wp-json/wp/v2/media \
-H "Authorization: Bearer your_token_here"
Ответ:
[
{
"id": 101,
"title": {
"rendered": "photo.jpg"
},
"media_type": "image",
"source_url": "https://example.com/wp-content/uploads/2024/05/photo.jpg"
},
{
"id": 102,
"title": {
"rendered": "document.pdf"
},
"media_type": "application",
"source_url": "https://example.com/wp-content/uploads/2024/05/document.pdf"
}
]
Загрузка медиафайла через REST API
Чтобы загрузить файл, необходимо отправить POST-запрос с бинарными данными файла. Рассмотрим пример загрузки изображения с помощью curl:
curl -X POST https://example.com/wp-json/wp/v2/media \
-H "Authorization: Bearer your_token_here" \
-H "Content-Disposition: attachment; filename=example.jpg" \
--data-binary @/path/to/your/example.jpg
Ответ сервера:
{
"id": 103,
"title": {
"rendered": "example.jpg"
},
"media_type": "image",
"source_url": "https://example.com/wp-content/uploads/2024/05/example.jpg"
}
Пояснение к параметрам:
- Content-Disposition указывает имя файла.
- —data-binary передаёт файл в бинарном формате.
Обновление информации о медиафайле
Чтобы изменить метаданные файла (например, заголовок или описание), используйте POST-запрос:
curl -X POST https://example.com/wp-json/wp/v2/media/103 \
-H "Authorization: Bearer your_token_here" \
-H "Content-Type: application/json" \
-d '{
"title": "Новое название",
"alt_text": "Описание изображения"
}'
Ответ:
{
"id": 103,
"title": {
"rendered": "Новое название"
},
"alt_text": "Описание изображения",
"source_url": "https://example.com/wp-content/uploads/2024/05/example.jpg"
}
Удаление медиафайла
Удалить файл можно с помощью DELETE-запроса:
curl -X DELETE https://example.com/wp-json/wp/v2/media/103?force=true \
-H "Authorization: Bearer your_token_here"
Ответ:
{
"deleted": true,
"previous": {
"id": 103,
"title": {
"rendered": "Новое название"
},
"source_url": "https://example.com/wp-content/uploads/2024/05/example.jpg"
}
}
Ограничения и безопасность
- Размер файла: Убедитесь, что на сервере установлены правильные ограничения для загрузки файлов (настройки php.ini: upload_max_filesize и post_max_size).
- Типы файлов: WordPress по умолчанию поддерживает загрузку популярных форматов (JPEG, PNG, PDF и т.д.). Для разрешения других типов файлов можно использовать фильтр upload_mimes.
- Права доступа: Только пользователи с ролью редактора или администратора могут загружать и управлять медиафайлами через API.
Подведение итогов
Теперь вы знаете, как взаимодействовать с медиаконтентом через WordPress REST API: получать файлы, загружать новые и обновлять метаданные. В следующей заключительной части рассмотрим расширение REST API с помощью кастомных маршрутов и подключение к сторонним сервисам.
Часть 7 — Расширение REST API и создание кастомных маршрутов
Мы подошли к финальной части нашего гайда! Теперь, когда вы освоили стандартные возможности WordPress REST API, пришло время изучить, как расширить его под свои нужды. Создание кастомных маршрутов открывает возможность добавлять специфичные функции и интегрировать WordPress с внешними сервисами.
Зачем нужны кастомные маршруты?
Стандартный REST API предоставляет базовый функционал, но часто этого недостаточно. Например:
- Добавление новых эндпоинтов для обработки специфичных данных.
- Интеграция с внешними API или базами данных.
- Создание более сложных приложений, где стандартные маршруты не покрывают всех требований.
Регистрация кастомного маршрута
Чтобы создать новый маршрут, нужно использовать функцию register_rest_route() в файле вашей темы или плагина (например, functions.php или my-custom-plugin.php).
add_action('rest_api_init', function () {
register_rest_route('custom/v1', '/hello/', array(
'methods' => 'GET',
'callback' => 'custom_hello_endpoint',
));
});
function custom_hello_endpoint() {
return new WP_REST_Response('Привет, мир!', 200);
}
Разберём код:
- rest_api_init — хук, запускающий регистрацию маршрутов.
- register_rest_route() — функция для создания маршрута.
- Маршрут: ‘custom/v1’ — пространство имён, /hello/ — путь маршрута.
- Метод: GET, но можно использовать POST, PUT, DELETE.
- callback — функция, которая будет выполнена при обращении к маршруту.
Приём данных через параметры запроса
Добавим возможность передавать параметры в наш кастомный маршрут:
register_rest_route('custom/v1', '/greet/', array(
'methods' => 'GET',
'callback' => 'custom_greet_endpoint',
'args' => array(
'name' => array(
'required' => true,
'validate_callback' => function ($param) {
return is_string($param);
}
)
)
));
function custom_greet_endpoint(WP_REST_Request $request) {
$name = $request->get_param('name');
return new WP_REST_Response("Привет, {$name}!", 200);
}
Теперь, если вызвать маршрут https://example.com/wp-json/custom/v1/greet/?name=Иван, сервер вернёт:
{"Привет, Иван!"}
Защита маршрутов и доступ по ролям
Чтобы ограничить доступ к маршруту, можно использовать параметр permission_callback:
register_rest_route('custom/v1', '/secure-data/', array(
'methods' => 'GET',
'callback' => 'get_secure_data',
'permission_callback' => function () {
return current_user_can('administrator');
}
));
function get_secure_data() {
return new WP_REST_Response('Это защищённые данные!', 200);
}
Теперь маршрут будет доступен только администраторам. Остальным пользователям сервер вернёт ошибку 403.
Интеграция с внешними API
WordPress REST API позволяет обращаться к другим сервисам. Например, получим данные из внешнего API:
function get_external_data() {
$response = wp_remote_get('https://jsonplaceholder.typicode.com/posts/1');
if (is_wp_error($response)) {
return new WP_REST_Response('Ошибка получения данных', 500);
}
$body = wp_remote_retrieve_body($response);
return new WP_REST_Response(json_decode($body), 200);
}
register_rest_route('custom/v1', '/external/', array(
'methods' => 'GET',
'callback' => 'get_external_data',
));
Тестирование и отладка маршрутов
Используйте Postman или Insomnia для тестирования кастомных маршрутов. Также в консоли браузера можно отправлять запросы через fetch.
Комментарии
0