JavaRush /Курси /Java Server /Серверний режим і HttpServ...

Серверний режим і HttpServer

Java Server
Рівень 22 , Лекція 0
Відкрита

1. Контекст: клієнт і сервер

Якщо дивитися на проєкт зверху, то на цьому етапі ReadLater Starter перестає бути «консольним застосунком з HTTP-клієнтом» і починає поводитися як маленький бекенд. Це не просто «ще один режим для галочки», а зміна ролі: раніше ми були тими, хто стукає в чужий API, а тепер — тими, до кого стукатимуть Postman, браузер або інший клієнт. І ось тут стається майже магічна, а насправді цілком інженерна річ: застосунок більше не «робить справу й іде», він «стає на пост» і чекає на вхідні запити.

Добре допомагає проста таблиця порівняння. Вона не про те, «хто кращий», а про те, як у голові розробника змінюється контекст:

Ознака Клієнтський режим (catalog ...) Серверний режим (server)
Хто ініціює дію наш застосунок зовнішній клієнт (Postman/браузер/curl)
Напрям HTTP вихідні запити вхідні запити
Життєвий цикл зробив → показав → завершився запустився → слухає порт → живе довго
Головний критерій «працює» ми отримали очікувану відповідь від провайдера клієнт отримав коректну відповідь від нас
Інструмент перевірки логи + вивід у консоль Postman/браузер + логи

У цьому й полягає ключова зміна: ми вчимося думати не так: «я викликав метод і отримав результат», а так: «хтось зовні зробив запит, а я зобовʼязаний повернути коректну HTTP-відповідь».

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

2. Режим server у застосунку

На старті дуже хочеться зробити абияк: «ну давайте в main() додамо if і запустимо сервер». Це приблизно як лагодити машину ізолентою: іноді працює, але потім соромно навіть перед собою, а іноді ще й дим іде. Ми вводимо явний режим запуску SERVER, тому що проєкт має залишатися одним артефактом із передбачуваною поведінкою. Ми вже домовилися, що в застосунку є режими CATALOG_SEARCH, CATALOG_DETAILS, і тепер зʼявляється третій — SERVER. Це робить запуск читабельним, логування — зрозумілим, а конфігурацію — придатною без зайвого шаманства.

Ще важливіше: у серверному режимі в застосунку інший життєвий цикл. Якщо клієнтський режим схожий на «піти в магазин за хлібом» (пішов, купив, повернувся), то серверний режим схожий на «відкрити магазин» (поставити табличку, увімкнути касу і стояти весь день). І якщо ці два сценарії змішати в одній неявній гілці, ви швидко отримаєте застосунок, який складно запускати, складно пояснювати і ще складніше підтримувати.

Мінімальне «закріплення» режиму виглядає ось так — без магічних рядків, без прихованих if-ів:

public enum LaunchMode {
    // Клієнтський режим: виконуємо вихідний запит для пошуку
    CATALOG_SEARCH,

    // Клієнтський режим: виконуємо вихідний запит для деталей
    CATALOG_DETAILS,

    // Серверний режим: запускаємо HTTP-сервер і чекаємо на вхідні запити
    SERVER
}

І в точці входу ми явно вибираємо гілку поведінки. Навіть якщо далі всередині буде багато деталей, сама розвилка має бути простою й читабельною:

import static com.example.readlater.app.LaunchMode.*;

// launchMode обчислюється з аргументів/конфігу до цього моменту
switch (launchMode) {
    case CATALOG_SEARCH -> runCatalogSearch(args);
    case CATALOG_DETAILS -> runCatalogDetails(args);

    // Тут починається "довгоживучий" режим застосунку
    case SERVER -> startServer(appConfig);
}

Зверніть увагу, наскільки це «некрасиво-просто»: ми не намагаємося бути розумнішими, ніж потрібно. Зараз важливіше, щоб студент міг очима прочитати поведінку застосунку і зрозуміти: «ага, ось тут серверний режим, а ось тут — клієнтські режими».

3. Мета server-mode на сьогодні

Коли чуєш «серверний режим», мозок одразу домальовує щось велике: «ендпоінти, бази даних, авторизація, красиві JSON-відповіді, а де тут Swagger». Спокійно. Ми свідомо тримаємо фокус вузьким: сьогодні нам важливо побачити сам факт вхідного HTTP-запиту і сам факт коректної відповіді. Усе інше — це наступний шар складності, і якщо стрибнути туди одразу, серверна фаза перетвориться на кашу з деталей.

У нормальному бекенд-проєкті є технічний, нудний, але неймовірно корисний ендпоінт — health check. Він відповідає на просте запитання: «сервіс живий?». Не «усі фічі працюють», не «бізнес-логіка ідеальна», а просто «процес запущено і він здатен відповісти мережею». Ми почнемо саме з цього, тому що це найчесніший мінімальний контракт між клієнтом і сервером.

І ось тут важлива думка: результат серверного режиму вимірюється не тим, що ви побачили в консолі «Server started», а тим, що зовнішній клієнт отримав очікувану HTTP-відповідь. Тобто критерій успіху переїжджає зі світу «я щось запустив» у світ «хтось інший отримав результат».

Щоб закріпити цю думку, корисно подивитися на просту схему:

sequenceDiagram
    participant C as "Клієнт: Postman/браузер"
    participant A as "ReadLater Starter: серверний режим"

    C->>A: "GET /health"
    A->>C: "200 OK + JSON"

На цій схемі немає «бізнес-логіки» — і це чудово. Нам зараз важлива механіка: запит дійшов, обробник спрацював, відповідь відправлено.

4. HttpServer з JDK як навчальний місток

Легко впасти в дві крайнощі. Перша — «раз у JDK є HttpServer, значить, і в production можна писати так». Друга — «якщо це не Spring Boot, то взагалі несерйозно». Обидві крайнощі шкідливі, тож давайте поставимо акуратну рамку.

HttpServer з JDK (модуль jdk.httpserver, пакет com.sun.net.httpserver) — це мінімальний вбудований серверний API, який дає змогу підняти HTTP-сервер без сторонніх залежностей і без фреймворків. Він не намагається бути «повноцінним вебстеком», і це нам зараз якраз на руку: ми можемо прочитати код цілком і побачити, де починається і де закінчується серверна магія. Тобто магії тут не буде — буде механіка, яку ви зможете розібрати власноруч.

Чому це корисно перед Spring? Тому що Spring MVC потім візьме на себе величезну частину рутини: маршрутизацію, binding JSON у DTO, виставлення заголовків, обробку помилок, коректні статуси й багато іншого. Але якщо ви жодного разу не бачили, як ця робота виглядає вручну, Spring сприйматиметься як «заклинання з анотаціями». Наше завдання — щоб ви після цього курсу думали: «ага, я знаю, що там усередині приблизно відбувається, просто тепер це автоматизовано».

При цьому чесно: HttpServer в JDK — не той інструмент, який зазвичай обирають для бойового бекенду. Там є обмеження щодо екосистеми, зручності й розширюваності, а в реальних проєктах частіше живуть сервери на базі Spring Boot (Tomcat/Jetty/Netty), Quarkus, Micronaut тощо. Але сьогодні ми не обираємо «найкращу зброю для битви», а беремо «прозорий тренажер», щоб зрозуміти механіку.

5. Критерій успіху: /health

Дуже людська пастка новачка — вважати, що раз застосунок «не впав» і «висить у терміналі», то він працює. У серверному режимі це не так. Сервер може запуститися, але слухати не той порт; може слухати не той інтерфейс; може відповідати не тим Content-Type; може впасти на першому ж запиті. І зовні все це виглядає однаково: «щось не відповідає».

Тому ми заздалегідь фіксуємо зрозумілий і вимірюваний результат. Наприкінці в нас має бути ендпоінт:

Шлях: /health

Метод: GET

Статус: 200 OK

Тіло: невеликий JSON, наприклад такого вигляду:

{
  "status": "UP",
  "appName": "readlater-starter"
}

Зверніть увагу: ця відповідь не про домен читання книжок, не про reading list і не про CRUD. Вона технічна. І саме тому вона ідеальна як стартова точка: ми тренуємо механіку, не відволікаючись на предметну область.

І ще одна важлива думка: серверний режим має спиратися на те, що в нас уже є. Host/port беремо з конфігурації, старт сервера логуємо через SLF4J, режим запуску явно відображаємо в логах. Тобто ми не починаємо новий застосунок, а продовжуємо розвивати той самий ReadLater Starter, тільки в новій ролі.

Сама механіка старту тут поки не головна. Спочатку достатньо зафіксувати зовнішній критерій готовності: GET /health має повертати очікувану HTTP-відповідь. Якщо сервер не відповідає, насамперед перевіряють адресу й порт, а не здогадуються за кодом.

6. Типові помилки під час роботи із server-mode

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

Помилка №1: робити server-mode окремим проєктом або окремим main()-класом «щоб не заважало».
Це здається зручним рівно один вечір. Потім ви починаєте дублювати конфігурацію, логування, JSON-мапінг, і раптом у вас два напівпроєкти замість одного. За змістом курсу ReadLater Starter має залишатися єдиним артефактом, інакше ви втрачаєте головну навичку: керувати зростанням застосунку без розпаду на хаос.

Помилка №2: ховати запуск сервера у випадковий if із магічним рядком.
Конструкція на кшталт if ("server".equals(args[0])) { ... } без нормального режиму запуску швидко перетворює точку входу на набір випадковостей. За тиждень ви самі забудете, як запускати застосунок правильно, а логіка старту виглядатиме як колекція «тимчасових рішень», які чомусь пережили автора.

Помилка №3: намагатися додати одразу багато шляхів і «справжній API», забувши про мінімальний критерій готовності.
Дуже легко захопитися і почати малювати GET /api/v1/reading-list, POST, валідацію тощо. Але без налагодженого мінімального каркаса сервера ви отримаєте ситуацію «нічого не працює, і незрозуміло чому». /health потрібен саме для того, щоб насамперед довести: сервер узагалі відповідає, і контракт відповіді контролюється.

Помилка №4: вважати результатом дня «сервер стартував» і не перевіряти HTTP-відповідь зовнішнім клієнтом.
Лог «Server started» — це заява сервера про власний успіх. Йому, звісно, приємно, але нас цікавить реальність: що бачить Postman. Поки не зроблено реальний HTTP-запит і не отримано 200 з коректним JSON та Content-Type, вважати задачу виконаною зарано.

Помилка №5: повертатися до System.out.println() саме в той момент, коли починається серверна фаза.
Парадоксально, але часто так і стається: у клієнтській фазі логування вже налаштували, а в серверній — «ну я тут швиденько виведу». Потім у логах не видно режиму, адреси, запитів, статусів, і діагностика перетворюється на ворожіння на кавовій гущі. Якщо вже ми вчимося бекенд-підходу, то і старт сервера, і будь-які важливі події мають логуватися як слід.

1
Задача
Java Server, 22 рівень, 0 лекція
Недоступна
Явний режим запуску `server`
Явний режим запуску `server`
1
Задача
Java Server, 22 рівень, 0 лекція
Недоступна
Окремий launcher для `server`-режиму
Окремий launcher для `server`-режиму
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ