Модуль 18

Middleware та життєвий цикл запиту

Ось урок, створений спеціально для тебе у стилі CS50. Приготуйся, сьогодні ми зазирнемо під капот веб-серверів!


🎓 Урок: Middleware та Життєвий цикл запиту

Вітаю! Я радий бачити вас на цьому занятті.

Сьогодні ми поговоримо про "невидимих героїв" веб-розробки. Про той клей, який тримає наші додатки купи і не дає їм розсипатися на тисячі шматків копіпасту. Ми говоримо про Middleware.

1. 🔥 Вступ: Чому ми тут?

Уявіть собі, що ви будуєте найбезпечніший нічний клуб у місті. У вас є VIP-кімната (це ваш сервер з даними). І є купа людей, які хочуть туди потрапити (це HTTP-запити).

Як ви організуєте вхід?

Варіант А: Ви ставите охоронця безпосередньо в VIP-кімнаті. Людина заходить, сідає на диван, і лише тоді охоронець питає: "А у вас є квиток?". Якщо ні — виганяє. Варіант Б: Ви ставите кордон перед входом. Спочатку фейс-контроль, потім каса, потім гардероб, і лише потім — VIP-кімната.

Риторичне запитання: Який варіант ефективніший? Звісно, другий. Навіщо пускати когось до бару, якщо йому навіть немає 18 років?

У чому проблема в коді? Уявіть, що у вас є 50 різних маршрутів (сторінка профілю, налаштування, кошик, історія замовлень). Усі вони потребують, щоб користувач був залогінений. Якщо ви будете писати перевірку if (!user.isLoggedIn) у кожній з цих 50 функцій — ви збожеволієте. А якщо логіка зміниться? Вам доведеться правити код у 50 місцях. Це жах. Це "спагеті-код".

Middleware (проміжне ПЗ) — це і є той самий фейс-контроль, гардероб і каса, через які проходить клієнт (запит), перш ніж дістатися до головної вечірки (вашого контролера).


2. 🧠 Теоретична база (Як це працює під капотом)

Давайте спростимо. Що таке веб-сервер? Це функція, яка приймає Запит (Request) і повертає Відповідь (Response).

Але між "прийняв" і "відповів" може статися багато всього.

Життєвий цикл запиту

Уявіть собі конвеєр на заводі. 1. Start: Сировина (HTTP Request) потрапляє на стрічку. 2. Middle: Вона проходить через різні станції. На одній її миють, на іншій фарбують, на третій перевіряють якість. 3. End: Готовий продукт (HTTP Response) відправляється клієнту.

Ці "станції" посередині — це і є Middleware.

Головна формула Middleware

Це просто функція, яка має доступ до трьох речей: 1. req (об'єкт запиту) — що прийшло? 2. res (об'єкт відповіді) — чим відповімо? 3. next (чарівна кнопка) — передати естафету далі.

❗️ Що треба запам’ятати залізно: Middleware має лише два шляхи: 1. Або завершити запит (надіслати відповідь і сказати "до побачення"). 2. Або викликати next(), щоб передати запит наступному middleware в черзі.

Якщо ви не зробите ні того, ні іншого — ваш сервер "зависне", і клієнт вічно бачитиме кружечок завантаження.

Інтуїтивно: Це як шари цибулі. Запит пробивається крізь шари всередину, а відповідь — назовні.


3. 🧪 Приклади (Codetime!)

Ми будемо використовувати синтаксис, схожий на Express.js (Node.js), бо він найбільш наочний, але логіка однакова і в Python (Django/FastAPI), і в PHP (Laravel).

Приклад 1: "Балакучий" Middleware (Логування)

Найпростіший приклад. Ми просто хочемо знати, що хтось прийшов.

// Це наш Middleware
function logger(req, res, next) {
    console.log(`🔔 Хтось постукав на сторінку: ${req.url}`);

    // Дуже важливо! Передаємо естафету далі.
    next(); 
}

// Використання
app.use(logger); 

app.get('/', (req, res) => {
    res.send("Привіт, світе!");
});

Питання до вас: Що буде, якщо я видалю рядок next() у функції logger? (Пауза для роздумів...) Відповідь: Сервер напише в консоль "Хтось постукав...", але браузер користувача буде вічно вантажитись. До res.send справа просто не дійде. Ланцюжок розірвано.


Приклад 2: Суровий охоронець (Авторизація)

Тепер реальний кейс. Захищаємо адмінку.

function checkPassword(req, res, next) {
    const secret = req.query.secret; // Шукаємо пароль у запиті

    if (secret === 'cs50') {
        // Пароль правильний? Проходь!
        next();
    } else {
        // Пароль неправильний? Стоп!
        // Ми НЕ викликаємо next(), ми перериваємо цикл.
        res.status(403).send("⛔️ Доступ заборонено!");
    }
}

// Застосовуємо тільки до секретної сторінки
app.get('/admin', checkPassword, (req, res) => {
    res.send("🔥 Ласкаво просимо до панелі керування!");
});

Тут ми бачимо силу middleware: ми відфільтрували "поганий" трафік ще до того, як він дістався головної логіки.


Приклад 3: Турботливий помічник (Модифікація даних)

Middleware може не тільки пропускати або забороняти. Він може змінювати запит. Уявіть, що ви приходите в готель, і портьє одразу чіпляє вам бирку на валізу.

function addTimestamp(req, res, next) {
    // Ми дописуємо нову властивість прямо в об'єкт запиту
    req.requestTime = new Date().toISOString();
    next();
}

app.use(addTimestamp);

app.get('/', (req, res) => {
    // Тепер у нас є доступ до req.requestTime!
    res.send(`Відповідь сформовано о: ${req.requestTime}`);
});

Навіщо це? Так працюють парсери JSON. Приходить "сирий" текст, middleware перетворює його на зручний об'єкт, і основна функція вже працює з готовими даними.


4. 🛠 Практична частина

Час забруднити руки кодом. Уявіть, що ви Back-end розробник.

Завдання 1: "Технічні роботи" Напишіть middleware, який перехоплює всі запити і замість сайту повертає текст: "Вибачте, ми на технічному обслуговуванні. Повернемось за 5 хвилин.". Підказка: Чи потрібно тут викликати next()?

Завдання 2: "Детектив" Створіть middleware, який перевіряє, який браузер використовує клієнт (зазвичай це заголовок User-Agent). Якщо це Internet Explorer (жах!) — повертайте помилку "Оновіть браузер". Якщо Chrome/Firefox/Safari — пропускайте далі.

Завдання 3: "Виправ баг" Розробник-початківець написав такий код:

function myMiddleware(req, res, next) {
    if (req.url === '/home') {
        res.send("Це дім");
    }
    next();
}

Питання: Що трапиться, якщо користувач зайде на /home? Чому в консолі сервера може з'явитися помилка Error: Cannot set headers after they are sent to the client?

Міні-кейс: "Лічильник" Як би ви реалізували підрахунок кількості відвідувань сайту, використовуючи глобальну змінну і middleware?


5. 💡 Мислення як у розробника

Як відрізнити новачка від профі в темі Middleware?

  1. Порядок має значення (Order Matters). Новачки часто ставлять middleware для обробки помилок на самому початку. Але ж помилки виникають в кінці! Правило: Спочатку йдуть ті, що "готують" запит (парсери, логери), потім маршрути (routes), а в самому кінці — обробники помилок. Це як воронка.

  2. Принцип єдиної відповідальності. Не пишіть одну гігантську функцію godMiddleware, яка робить все: логує, перевіряє пароль і парсить боді. Розбийте це на маленькі цеглинки: logger, auth, bodyParser. З маленьких цеглинок легше будувати великі замки.

  3. Не блокуйте потік. Якщо ваше middleware робить щось дуже важке (наприклад, рахує число Пі до мільярдного знаку), весь сервер зупиниться для всіх користувачів. Важкі операції — це окрема тема (асинхронність), але пам'ятайте: middleware має працювати швидко. Тук-тук — пройшов далі.


6. 🧩 Підсумок

Отже, що ми сьогодні розібрали?

  • Middleware — це посередник. Це фільтр, через який проходять дані.
  • Ви зрозуміли силу функції next(): без неї конвеєр зупиняється.
  • Ви навчилися перехоплювати запити, змінювати їх або відхиляти до того, як вони нароблять лиха.

Тепер ви не просто пишете функції, ви керуєте потоком даних у вашому додатку. Ви архітектор, який будує надійну систему безпеки та обробки.

Що далі? Тепер, коли ми вміємо фільтрувати та обробляти запити, виникає питання: а де зберігати дані про користувачів надовго? У змінних? Вони зникнуть при перезавантаженні. На наступному уроці ми познайомимося з Базами Даних і дізнаємося, як наше middleware може "спілкуватися" з вічною пам'яттю.

А поки що — це був CS50. (Тобто, ваш урок). ;)


📝 Як тобі такий формат?

Якщо ти готовий, спробуй виконати "Завдання 1" прямо зараз у голові або на папері, і ми перевіримо результат!