Skip to content

Latest commit

 

History

History
230 lines (159 loc) · 20.4 KB

File metadata and controls

230 lines (159 loc) · 20.4 KB

Async HTTP server — solution

Задание

📌 Полное условие задачи без потери содержания 📖 Подробное описание того, что должен делать сервер 🧠 Что это означает на практике ✅ Итог
Нужно реализовать асинхронный HTTP-сервер на порту 8080. Сервер должен: - отвечать на GET и HEAD; - на GET /{target} возвращать: - статус 200 OK - Content-Type: text/html - тело Hello, {target_without_leading_slash} - на HEAD возвращать тот же ответ, но без тела; - на остальные методы возвращать: - 405 Method Not Allowed - Content-Type: text/html - Allow: GET, HEAD - тело Invalid method. Также сервер должен: - корректно обрабатывать SIGINT и SIGTERM; - печатать Server has started... перед запуском RunWorkers(...). Это значит, что сервер должен быть одновременно HTTP-корректным, асинхронным и корректно завершаемым по сигналам Реализуется полноценный async HTTP server под требования задания

Почему это решение соответствует заданию

GET

🧪 Проверка GET-запроса и соответствие условию 📖 Полное объяснение того, какой ответ формирует сервер ⚙️ Как это делается в коде ✅ Итог
На запрос GET /Ivan сервер вернёт: статус 200 OK, Content-Type: text/html, тело: Hello, Ivan Из target удаляется ведущий /: if (!target.empty() && target.front() == '/') { target.erase(target.begin()); } потом строится: std::string body = "Hello, "s + target; GET соответствует условию

HEAD

🧪 Проверка HEAD-запроса и соответствие условию 📖 Полное объяснение того, как сервер формирует ответ на HEAD ⚙️ Как это сделано в коде ✅ Итог
На HEAD /Ivan возвращается тот же ответ, что и для GET, но: тело пустое; Content-Length остаётся равным длине тела соответствующего GET-ответа Это сделано здесь: if (req.method() == http::verb::head) { response.body().clear(); response.content_length(body.size()); } То есть: фактическое тело очищается; но длина выставляется по исходной строке Hello, Ivan. Это ровно соответствует HTTP-смыслу HEAD HEAD соответствует условию

Другие методы

🧪 Проверка POST, PUT, DELETE и других неподдерживаемых методов 📖 Полное описание ожидаемого ответа ⚙️ Как это сделано в коде ✅ Итог
Например POST, PUT, DELETE Сервер возвращает: статус 405 Method Not Allowed, Content-Type: text/html, Allow: GET, HEAD, тело: Invalid method. Это сделано в: const auto make_invalid_method_response = [&req]() { auto response = MakeStringResponse(http::status::method_not_allowed, "Invalid method."sv, req.version(), req.keep_alive(), ContentType::TEXT_HTML); response.set(http::field::allow, "GET, HEAD"); return response; }; Неподдерживаемые методы обрабатываются правильно

Сигналы

🧪 Корректное завершение по сигналам 📖 Полное описание поведения ⚙️ Как это сделано в коде ✅ Итог
Сервер обрабатывает: SIGINT, SIGTERM Завершение идёт корректно, а не грубо через: net::signal_set signals(ioc, SIGINT, SIGTERM); и корректно завершает работу через: ioc.stop(); Graceful shutdown реализован

Фраза для тестов

🧪 Требование автотестов на стартовую фразу 📖 Полное описание ⚙️ Как это выполнено ✅ Итог
Перед RunWorkers(...) выводится: Server has started... Это прямо требуется условием Выполнено через: std::cout << "Server has started..."sv << std::endl; Требование тестов выполнено

Структура solution

solution/
├── CMakeLists.txt
├── conanfile.txt
├── README.md
└── src/
    ├── http_server.cpp
    ├── http_server.h
    ├── main.cpp
    └── sdk.h

Архитектура

🏗 Компонент архитектуры сервера 📖 Полное описание его роли без потери содержания 🧠 Почему он нужен ✅ Итог
Listener Принимает входящие TCP-соединения асинхронно. Это точка входа для новых клиентов Приём соединений отделён от HTTP-логики
SessionBase Реализует общий цикл HTTP-сессии: Run → Read → OnRead → HandleRequest → Write → OnWrite → Read Это общая инфраструктура любой сессии, не зависящая от типа обработчика Базовая логика вынесена отдельно
Session<RequestHandler> Хранит request_handler_ и вызывает его в HandleRequest Это позволяет делегировать обработку конкретного HTTP-запроса Прикладная логика отделена от сетевого ядра
ServeHttp(...) Запускает сервер, скрывая сложность Listener и Session Пользователь может просто передать handler Есть удобный фасад для запуска сервера

Как собрать

cd ~/cppbackend/sprint1/problems/async_server/solution
mkdir -p build
cd build
conan install .. --build=missing
cmake ..
cmake --build .

Как запустить

cd ~/cppbackend/sprint1/problems/async_server/solution/build
./bin/hello_async

После запуска сервер должен вывести:

Server has started...

Как проверить вручную через curl

GET /Ivan

curl -i http://127.0.0.1:8080/Ivan

Ожидаемо:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 11

Hello, Ivan

HEAD /Ivan

curl -I http://127.0.0.1:8080/Ivan

Ожидаемо:

200 OK
Content-Type: text/html
Content-Length: 11

тела нет

GET /

curl -i http://127.0.0.1:8080/

Ожидаемо:

Hello, 

Потому что target после удаления / становится пустым.

POST /Ivan

curl -i -X POST http://127.0.0.1:8080/Ivan

Ожидаемо:

HTTP/1.1 405 Method Not Allowed
Content-Type: text/html
Allow: GET, HEAD
Content-Length: 14

Invalid method.

Важное замечание про \n и curl

⚠️ Что нужно обязательно указать 📖 Полное объяснение без потери содержания 🧠 Как правильно поступать ✅ Итог
👉 то есть без \n Не нужно добавлять \n в серверный ответ только ради красивого вида в терминале, если это ломает ожидаемую длину тела или меняет сам ответ Вариант 3 (хитрый): не менять сервер, а сделать в curl: curl -i http://127.0.0.1:8080/Ivan && echo или: curl http://127.0.0.1:8080/Ivan; echo 👉 это добавит перенос строки снаружи, не ломая сервер Красивый вывод делается на стороне curl, а не сервера

Что важно по архитектуре

🏗 Элемент архитектуры 📖 Полное описание без потери содержания 🧠 Почему это важно ✅ Итог
Listener Асинхронно принимает новые соединения через async_accept. Это даёт неблокирующий accept-loop Сервер принимает клиентов асинхронно
SessionBase Хранит: beast::tcp_stream, beast::flat_buffer, текущий HTTP-запрос. И реализует общий цикл: Run → Read → OnRead → HandleRequest → Write → OnWrite → Read. Это общая база для HTTP-сессии Повторяющаяся логика вынесена в одно место
Session<RequestHandler> Хранит request_handler_ и вызывает его в HandleRequest. Делает обработчик запроса настраиваемым Гибкая архитектура
ServeHttp(...) Запускает сервер, скрывая сложность Listener и Session. Пользователь работает через простой интерфейс Серверное ядро спрятано за фасадом

Сборка (кратко)

cd ~/cppbackend/sprint1/problems/async_server/solution
mkdir -p build
cd build
conan install .. --build=missing
cmake ..
cmake --build .

Запуск (кратко)

cd ~/cppbackend/sprint1/problems/async_server/solution/build
./bin/hello_async

Проверка через curl (кратко)

🧪 Что проверить 💻 Команда 📖 Ожидаемый результат ✅ Итог
GET curl -i http://127.0.0.1:8080/Ivan 200 OK, Content-Type: text/html, Content-Length: 11, тело Hello, Ivan GET работает
HEAD curl -I http://127.0.0.1:8080/Ivan 200 OK, Content-Type: text/html, Content-Length: 11, без тела HEAD работает
Invalid method curl -i -X POST http://127.0.0.1:8080/Ivan 405 Method Not Allowed, Allow: GET, HEAD, тело Invalid method. Ошибка метода работает

Корректное завершение

🧩 Graceful shutdown 📖 Полное описание без потери содержания 🧠 Что происходит ✅ Итог
Сервер подписывается на: SIGINT, SIGTERM При получении сигнала вызывается ioc.stop(), после чего сервер корректно завершает цикл обработки событий Это даёт серверу завершиться управляемо, а не грубо Shutdown реализован правильно

Итог

🏁 Финальный вывод по solution 📖 Полное описание без потери содержания 🧠 Что это означает ✅ Итог
Реализован асинхронный HTTP-сервер на Boost.Asio + Boost.Beast, соответствующий требованиям задания. Он правильно отвечает на GET и HEAD, корректно обрабатывает неподдерживаемые методы, стартует с требуемой фразой, умеет завершаться по сигналам и построен на архитектуре Listener / SessionBase / Session<RequestHandler> / ServeHttp(...) Это полноценное решение под условие задачи, а не просто минимальный код “чтобы ответить на curl” Solution завершён корректно