JavaRush /Курси /ChatGPT Apps /Шлюз і захист периметра: proxy, rate limiting, черги та b...

Шлюз і захист периметра: proxy, rate limiting, черги та backpressure

ChatGPT Apps
Рівень 16 , Лекція 1
Відкрита

1. Навіщо взагалі захищати периметр ChatGPT App

У класичному вебзастосунку користувач — це браузер, який доволі передбачувано надсилає запити до ваших ендпойнтів. У світі ChatGPT Apps зʼявляється новий тип клієнта: LLM, яка сама вирішує, коли й які інструменти викликати.

Модель може:

  • в одному діалозі кілька разів поспіль викликати той самий tool;
  • експериментувати: «а якщо ще раз викликати suggest_gifts з трохи іншими параметрами?»;
  • працювати паралельно для сотень користувачів.

Додайте до цього ботів, тестові скрипти, а також помилки у власному коді (наприклад, нескінченний цикл, який постійно запускає tool‑call) — і ви отримаєте майже ідеальний рецепт DoS‑атаки з добрих намірів.

Є ще один нюанс — вартість. Кожен tool‑call може:

  • викликати зовнішні платні API (курʼєри, платежі, каталоги),
  • запускати інші LLM (наприклад, RAG‑пошук),
  • стартувати важкі фонові завдання.

Без обмежень і захисту периметра один «невдалий» клієнт може:

  • вивести з ладу всі ваші бекенд‑сервіси за gateway (Gift API, Commerce API тощо),
  • вичерпати ліміти зовнішніх API,
  • і відчутно роздути бюджет на моделі.

Завдання цієї лекції — показати, як gateway/proxy + rate limiting + черги + backpressure перетворюють потенційну катастрофу на керовану систему.

Інсайт

Платформа ChatGPT не надає жодних механізмів захисту вашого MCP‑сервера від зовнішнього трафіку. Будь‑який інтернет‑клієнт може надсилати на нього запити — зокрема й утиліти на кшталт MCP Jam.

Єдине, що може запропонувати ChatGPT, — обмежити вхідний трафік за IP‑адресами, налаштувавши зворотний проксі (наприклад, NGINX) на роботу з allowlist. Якщо IP‑фільтрацію не налаштовано, ваш MCP‑сервер залишається повністю відкритим, а це небезпечно — і для вас, і для ваших користувачів.

2. Proxy/Gateway як «щит» перед backend‑сервісами та агентами

Спочатку пригадаємо схему — але тепер уже крізь призму захисту.

Уявіть типову ситуацію:

flowchart LR
  ChatGPT["ChatGPT / Віджет"]
    --> GW["MCP Gateway (Auth, Rate Limit, Logs)"]

  GW --> GiftAPI["Gift REST API (підбір подарунків)"]
  GW --> CommerceAPI["Commerce REST API (checkout, ACP)"]
  GW --> Analytics["Analytics Service / REST API"]

  GW --> Queue["Черга завдань"]
  Queue --> Worker["Background workers"]

Gateway стоїть між зовнішнім світом (ChatGPT, webhooks, тестові клієнти) і всім іншим. Він:

  • бачить абсолютно всі вхідні запити;
  • першим перевіряє токен і формат запиту;
  • уміє відкидати завідомо некоректні речі (чужий host, підозрілий path, надто велике тіло запиту);
  • вирішує, до якого внутрішнього REST‑/HTTP‑сервісу взагалі має сенс спрямувати запит.

На цьому ж рівні зʼявляються:

  • rate limiting — обмежуємо, скільки запитів можна зробити за певний інтервал часу;
  • найпростіший backpressure — відмовляємо, якщо сервіси «під капотом» уже захлинаються;
  • перехід на асинхронність — важкі речі одразу надсилаємо в чергу, а клієнту відповідаємо: «прийнято, чекаємо».

Тобто gateway — це не лише маршрутизатор, а й «захисний жилет». Головне — не перетворити його на «моноліт усього бізнесу». Про це ми вже говорили в попередній лекції.

3. Які потоки трафіку потрібно контролювати

В екосистемі ChatGPT App зазвичай є три основні типи трафіку, які нас цікавлять із погляду обмежень і захисту.

По‑перше, це MCP tool‑calls від ChatGPT. Тобто все, що приходить через MCP‑протокол: виклики suggest_gifts, get_product_details, create_checkout_session та інших інструментів. Модель може генерувати їх доволі інтенсивно — особливо якщо «під капотом» ще й Agents.

По‑друге, це вихідні запити наших бекендів до зовнішніх API. Усередині сервісу можуть діяти власні rate limits сторонніх систем: каталоги, логістика, платежі. Порушити їх — означає отримати блокування, штрафи або падіння якості.

По‑третє, це вхідні вебхуки — сповіщення від ACP, платіжних провайдерів (Stripe тощо), курʼєрів. Вони приходять незалежно від активності користувачів. Якщо наш ендпойнт пригальмовує або відповідає помилкою, зовнішня система почне робити повторні спроби (retries) і може влаштувати вам «шторм» сповіщень.

Для GiftGenius це виглядає так:

  • користувач і модель активно викликають suggest_gifts і find_similar_gifts;
  • checkout‑tool викликає ACP/комерційний бекенд;
  • після оплати платіжний провайдер надсилає webhooks payment.succeeded / payment.failed.

Усі ці потоки сходяться в одну точку — Gateway. Отже, саме там і логічно ставити «лічильники, фільтри й запобіжники».

4. Rate limiting: базовий захист і економія коштів

Що таке rate limiting у нашому контексті

Rate limiting — це механізм, який обмежує кількість запитів від конкретного клієнта за одиницю часу. Ідея стара як інтернет, але в контексті ChatGPT Apps вона одразу розвʼязує три завдання:

  • не дає одному клієнту (або помилці) «покласти» ваші сервіси;
  • допомагає дотримуватися лімітів зовнішніх API;
  • захищає ваш гаманець від неконтрольованих викликів моделей.

Класичні алгоритми:

  • фіксоване вікно (Fixed Window),
  • ковзне вікно (Sliding Window),
  • Token Bucket (відро/«бакет» із токенами).

Нам важливо розуміти це радше концептуально: «протягом хвилини — не більше N запитів», «кожен запит “зʼїдає” токен, токени поповнюються зі швидкістю X за секунду» тощо. Реалізацію зазвичай бере на себе бібліотека або API Gateway.

Де ставити ліміти

Ліміти можна вводити на різних рівнях.

На рівні зворотного проксі (NGINX, Cloudflare, AWS API Gateway) зручно:

  • відсікати найагресивніший трафік за IP;
  • обмежувати розмір тіла запиту;
  • захищатися від простих DDoS‑патернів.

На рівні MCP Gateway (застосунок) корисно робити більш «змістовний» rate limiting:

  • за користувачем (userId із токена),
  • за організацією (tenantId),
  • за типом операції (наприклад, create_checkout_session лімітуємо жорстко, search — мʼякше),
  • за джерелом (webhook vs tool‑call).

Окремо можна додавати ліміти вже в самих мікросервісах — для особливо дорогих операцій. Але це наступний рівень деталізації.

Як обирати ключ для лімітів

Найпоширеніша помилка — лімітувати за IP‑адресою. Для ChatGPT це не надто корисно:

  • усі запити можуть іти з одного діапазону OpenAI,
  • різні користувачі опиняються за однією й тією самою IP‑адресою.

Нам значно корисніше:

  • userId — конкретний користувач у вашому застосунку;
  • tenantId — організація (якщо ви робите B2B і один чат використовують багато співробітників);
  • API‑токен або clientId, якщо у вас кілька інтеграцій.

У GiftGenius зазвичай достатньо userId + tenantId, витягнутих із токена, який ChatGPT передає у викликах MCP.

Проста реалізація rate limiting у TypeScript

Уявімо, що в нас є невеликий MCP Gateway на Express. Додамо туди найпростіший rate limiting: не більше 30 tool‑calls на хвилину для одного користувача.

// Примітивний rate limiting: N запитів на хвилину на userId
const WINDOW_MS = 60_000;
const MAX = 30;
const hits = new Map<string, { ts: number; count: number }>();

function rateLimit(req: Request, res: Response, next: NextFunction) {
  const userId = (req.headers["x-user-id"] as string) ?? "anonymous";
  const now = Date.now();
  const rec = hits.get(userId) ?? { ts: now, count: 0 };

  if (now - rec.ts > WINDOW_MS) {      // Вікно «застаріло» — починаємо заново
    rec.ts = now;
    rec.count = 0;
  }
  rec.count += 1;
  hits.set(userId, rec);

  if (rec.count > MAX) {
    return res.status(429).json({
      error: "rate_limit_exceeded",
      retryAfterSec: 60,
      message: "Too many tool calls, please retry later."
    });
  }
  next();
}

А тепер використаємо його в маршруті MCP:

// Застосовуємо middleware до всіх MCP tool-calls
app.post("/mcp/tools/call", rateLimit, async (req, res) => {
  const result = await callBackendForTool(req.body); // REST-виклик у Gift/Commerce/Analytics API
  res.json(result);
});

Ключові моменти:

  • ми повертаємо змістовну помилку (error: "rate_limit_exceeded"), а не просто 500;
  • модель зможе прочитати цю помилку, зрозуміти, що сталося, і коректно пояснити користувачеві ситуацію — замість того, щоб почати вигадувати.

У реальній системі лічильники, звісно, живуть не в памʼяті одного процесу, а в Redis або іншому спільному сховищі. Так само це працює і в кластері. Але для розуміння принципу цього достатньо.

Rate limiting і ліміти на рівні gateway захищають нас від лавини запитів. Але вони не розвʼязують іншу проблему: окремі операції все одно можуть бути дуже важкими й виконуватися довго. Тут уже одного синхронного HTTP недостатньо — і на сцену виходять черги та асинхронні завдання.

5. Черги й асинхронні завдання: коли синхронно вже не виходить

Проблема таймаутів ChatGPT

Навіть якщо ви акуратно налаштували rate limiting, ChatGPT (як і будь‑які HTTP‑клієнти) не любить, коли відповідь надходить занадто пізно. Платформа обмежує час виконання виклику інструмента (tool‑call). І якщо ви чекатимете, поки завершить роботу якийсь «суперскладний» алгоритм, то:

  • користувач бачитиме нескінченний спінер;
  • платформа обірве запит за таймаутом;
  • модель вирішить, що «щось пішло не так», і почне вигадувати пояснення.

Рішення — переводити важкі операції в асинхронний режим. Класичний патерн:

  1. Gateway приймає запит.
  2. Ставить завдання в чергу.
  3. Одразу повертає відповідь 202 Accepted з jobId.
  4. Окремий воркер забирає завдання з черги та обробляє його.
  5. Клієнт (наш віджет або навіть ChatGPT через додатковий tool) періодично запитує статус за jobId або отримує сповіщення через MCP‑подію.

У термінах ChatGPT App це зазвичай виглядає як два інструменти. Перший tool приймає запит, ставить завдання в чергу та повертає jobId. Другий — дозволяє моделі або віджету за цим jobId дізнаватися статус і забирати результат. Додатково ці події про прогрес можна дублювати через MCP‑нотифікації.

Мінічерга для GiftGenius (приклад коду)

Припустімо, у нас є важкий інструмент generate_large_gift_report, який може працювати десятки секунд. У реальному App він міг би повертати лише jobId, а окремий tool get_report_status дозволяв би моделі або віджету за цим jobId дізнаватися стан і забирати результат. На рівні Gateway зробимо для нього окремий ендпойнт із чергою.

type Job = { id: string; payload: any };
const queue: Job[] = [];
const MAX_QUEUE = 100;

app.post("/mcp/tools/generate_report", (req, res) => {
  if (queue.length >= MAX_QUEUE) {
    return res.status(503).json({
      error: "system_busy",
      message: "System is busy, please retry later."
    });
  }

  const job: Job = { id: crypto.randomUUID(), payload: req.body };
  queue.push(job);
  res.status(202).json({ jobId: job.id, status: "accepted" });
});

І примітивний воркер, який раз на 200 мс бере одне завдання:

async function processJob(job: Job) {
  // Тут викликаємо реальний backend-сервіс або агентний workflow через REST
  await handleHeavyGiftReport(job.payload);
}

setInterval(async () => {
  const job = queue.shift();
  if (!job) return;
  await processJob(job);
}, 200);

Зрозуміло, що це сильно спрощений приклад:

  • у реальному житті черга живе в Redis, SQS, Kafka тощо;
  • статус завдання зберігається десь іще, щоб його можна було запитати;
  • воркерів зазвичай кілька.

Але сама ідея вже зрозуміла: Gateway не тримає запит відкритим, доки все не виконається. Він приймає, ставить у роботу й відповідає швидко.

6. Backpressure: як не потонути у власній черзі

Чим backpressure відрізняється від rate limiting

Rate limiting у першу чергу відповідає на запитання: «скільки запитів може робити один клієнт за інтервал часу?». Це захист від «надто активного користувача» або помилки на боці конкретного клієнта.

Backpressure натомість каже: «а скільки всього завдань/запитів наша система здатна “перетравити” одночасно, не розсипавшись?». Це вже про загальний обсяг навантаження — незалежно від того, від кого воно надійшло.

Приклад:

  • rate limiting: «користувач не може викликати suggest_gifts частіше, ніж 30 разів на хвилину»;
  • backpressure: «у черзі не може бути більше 100 невиконаних завдань; інакше ми починаємо відмовляти всім новим запитам».

В ідеалі ці механізми доповнюють одне одного: rate limiting тримає клієнтів у межах, а backpressure рятує систему, якщо людей усе одно прийшло надто багато.

Проста реалізація обмеження активних завдань

Один із найпростіших варіантів backpressure — обмежити кількість активних викликів «під капотом». Наприклад, не тримати одночасно більше 50 активних tool‑calls до конкретного бекенд‑/REST‑сервісу (Gift API, Commerce API тощо).

let activeCalls = 0;
const MAX_ACTIVE = 50;

app.post("/mcp/tools/call", async (req, res) => {
    if (activeCalls >= MAX_ACTIVE) {
        return res.status(429).json({
            error: "gateway_overloaded",
            message: "Gateway is temporarily overloaded, please retry later."
        });
    }

    activeCalls += 1;
    try {
        const result = await callBackendForTool(req.body); // REST-виклик у Gift/Commerce/Analytics API
        res.json(result);
    } catch (err) {
        console.error("Tool call error", err);
        res.status(500).json({ error: "internal_error" });
    } finally {
        activeCalls -= 1;
    }
});

Що тут відбувається:

  • поки кількість одночасно виконуваних запитів менша за MAX_ACTIVE, ми пропускаємо новий виклик;
  • коли ліміт вичерпано — одразу повертаємо змістовну помилку;
  • важливо обовʼязково зменшувати лічильник у finally, щоб у разі помилок не втратити «слоти».

Це і є найпростіший backpressure: ми чесно кажемо клієнту: «зараз не можу, спробуйте пізніше», замість того щоб бездумно приймати все й “помирати”.

Надалі можна:

  • зробити різні MAX_ACTIVE для різних типів операцій (наприклад, checkout майже завжди пропускати, а генерацію звітів обмежувати жорсткіше),
  • перемикати ліміти динамічно залежно від метрик навантаження.

7. Вебхуки та «шторми»: захист вхідних подій

Досі ми дивилися переважно на запити, які ініціюємо ми або ChatGPT (tool‑calls, вихідні запити, асинхронні завдання). Але в реальному житті є ще одне важливе джерело навантаження на Gateway — вхідні вебхуки від зовнішніх систем.

Вебхуки — це зворотний бік медалі: якщо tool‑calls ініціюємо ми (через модель), то вебхуки ініціює зовнішній сервіс. Це якраз той третій тип трафіку з розділу 3, який ми не контролюємо ні за часом, ні за частотою, але мусимо вміти обробляти без збоїв. Платіжний провайдер, ACP, логістика — усі вони надсилають сповіщення (вебхуки) на наш ендпойнт за кожної значущої зміни: «платіж пройшов», «замовлення створено», «доставка оновила статус».

Проблеми починаються, коли:

  • наш ендпойнт відповідає повільно;
  • відповідає помилкою;
  • періодично недоступний.

Тоді зовнішній сервіс, дотримуючись кращих практик, починає робити повторні спроби (retries). І якщо не пощастить, ви отримаєте «шторм» вебхуків — десятки або сотні повторних подій, які намагаються «достукатися» до вас будь‑якою ціною.

Щоб не «загинути» від такої турботи, на рівні Gateway варто:

  1. Лімітувати вхідні вебхуки за джерелом: наприклад, «не більше 10 подій на хвилину для одного event_type від конкретного провайдера».
  2. Перевіряти підпис до розбору JSON: HMAC‑підпис або аналогічний механізм дозволяє відкидати фальшиві запити.
  3. Робити обробку подій ідемпотентною: за event_id або аналогічним полем, щоб повторні події не призводили до дублікатів замовлень чи платежів.
  4. За сильного «шторму» вмикати додатковий backpressure: тимчасово відповідати «503: спробуйте пізніше», якщо downstream‑сервіси не встигають.

Найпростіший приклад (ідея, а не production‑код):

app.post("/webhooks/stripe", rateLimitWebhook, (req, res) => {
    const sig = req.headers["stripe-signature"] as string;
    if (!isValidSignature(req.rawBody, sig)) {
        return res.status(400).send("Invalid signature");
    }

    const event = JSON.parse(req.body.toString());
    if (isAlreadyProcessed(event.id)) {
        return res.json({ received: true }); // ідемпотентність
    }

    handleStripeEvent(event);
    res.json({ received: true });
});

Тут на рівні Gateway ми:

  • застосовуємо окрему політику rate limiting для вебхуків;
  • валідуємо підпис до того, як довіряти вмісту;
  • захищаємося від дублів через isAlreadyProcessed.

8. Застосовуємо до GiftGenius: приклад політики лімітів і черг

Тепер відійдімо від абстракцій і подивімося, як це може виглядати для нашого навчального GiftGenius.

Уявімо три ключові сценарії:

  1. Пошук подарунків (suggest_gifts, find_similar_gifts).
  2. Створення замовлення / checkout (create_checkout_session, confirm_order).
  3. Прийом вебхуків від платіжного провайдера та ACP.

Для кожного сценарію логічно визначити:

  • за яким ключем рахуємо ліміт;
  • скільки запитів на хвилину дозволяємо;
  • що робимо при перевищенні.

Наприклад:

Сценарій Ключ ліміту Ліміт на хвилину Поведінка при перевищенні
Пошук подарунків userId 30 429 + порада «звузити параметри пошуку»
Створення замовлення userId + tenantId 5 429 + текст «надто багато спроб — перевірте замовлення»
Вхідні вебхуки provider + eventType 10 429/503, лог, можлива деградація

Для вебхуків зазвичай логічніше лімітувати за комбінацією «провайдер + тип події», а дублікати відсікати окремим механізмом ідемпотентності — за event_id.

У коді це перетворюється на різні middleware: rateLimitSearch, rateLimitCheckout, rateLimitWebhook.

Для важких операцій, на кшталт «згенерувати великий PDF‑звіт про подарунки за рік», ми використовуємо чергу й асинхронний патерн, який уже показали вище. У такому разі Gateway:

  • приймає запит від ChatGPT;
  • ставить завдання в чергу;
  • повертає jobId і підказку для моделі, як отримати статус;
  • обмежує розмір черги (backpressure), щоб не переповнити систему.

Важливо памʼятати: і rate limiting, і backpressure — це не лише про безпеку та надійність, а й про UX. Набагато приємніше почути від асистента: «Сервіс зараз перевантажений, давайте спробуємо за хвилину», ніж дивитися на спінер до таймауту або отримати «Internal Server Error».

9. Мініпрактикум: додаємо захист у наш MCP Gateway

Щоб матеріал не залишився теорією, зберемо мініпрактикум, який ви можете реалізувати у своєму навчальному проєкті.

Rate limiting для всіх MCP tool‑calls

Додайте middleware rateLimit (як вище) і підключіть його до /mcp/tools/call. Для початку можна взяти дуже простий ліміт: 30 запитів на хвилину на userId. Потім поекспериментуйте:

  • зменште ліміт і подивіться, як ваш App і модель на це реагують;
  • зробіть різні ліміти для різних типів tools, передаючи, наприклад, toolName у middleware.

Найпростіший backpressure за активними викликами

Додайте лічильник activeCalls і обмеження MAX_ACTIVE. Спробуйте імітувати навантаження (наприклад, скриптом, який надсилає пакет запитів) і подивіться, у який момент Gateway почне відповідати gateway_overloaded.

Тут важлива саме поведінка: ви не чекаєте, доки все впаде, а відмовляєтеся брати нові завдання й чесно кажете клієнту, що зараз надто спекотно.

Черга для важкого інструмента

Виберіть одну важку операцію (або штучно зробіть її «важкою», вставивши setTimeout/довгий fetch) і переведіть на патерн «черга + jobId». Мінімально:

  • endpoint POST /mcp/tools/generate_report — ставить завдання в чергу і повертає jobId;
  • endpoint GET /jobs/:id — повертає статус (pending, done, error, плюс, можливо, результат);
  • воркер, який раз на X мілісекунд викликає processJob.

Цього достатньо, щоб зрозуміти, як виглядатиме реальна інтеграція з BullMQ або іншим queue‑движком.

10. Типові помилки під час захисту периметра

Помилка № 1: Лімітувати лише за IP.
У світі ChatGPT Apps це майже марно: більшість запитів надходить з адрес OpenAI, і всі ваші користувачі опиняться за однією й тією самою IP. У підсумку хтось один «випалить» ліміт для всіх, а справжній винуватець залишиться невідомим. Правильніше лімітувати за userId, tenantId або токеном, а IP використовувати лише як дуже грубий фільтр на рівні зворотного проксі.

Помилка № 2: Повернути «голий» 500 замість змістовної помилки.
Якщо у відповідь на перевищення ліміту або перевантаження ви просто надсилаєте 500 Internal Server Error, модель не розуміє, що сталося, і починає вигадувати. Натомість структурована помилка з кодом (rate_limit_exceeded, gateway_overloaded) і зрозумілим для людини описом дозволяє LLM коректно пояснити ситуацію користувачеві й, за потреби, запропонувати спробувати ще раз пізніше.

Помилка № 3: Робити нескінченну чергу без backpressure.
Іноді здається: «давайте просто все ставити в чергу, а там розберемося». На практиці черга розростається до тисяч завдань, затримки збільшуються, памʼять закінчується, а користувачі так і не бачать результату. Завжди обмежуйте розмір черги й кількість активних операцій. Краще чесно відмовити новим запитам із 503 або 429, ніж перетворити чергу на чорну діру.

Помилка № 4: Покладатися лише на rate limiting і ігнорувати вебхуки.
Багато хто захищає тільки вхідний трафік від ChatGPT, а вебхуки залишають «якось воно буде». Коли платіжний провайдер починає робити повторні спроби, саме вебхуки можуть влаштувати вам справжній шторм. Для ендпойнтів вебхуків потрібні власні ліміти, перевірка підпису та ідемпотентна обробка. Інакше легко отримати десяток дублікатів одного й того самого замовлення.

Помилка № 5: Зберігати всі лічильники й чергу лише в памʼяті одного інстансу.
Для навчального проєкту це нормально. Але в робочому середовищі, коли ви масштабуєте Gateway до кількох інстансів, лічильники на кожному вузлі почнуть «жити своїм життям». Ліміти перестануть бути глобальними, а перезапуск вузла обнулить чергу. У реальній системі для зберігання стану лімітів і черг використовують спільне сховище (Redis, хмарні черги тощо). Ми ще поговоримо про це в лекціях про масштабування та робоче середовище.

Помилка № 6: Додавати бізнес‑логіку всередину Gateway, «раз уже він і так усюди посередник».
Іноді виникає спокуса: «ну давайте прямо в Gateway вирішимо, які подарунки показувати — нам же все одно туди приходять запити». У результаті gateway перетворюється на моноліт із купою логіки, який одночасно і маршрутизатор, і «бізнес‑мозок», і логер. Це сильно ускладнює масштабування та супровід. Gateway має залишатися мережевим/інфраструктурним шаром: автентифікація, авторизація, ліміти, кеш, маршрутизація — так; підбір подарунків — ні.

Помилка № 7: Вважати, що «ми маленькі — нас це не стосується».
Часто думають: «у нас же не мільйон користувачів, можна обійтися без gateway/лімітів». Насправді навіть одна помилка в клієнтському коді (або в промпті, який змушує модель викликати tool по колу) може влаштувати вам невеликий, але дуже локальний апокаліпсис. Базовий rate limiting і хоча б примітивний backpressure — це не розкіш, а гігієна робочого середовища: користуватися треба від самого початку, доки не почало боліти.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ