diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c34a5ee..22763b5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -104,40 +104,48 @@ jobs: cp -R /home/forconan/.conan /github/home/.conan - name: run tests sprint2 static_content + #if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint2 ./static_content/run.sh - name: run tests sprint2 logger + #if: ${{ false }} run: | ./cpp-backend-tests-practicum/scripts/sprint2/logger/run.sh - name: run tests sprint2 server_logging + #if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint2 ./server_logging/run.sh - name: run tests sprint2 join_game + #if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint2 ./join_game/run.sh - name: run tests sprint2 game_state + #if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint2 ./game_state/run.sh - name: run tests sprint2 move_players + #if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint2 ./move_players/run.sh - name: run tests sprint2 time_control + #if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint2 ./time_control/run.sh - name: run tests sprint2 command_line + #if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint2 ./command_line/run.sh @@ -159,92 +167,100 @@ jobs: pip show pytest - name: run tests sprint3 instrumentation + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./instrumentation/run.sh -# - name: Load map_json binary -# uses: actions/download-artifact@v3 -# with: -# name: game_server -# path: ${{ github.workspace }}/sprint1/problems/map_json/solution/build/bin - -# - run: chmod +x ${GITHUB_WORKSPACE}/sprint1/problems/map_json/solution/build/bin/game_server - - name: Load FlameGraph + if: ${{ false }} uses: actions/checkout@v3 with: repository: 'brendangregg/FlameGraph' path: ${{ github.workspace }}/sprint3/problems/flamegraph/solution/FlameGraph - name: run tests sprint3 flamegraph + if: ${{ false }} run: | ./cpp-backend-tests-practicum/scripts/sprint1/map_json/build.sh cd cpp-backend-tests-practicum/scripts/sprint3 ./flamegraph/run.sh - name: run tests sprint3 gen_objects + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./gen_objects/run.sh - name: run tests sprint3 static_lib + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 pip show pytest ./static_lib/run.sh - name: run tests sprint3 gather-tests + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./gather-tests/run.sh - name: run tests sprint3 gather-tests_wrong1 + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./gather-tests_wrong1/run.sh - name: run tests sprint3 gather-tests_wrong2 + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./gather-tests_wrong2/run.sh - name: run tests sprint3 gather-tests_wrong3 + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./gather-tests_wrong3/run.sh - name: run tests sprint3 gather-tests_wrong4 + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./gather-tests_wrong4/run.sh - name: run tests sprint3 gather-tests_wrong5 + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./gather-tests_wrong5/run.sh - name: run tests sprint3 gather + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./gather/run.sh - name: run tests sprint3 find_return + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./find_return/run.sh - name: run tests sprint3 scores + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./scores/run.sh - name: run tests sprint3 ammo + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./ammo/run.sh - name: run tests sprint3 load + if: ${{ false }} run: | cd cpp-backend-tests-practicum/scripts/sprint3 ./load/run.sh @@ -311,14 +327,6 @@ jobs: cd cpp-backend-tests-practicum/scripts/sprint3 ./load/run_ci.sh -## - name: run tests sprint3 stress -## run: | -## cd sprint3/problems/stress/solution -## yandex-tank -c ${GITHUB_WORKSPACE}/sprint3/problems/stress/solution/load.yaml ${GITHUB_WORKSPACE}/sprint3/problems/stress/solution/ammo.txt -## pytest --junitxml=${GITHUB_WORKSPACE}/stress.xml ${GITHUB_WORKSPACE}/cpp-backend-tests-practicum/tests/test_s03_stress.py -## env: -## DIRECTORY: ${{ github.workspace }}/sprint3/problems/stress/solution/logs -# - name: Publish XML reports uses: EnricoMi/publish-unit-test-result-action@v2 if: always() diff --git a/README.md b/README.md index 98bb81a..9b7c16c 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,32 @@ cppbackend/ --- +## 🧪 Sprint 2 — Статические файлы и веб-клиент + +| 🧪 Папка урока второго спринта, связанного с отдачей статических файлов и клиентской частью | 📖 Что это за урок и какую задачу он решает в рамках развития HTTP-сервера | 🧠 Почему этот урок относится к следующему этапу после базового HTTP-сервера | ✅ Переход | +| -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------- | +| `lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files` | Урок по отдаче статических файлов: HTML, CSS, JavaScript, MIME-типы и защита путей | Переводит сервер из API-режима в полноценный веб-сервер с клиентской частью | [README](lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2` | Расширение урока: поддержка изображений, аудио и видео, расширенные MIME-типы | Добавляет полноценную работу с media-ресурсами (PNG, WAV, MP4) и делает сервер ближе к production | [README](lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part3` | Расширение урока: разделение REST API и статических файлов, URI, URL-encoding и percent-encoding | Добавляет маршрутизацию между API и static, JSON-ответы, декодирование URL-encoded путей и работу с файлами с пробелами | [README](lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part3/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part4` | Расширение урока: безопасный доступ к файлам, path traversal, URL Decode и canonical-проверка пути | Показывает правильную защиту static-каталога через `weakly_canonical` и проверку, что путь остаётся внутри `static_root` | [README](lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part4/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part5` | Расширение урока: отдача файлов через `boost::beast::http::file_body` вместо чтения в `std::string` | Делает отдачу больших static-файлов правильнее и ближе к реальному HTTP-серверу, особенно для видео, аудио и крупных изображений | [README](lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part5/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part1` | Logging Patterns Part 1: запись лога в `/var/log/sample.log` | Переход к системному логированию в Linux | [README](lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part1/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part2` | Logging Patterns Part 2: Singleton логгер | Глобальный доступ к логированию | [README](lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part2/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part3` | Logging Patterns Part 3: удобное логирование через variadic template и fold expression без ручного `std::to_string` | Показывает, как сделать интерфейс логгера удобнее: передавать строки, числа и другие значения напрямую в `Logger::GetInstance().Log(...)` | [README](lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part3/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part4` | Logging Patterns Part 4: потокобезопасный логгер через `std::mutex` и `std::lock_guard` | Показывает проблему многопоточности: несколько потоков пишут в один лог-файл, поэтому запись нужно защищать мьютексом | [README](lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part4/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part5` | Logging Patterns Part 5: правильное открытие лог-файла через `std::ios::app`, чтобы старые записи не удалялись при новом запуске | Завершает блок логирования: показывает append-режим, сохранение истории логов и подводит к теме ротации логов по размеру и дате | [README](lessons/sprint_18_20_theme_1_4_lesson_5_10_logging_patterns_part5/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_6_10_conan` | Conan: правильная линковка C++ зависимостей через `target_link_libraries`, Boost.Chrono, Conan cache, Release-сборка без пересборки Boost | Показывает разницу между `include` и линковкой, объясняет почему Release и Debug — разные Conan-пакеты, и фиксирует честный рабочий сценарий без `--build=missing` | [README](lessons/sprint_18_20_theme_1_4_lesson_6_10_conan/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_7_10_boost_log` | Boost.Log: логирование, уровни сообщений severity, фильтры, запись в файл, форматирование, пользовательские атрибуты, многопоточное логирование и бенчмарк | Продолжает тему Conan и C++ backend-инфраструктуры: показывает практическое логирование через Boost.Log, сравнение с custom logger и проверку созданных log-файлов | [README](lessons/sprint_18_20_theme_1_4_lesson_7_10_boost_log/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_8_10_cron` | Cron: планирование периодических задач в Linux, личный crontab, системные cron-файлы, cron-логи и запуск shell-скриптов по расписанию | Показывает, как автоматизировать backend-задачи: проверки, логирование, ежедневные отчёты, очистку временных файлов и ротацию логов с помощью cron | [README](lessons/sprint_18_20_theme_1_4_lesson_8_10_cron/README.md) | +| `lessons/sprint_18_20_theme_1_4_lesson_9_10_log_rotation` | Log Rotation: ротация логов bash-скриптом и cron, gzip-сжатие старых логов, удаление десятидневных архивов и подготовка к `logrotate` | Показывает, как контролировать рост лог-файлов на сервере: создавать тестовые логи, архивировать позавчерашний лог, удалять старые архивы и запускать ротацию автоматически каждый день в 00:15 | [README](lessons/sprint_18_20_theme_1_4_lesson_9_10_log_rotation/README.md) | +| `lessons/sprint_18_20_theme_2_4_lesson_2_5_prometheus_metrics` | Prometheus Metrics: установка Node Exporter, получение системных метрик Linux-сервера, открытие портов 9100 и 9090, запуск Prometheus через Docker и настройка `prometheus.yml` | Показывает, как подключить Node Exporter как источник метрик, проверить `/metrics`, открыть Prometheus UI, выполнить запрос `node_cpu_seconds_total` и убедиться, что target находится в состоянии UP | [README](lessons/sprint_18_20_theme_2_4_lesson_2_5_prometheus_metrics/README.md) | +| `lessons/sprint_18_20_theme_2_4_lesson_3_5_promql_grafana` | PromQL and Grafana: написание PromQL-запросов, вычисление CPU Usage, Disk Usage и RAM Usage, запуск Grafana через Docker и подключение Prometheus как Data Source | Показывает, как строить dashboard мониторинга: подключить Prometheus к Grafana, создать панели CPU, Disk и RAM, использовать `rate`, фильтрацию по labels и формулы для отображения серверных метрик | [README](lessons/sprint_18_20_theme_2_4_lesson_3_5_promql_grafana/README.md) | +| `lessons/sprint_18_20_theme_2_4_lesson_4_5_golden_metrics` | Four Golden Metrics: экспорт метрик приложения через Python exporter, создание Counter и Histogram-метрик, подключение exporter к Prometheus и визуализация Latency, Traffic, Errors и Saturation в Grafana | Показывает, как превратить JSON-логи приложения в Prometheus-метрики, отдавать их на `/metrics` через порт 9200, добавить target `server`, построить панели Rate, Latency, Errors, CPU Usage, Disk Usage и RAM Usage | [README](lessons/sprint_18_20_theme_2_4_lesson_4_5_golden_metrics/README.md) | +| `lessons/sprint_18_20_theme_3_4_lesson_2_10_game_server_architecture` | Game Server Architecture: архитектура игрового сервера, модель игрового мира, игроки, собаки, игровые сеансы, изменяемое состояние, защита модели в многопоточном HTTP-сервере через `boost::asio::strand`, разделение на `model`, `app`, `request_handler` и `http_server` | Показывает, как проектировать игровой backend: сервер хранит полную модель мира и является источником правды, клиент отвечает только за отображение и ввод, `GameSession` отделяет динамическое состояние от `Map`, `Player` связывает token, session и `Dog id`, а зависимости идут в направлении `http_handler → app → model` без циклов | [README](lessons/sprint_18_20_theme_3_4_lesson_2_10_game_server_architecture/README.md) | +| `lessons/sprint_18_20_theme_3_4_lesson_7_10_program_options` | Boost.ProgramOptions: разбор параметров командной строки, поддержка коротких и длинных аргументов, обработка help, проверка обязательных параметров и работа с множественными значениями | Добавляет полноценный CLI-интерфейс для сервера: позволяет задавать режимы запуска через аргументы и безопасно обрабатывать пользовательский ввод | [README](lessons/sprint_18_20_theme_3_4_lesson_7_10_program_options/README.md) | + +--- + # 🚀 SPRINT 1 ## 📦 Что находится в sprint1 @@ -224,6 +250,49 @@ cppbackend/ --- +# 🚀 SPRINT 2 + +## 📦 Что находится в sprint2 + +| 📦 Основные группы задач внутри `sprint2/problems`, которые относятся ко второму спринту C++ backend и будут постепенно оформляться отдельными README внутри solution-папок | 📖 Какие задачи уже есть в структуре второго спринта и какую роль они будут играть в развитии игрового сервера | 🧠 Почему этот блок нужен в корневом README уже сейчас, даже если пока подробно оформлена только задача `static_content` | ✅ Итог | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------ | +| `command_line` | Уже реализованная задача второго спринта по поддержке параметров командной строки игрового сервера: `--help`, `--tick-period`, `--config-file`, `--www-root`, `--randomize-spawn-points`, тестовый режим, автоматический tick и запрет ручного `/api/v1/game/tick` в автоматическом режиме | Для этой задачи уже есть подробный README в solution, поэтому из корневого README можно сразу перейти к полному разбору CLI-параметров и автоматического обновления игрового времени | [Открыть README](sprint2/problems/command_line/solution/README.md) | +| `game_state` | Уже реализованная задача второго спринта, связанная с получением игрового состояния через REST API: `GET /api/v1/game/state`, `HEAD /api/v1/game/state`, расширение `model::Dog` полями `pos`, `speed`, `dir`, возврат состояния игроков одной игровой сессии, проверка `Authorization: Bearer ` и обработка ошибок `invalidToken`, `unknownToken`, `invalidMethod` | Для этой задачи уже есть подробный README в solution, поэтому из корневого README можно сразу перейти к полному разбору state endpoint, структуры JSON-ответа `players`, состояния собаки, обязательных API-заголовков и связи с предыдущей задачей `join_game` | [Открыть README](sprint2/problems/game_state/solution/README.md) | +| `join_game` | Уже реализованная задача второго спринта, связанная с подключением игрока к игре через REST API: `POST /api/v1/game/join`, `GET /api/v1/game/players`, `HEAD /api/v1/game/players`, создание `model::Dog`, `model::GameSession`, `app::Player`, `app::Players`, `app::PlayerTokens`, `app::JoinGameUseCase`, выдача `authToken` и получение списка игроков игровой сессии | Для этой задачи уже есть подробный README в solution, поэтому из корневого README можно сразу перейти к полному разбору входа игрока в игру, Bearer-авторизации, обработки ошибок `invalidArgument`, `mapNotFound`, `invalidToken`, `unknownToken`, `invalidMethod` и обязательных API-заголовков | [Открыть README](sprint2/problems/join_game/solution/README.md) | +| `logger` | Уже реализованная задача второго спринта по потокобезопасному логгеру с variadic template, fold expression, Singleton-доступом, макросом `LOG`, `SetTimestamp`, лог-файлами по датам, append-режимом и защитой через `std::mutex` | Для этой задачи уже есть подробный README в `solution`, поэтому из корневого README можно сразу перейти к полному разбору реализации логгера | [Открыть README](sprint2/problems/logger/solution/README.md) | +| `move_players` | Уже реализованная задача второго спринта, связанная с управлением движением игрока через REST API: `POST /api/v1/game/player/action`, команды `L`, `R`, `U`, `D`, `""`, изменение `speed` и `dir` у собаки, чтение `dogSpeed` из карты, `defaultDogSpeed` из корневого JSON и fallback-скорость `1.0` | Для этой задачи уже есть подробный README в solution, поэтому из корневого README можно сразу перейти к полному разбору action endpoint, команд движения, расчёта скорости, Bearer-авторизации, ошибок `invalidArgument`, `invalidToken`, `unknownToken`, `invalidMethod` и связи с предыдущим состоянием игры | [Открыть README](sprint2/problems/move_players/solution/README.md) | +| `static_content` | Уже реализованная задача по отдаче статических файлов, разделению REST API и static content, MIME-типам, HEAD, Content-Length, URL-encoding и защите от path traversal | Для этой задачи уже есть подробный README в `solution`, поэтому из корневого README можно сразу перейти к полному разбору | [Открыть README](sprint2/problems/static_content/solution/README.md) | +| `time_control` | Уже реализованная задача второго спринта, связанная с ручным управлением игровым временем через REST API: `POST /api/v1/game/tick`, обработка `timeDelta`, пересчёт позиции собак по формулам `x = x0 + vx * Δt` и `y = y0 + vy * Δt`, ограничение движения дорогами, остановка на границе и стабильная начальная позиция на первой дороге карты | Для этой задачи уже есть подробный README в solution, поэтому из корневого README можно сразу перейти к полному разбору tick endpoint, формулы движения, проверки дорог, обработки ошибок `invalidArgument`, `invalidMethod` и связи с предыдущей задачей `move_players` | [Открыть README](sprint2/problems/time_control/solution/README.md) | +| `server_logging` | Уже реализованная задача второго спринта по JSON-логированию игрового HTTP-сервера через Boost.Log: запуск сервера, остановка сервера, получение запроса, отправка ответа, ошибки, IP клиента, response_time, HTTP code и content_type | Для этой задачи уже есть подробный README в solution, поэтому из корневого README можно сразу перейти к полному разбору JSON-логирования сервера | [Открыть README](sprint2/problems/server_logging/solution/README.md) |server_logging | Уже реализованная задача второго спринта по JSON-логированию игрового HTTP-сервера через Boost.Log: запуск сервера, остановка сервера, получение запроса, отправка ответа, ошибки, IP клиента, response_time, HTTP code и content_type | Для этой задачи уже есть подробный README в solution, поэтому из корневого README можно сразу перейти к полному разбору JSON-логирования сервера | [Открыть README](sprint2/problems/server_logging/solution/README.md) | + +--- + +## 🗂 Полная навигация по задачам `sprint2/problems` + +| 📌 Каталог задачи внутри `sprint2/problems`, который отражает отдельный этап развития игрового backend-сервера во втором спринте | 📖 Что сейчас известно по структуре и как этот раздел будет использоваться для навигации по будущим solution README | 🔗 Прямой переход в README готового решения, если он уже оформлен и доступен для чтения | ✅ Итог | +| -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ------ | +| `sprint2/problems/command_line` | Каталог уже содержит реализованное решение поддержки параметров командной строки игрового сервера через Boost.ProgramOptions, автоматического Tick через Boost.Asio timer, режима `--randomize-spawn-points` и проверки `/api/v1/game/tick` | [README solution](sprint2/problems/command_line/solution/README.md) | Навигация готова | +| `sprint2/problems/game_state` | Каталог уже содержит реализованное решение получения игрового состояния: сервер после входа игрока через `join_game` возвращает через `/api/v1/game/state` объект `players` с состоянием собак `pos`, `speed`, `dir`, проверяет Bearer token, поддерживает HEAD без тела ответа и использует обновлённые `data` и `static` из precode задачи `game_state` | [README solution](sprint2/problems/game_state/solution/README.md) | Навигация готова | +| `sprint2/problems/join_game` | Каталог уже содержит реализованное решение входа игрока в игру: сервер создаёт собаку, создаёт игрока, связывает `Player → Dog`, выдаёт auth token, проверяет `Authorization: Bearer `, возвращает список игроков через `/api/v1/game/players` и поддерживает метод HEAD без тела ответа | [README solution](sprint2/problems/join_game/solution/README.md) | Навигация готова | +| `sprint2/problems/logger` | Каталог уже содержит реализованное решение потокобезопасного логгера с variadic template, `LOG`, `SetTimestamp`, датированными лог-файлами и синхронизацией через mutex | [README solution](sprint2/problems/logger/solution/README.md) | Навигация готова | +| `sprint2/problems/move_players` | Каталог уже содержит реализованное решение управления движением игрока: сервер после входа через `join_game` принимает команду `/api/v1/game/player/action`, проверяет Bearer token, меняет скорость и направление собаки, поддерживает остановку через пустой `move`, а затем отдаёт обновлённое состояние через `/api/v1/game/state` | [README solution](sprint2/problems/move_players/solution/README.md) | Навигация готова | +| `sprint2/problems/static_content` | Каталог уже содержит реализованное решение и подробный README внутри `solution` | [README solution](sprint2/problems/static_content/solution/README.md) | Навигация готова | +| `sprint2/problems/time_control` | Каталог уже содержит реализованное решение управления игровым временем: сервер после команды движения через `/api/v1/game/player/action` принимает `/api/v1/game/tick`, продвигает модель на `timeDelta` миллисекунд, перемещает собак согласно скорости, ограничивает движение дорогами и отдаёт обновлённое состояние через `/api/v1/game/state` | [README solution](sprint2/problems/time_control/solution/README.md) | Навигация готова | +| `sprint2/problems/server_logging` | Каталог уже содержит реализованное решение JSON-логирования игрового HTTP-сервера через Boost.Log с выводом в stdout, логированием request received, response sent, server started, server exited и error | [README solution](sprint2/problems/server_logging/solution/README.md) | Навигация готова | + +--- + +## 🧭 Как пользоваться разделом Sprint 2 + +| 📌 Шаг навигации по `sprint2`, который нужен, чтобы использовать корневой README как карту второго спринта | 📖 Что именно делать на этом шаге, чтобы быстро перейти к уже готовому материалу или увидеть будущую структуру спринта | 🧠 Почему такой подход удобен, когда задач всего 7, но подробно оформлена пока только одна | ✅ Итог | +| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------ | +| Шаг 1 | Открыть раздел `SPRINT 2` в корневом README | Здесь сразу видны все 7 задач второго спринта | Общая карта есть | +| Шаг 2 | Перейти по ссылке у `static_content` | Эта задача уже реализована и имеет подробный README | Готовый материал доступен | +| Шаг 3 | Остальные задачи пока оставить как зарезервированные строки без ссылок | Это честно показывает структуру спринта и не создаёт битые ссылки | Навигация не ломается | +| Шаг 4 | Когда появится README у следующей задачи, заменить текст `README будет добавлен позже` на ссылку | Так корневой README будет расширяться без переписывания всей структуры | Обновление будет простым | + +--- + # 💡 Идея корневого README | 💡 Основная идея организации корневого README как центральной карты всего уже сделанного материала | 📖 Почему такой подход лучше, чем пытаться дублировать большие подробные объяснения прямо в корневом файле | 🧠 Что это даёт для чтения, навигации и поддержки репозитория в будущем | ✅ Итог | diff --git a/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/CMakeLists.txt b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/CMakeLists.txt new file mode 100644 index 0000000..72e0260 --- /dev/null +++ b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.11) + +project(web_server_lesson_18_static_files CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(static_server + src/main.cpp +) + +target_include_directories(static_server PRIVATE + /home/ubuntu/.conan/data/boost/1.86.0/_/_/package/4c73f888ee1301ffed212b5fd37391dd45ff1c09/include +) + +target_link_libraries(static_server + pthread +) diff --git a/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/README.md b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/README.md new file mode 100644 index 0000000..58dd6c2 --- /dev/null +++ b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/README.md @@ -0,0 +1,856 @@ +# 🌐 Урок: Отдача статических файлов + +--- + +## 🎯 Цель урока + +| 📌 Что именно изучается в этом практическом уроке по отдаче статических файлов через HTTP-сервер на C++ | 📖 Подробное объяснение цели урока и того, что сервер научился отдавать клиенту через браузер | 🧠 Почему это важно для полноценной браузерной игры и чем это отличается от REST API-сервера | ✅ Итог | +| ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | +| Отдача статических файлов | В этом практическом уроке мы научили HTTP-сервер отдавать статические файлы: HTML-страницы, CSS-стили, JavaScript-сценарии, изображения и другие файлы | До этого сервер мог работать как REST API-сервер, то есть отдавать данные, например JSON с игровыми картами. Но для полноценной игры в браузере нужен ещё игровой клиент | Сервер теперь может отдавать не только API-данные, но и файлы клиентской части | +| Игровой клиент | Игровой клиент обычно состоит из `index.html`, `style.css`, `app.js`, `images/*` | Браузер загружает HTML, затем отдельно CSS и JavaScript, а уже они формируют внешний вид и поведение страницы | Клиентская часть может жить рядом с C++ сервером | +| Проверка результата | После этой практики браузер может открыть страницу `http://localhost:8080/` и получить HTML, CSS и JavaScript с нашего C++ сервера | Это главный практический результат урока | `http://localhost:8080/` открывает страницу с C++ сервера | + +--- + +## 📂 Что такое статические файлы + +| 📌 Понятие статических файлов и примеры ресурсов, которые сервер отдаёт клиенту без генерации нового содержимого | 📖 Подробное объяснение и список примеров файлов, которые относятся к статическим ресурсам | 🧠 Как браузер запрашивает такие файлы и где сервер должен искать их на диске | ✅ Итог | +| ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| Статические файлы | Статические файлы — это файлы, которые сервер отдаёт клиенту без генерации нового содержимого | Примеры: `index.html`, `style.css`, `app.js`, `logo.png`, `favicon.ico` | Сервер просто находит файл, читает его и отдаёт в HTTP-ответе | +| Пример запроса | Если браузер запрашивает `GET /index.html`, сервер должен найти файл `static/index.html` | После этого сервер должен прочитать файл и отправить HTTP-ответ с корректным `Content-Type` | Запрос `/index.html` соответствует файлу `static/index.html` | +| Пример ответа | Для HTML сервер должен отправить ответ вида `HTTP/1.1 200 OK` и `Content-Type: text/html` | Так браузер понимает, что тело ответа нужно интерпретировать как HTML | `Content-Type` обязателен для правильной обработки файла | + +```text +index.html +style.css +app.js +logo.png +favicon.ico +``` + +```text +GET /index.html +``` + +```text +static/index.html +``` + +```text +HTTP/1.1 200 OK +Content-Type: text/html +``` + +--- + +## 🌍 Что такое HTTP + +| 📌 Термин HTTP и его расшифровка | 📖 Подробное объяснение каждого слова из названия и роли HTTP в общении браузера и сервера | 🧠 Что показывает пример HTTP-запроса | ✅ Итог | +| -------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | +| HTTP | HTTP — HyperText Transfer Protocol | Расшифровка: Hyper — гипертекстовый, Text — текст, Transfer — передача, Protocol — протокол | HTTP — это протокол общения браузера и сервера | +| Пример запроса | Пример запроса: `GET /style.css HTTP/1.1` и `Host: localhost:8080` | Здесь `GET` означает: клиент просит получить ресурс; `/style.css` — это путь к ресурсу; `HTTP/1.1` — это версия протокола HTTP | Запрос говорит серверу, какой ресурс нужен клиенту | + +```http +GET /style.css HTTP/1.1 +Host: localhost:8080 +``` + +```text +GET +``` + +```text +/style.css +``` + +```text +HTTP/1.1 +``` + +--- + +## 🔌 Что такое REST API + +| 📌 Термин REST API и его расшифровка | 📖 Подробное объяснение REST, API и примера endpoint-а, который возвращает JSON-данные | 🧠 Почему одного REST API недостаточно для браузерной игры | ✅ Итог | +| ------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| REST API | REST API — Representational State Transfer Application Programming Interface | Расшифровка REST: Representational — представление ресурса, State — состояние, Transfer — передача. Расшифровка API: Application — приложение, Programming — программирование, Interface — интерфейс | REST API обычно отдаёт данные | +| Пример REST API | Например, `GET /api/v1/maps` может вернуть JSON-массив с картами | Это подходит для данных, но не заменяет HTML-страницу, CSS и JavaScript | Для браузерной игры нужен не только REST API, но и статические файлы | + +```text +GET /api/v1/maps +``` + +```json +[ + { + "id": "map1", + "name": "Desert" + } +] +``` + +--- + +## 🧱 Что такое HTML + +| 📌 Термин HTML и его расшифровка | 📖 Подробное объяснение роли HTML-файла в браузерной странице | 🧠 Какой файл используется в этом уроке | ✅ Итог | +| -------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------- | +| HTML | HTML — HyperText Markup Language | Расшифровка: Hyper — гипертекстовый, Text — текст, Markup — разметка, Language — язык | HTML описывает структуру веб-страницы | +| Файл урока | В этом уроке используется файл `static/index.html` | Именно этот файл отдаётся при запросе `/` или `/index.html` | HTML-файл является главной страницей | + +```text +static/index.html +``` + +--- + +## 🎨 Что такое CSS + +| 📌 Термин CSS и его расшифровка | 📖 Подробное объяснение того, за что отвечает CSS на странице | 🧠 Какой файл используется в этом уроке | ✅ Итог | +| ------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------- | ---------------------------------------------- | +| CSS | CSS — Cascading Style Sheets | Расшифровка: Cascading — каскадные, Style — стили, Sheets — таблицы | CSS отвечает за внешний вид страницы | +| Что задаёт CSS | CSS отвечает за цвет фона, цвет текста, отступы, рамки, расположение элементов | В этом уроке используется файл `static/style.css` | CSS-файл делает страницу визуально оформленной | + +```text +static/style.css +``` + +--- + +## ⚡ Что такое JavaScript + +| 📌 Термин JavaScript и сокращение JS | 📖 Подробное объяснение того, зачем JavaScript нужен браузерной странице | 🧠 Какой файл используется в этом уроке | ✅ Итог | +| ------------------------------------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ---------------------------------------------------- | +| JavaScript | JavaScript — язык программирования, который выполняется в браузере. JS — JavaScript | Расшифровка: Java — историческая часть названия, Script — сценарий | JavaScript нужен, чтобы страница могла выполнять код | +| Назначение JS | JavaScript нужен, чтобы страница могла рисовать игру, обращаться к API и обрабатывать действия игрока | В этом уроке используется файл `static/app.js` | JS-файл добавляет поведение странице | + +```text +static/app.js +``` + +--- + +## 🧾 Что такое MIME type + +| 📌 Термин MIME и его расшифровка | 📖 Подробное объяснение того, зачем MIME-типы нужны в HTTP и какой заголовок за это отвечает | 🧠 Как браузер использует `Content-Type` | ✅ Итог | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | +| MIME | MIME — Multipurpose Internet Mail Extensions | Расшифровка: Multipurpose — многоцелевые, Internet — интернет, Mail — почта, Extensions — расширения | MIME-типы говорят клиенту, как понимать тело ответа | +| История MIME | Изначально MIME-типы появились для электронной почты, чтобы письма могли содержать не только простой текст, но и вложения | В HTTP MIME-типы используются через заголовок `Content-Type` | Один и тот же механизм используется для файлов разных типов | +| `Content-Type` | Этот заголовок говорит браузеру, как обрабатывать тело ответа | Если сервер отдаёт HTML, он должен указать `Content-Type: text/html`; если CSS — `text/css`; если JavaScript — `application/javascript` | Без правильного типа браузер может обработать файл неверно | + +```http +Content-Type +``` + +| 📄 Расширение файла и тип ресурса, который отдаётся браузеру | 🌐 Корректный `Content-Type`, который должен быть выставлен сервером | 🧠 Как браузер будет интерпретировать тело ответа | ✅ Итог | +| ------------------------------------------------------------ | -------------------------------------------------------------------- | ------------------------------------------------- | --------------------------------- | +| `.html` | `text/html` | HTML-страница | Страница открывается как HTML | +| `.css` | `text/css` | CSS-стили | Стили применяются к странице | +| `.js` | `application/javascript` | JavaScript-сценарий | Скрипт выполняется браузером | +| `.json` | `application/json` | JSON-данные | Данные читаются как JSON | +| `.png` | `image/png` | PNG-изображение | Картинка отображается | +| `.jpg` | `image/jpeg` | JPEG-изображение | Картинка отображается | +| `.txt` | `text/plain` | Обычный текст | Текст показывается как plain text | + +```text +.html -> text/html +.css -> text/css +.js -> application/javascript +.json -> application/json +.png -> image/png +.jpg -> image/jpeg +.txt -> text/plain +``` + +```http +Content-Type: text/html +``` + +```http +Content-Type: text/css +``` + +```http +Content-Type: application/javascript +``` + +--- + +## 🎯 Практическая цель + +| 📌 Запрос браузера или curl к HTTP-серверу | 📖 Какой файл или ответ должен получить клиент | 🧠 Что проверяется этим сценарием | ✅ Итог | +| ------------------------------------------ | ---------------------------------------------- | ---------------------------------------------- | ------------------------- | +| `GET /` | `static/index.html` | Главная страница открывается по корневому пути | Работает главная страница | +| `GET /index.html` | `static/index.html` | HTML-файл можно открыть напрямую | HTML отдаётся | +| `GET /style.css` | `static/style.css` | CSS-файл отдаётся отдельно | Стили загружаются | +| `GET /app.js` | `static/app.js` | JavaScript-файл отдаётся отдельно | Скрипт загружается | +| `GET /unknown.png` | `404 Not Found` | Несуществующий файл не найден | Ошибка 404 работает | +| `GET /../../secret` | `403 Forbidden` | Попытка выхода из `static` запрещена | Защита пути работает | + +```text +GET / -> static/index.html +GET /index.html -> static/index.html +GET /style.css -> static/style.css +GET /app.js -> static/app.js +GET /unknown.png -> 404 Not Found +GET /../../secret -> 403 Forbidden +``` + +--- + +## 📂 Итоговая папка урока + +| 📌 Где находится практический урок и как устроена итоговая папка проекта | 📖 Полный путь и дерево проекта | 🧠 Что находится в каждой части проекта | ✅ Итог | +| ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------ | +| Папка урока | Практический урок находится здесь: `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files` | Внутри лежит CMake-проект, исходный код сервера и папка `static` с клиентскими файлами | Урок оформлен отдельной папкой | +| Структура проекта | `CMakeLists.txt`, `README.md`, `build/`, `src/main.cpp`, `static/index.html`, `static/style.css`, `static/app.js` | `src` содержит C++ сервер, `static` содержит файлы для браузера | Структура проекта понятна | + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files +``` + +```text +sprint_18_20_theme_1_4_lesson_3_10_static_files/ +├── CMakeLists.txt +├── README.md +├── build/ +├── src/ +│ └── main.cpp +└── static/ + ├── index.html + ├── style.css + └── app.js +``` + +--- + +## ⚠️ Важный момент про Boost + +| 📌 Что произошло при сборке и почему Boost сначала не был найден стандартным способом | 📖 Полное объяснение проблемы, проверки, найденного решения и причины отказа от установки `libboost-all-dev` | 🧠 Почему выбранный способ лучше в этой ситуации | ✅ Итог | +| ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------ | +| Ошибка Boost | Сначала возникла ошибка `Could NOT find Boost` | Причина: в WSL есть `cmake`, но нет `/usr/include/boost/asio.hpp` | Boost не был установлен в системные include-пути | +| Проверка системы | Проверка показала `cmake version 3.28.3` и ошибку `ls: cannot access '/usr/include/boost/asio.hpp': No such file or directory` | Это подтвердило, что CMake есть, а системных заголовков Boost нет | Причина ошибки найдена | +| Boost в Conan | При этом Boost уже был найден в Conan-кэше: `/home/ubuntu/.conan/data/boost/1.86.0/_/_/package/4c73f888ee1301ffed212b5fd37391dd45ff1c09/include/boost/asio.hpp` | Значит Boost уже был скачан ранее и его можно использовать без установки через apt | Используем готовый Boost из Conan | +| Что не стали делать | Поэтому мы не стали устанавливать `sudo apt install libboost-all-dev` | Так мы избежали лишней установки Boost на 500 MB или больше | 500 MB или больше не загружались | +| Что сделали вместо этого | Вместо этого мы переиспользовали уже существующий Boost из Conan | Conan — это пакетный менеджер для C++ | Решение экономит место и время | + +```text +Could NOT find Boost +``` + +```text +В WSL есть cmake, но нет /usr/include/boost/asio.hpp +``` + +```bash +cmake --version +ls /usr/include/boost/asio.hpp +``` + +```text +cmake version 3.28.3 +ls: cannot access '/usr/include/boost/asio.hpp': No such file or directory +``` + +```text +/home/ubuntu/.conan/data/boost/1.86.0/_/_/package/4c73f888ee1301ffed212b5fd37391dd45ff1c09/include/boost/asio.hpp +``` + +```bash +sudo apt install libboost-all-dev +``` + +--- + +## ⚙️ CMakeLists.txt + +| 📌 Файл сборки проекта и его полный путь | 📖 Назначение файла `CMakeLists.txt` в этом уроке | 🧠 Что он делает при сборке | ✅ Итог | +| ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | +| `CMakeLists.txt` | Файл находится здесь: `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/CMakeLists.txt` | Он описывает C++ проект, стандарт C++17, исполняемый файл `static_server`, путь к Boost из Conan и подключение `pthread` | Проект собирается через CMake | + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/CMakeLists.txt +``` + +```cmake +cmake_minimum_required(VERSION 3.11) + +project(web_server_lesson_18_static_files CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(static_server + src/main.cpp +) + +target_include_directories(static_server PRIVATE + /home/ubuntu/.conan/data/boost/1.86.0/_/_/package/4c73f888ee1301ffed212b5fd37391dd45ff1c09/include +) + +target_link_libraries(static_server + pthread +) +``` + +| 📌 Строка или команда из `CMakeLists.txt` | 📖 Полный разбор того, что она делает | 🧠 Зачем это нужно проекту | ✅ Итог | +| ------------------------------------------------ | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ------------------------------- | +| `cmake_minimum_required(VERSION 3.11)` | Минимальная версия CMake для сборки проекта | CMake — это система генерации сборочных файлов | Версия сборочной системы задана | +| `project(web_server_lesson_18_static_files CXX)` | Создаёт проект C++ | `CXX` означает C++ compiler | Проект объявлен как C++ | +| `set(CMAKE_CXX_STANDARD 17)` | Включает стандарт C++17 | Код компилируется как C++17 | Стандарт задан | +| `set(CMAKE_CXX_STANDARD_REQUIRED ON)` | Требует именно этот стандарт | Сборка не должна откатиться на другой стандарт | Требование стандарта включено | +| `add_executable(static_server src/main.cpp)` | Создаёт исполняемый файл `static_server` из файла `src/main.cpp` | Это главный бинарник сервера | Бинарник создаётся | +| `target_include_directories(...)` | Добавляет путь к заголовочным файлам Boost | Используется Boost из Conan-кэша | Заголовки Boost найдены | +| `target_link_libraries(static_server pthread)` | Подключает POSIX threads | POSIX — Portable Operating System Interface. `pthread` — POSIX threads, библиотека потоков | Потоки подключены | + +```cmake +cmake_minimum_required(VERSION 3.11) +``` + +```cmake +project(web_server_lesson_18_static_files CXX) +``` + +```cmake +set(CMAKE_CXX_STANDARD 17) +``` + +```cmake +set(CMAKE_CXX_STANDARD_REQUIRED ON) +``` + +```cmake +add_executable(static_server src/main.cpp) +``` + +```text +static_server +``` + +```text +src/main.cpp +``` + +```cmake +target_include_directories(...) +``` + +```cmake +target_link_libraries(static_server pthread) +``` + +--- + +## 📄 HTML-файл + +| 📌 HTML-файл урока и его полный путь | 📖 Полное содержимое файла и важные элементы страницы | 🧠 Что именно делает браузер при загрузке HTML | ✅ Итог | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | +| `static/index.html` | Файл находится здесь: `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/index.html` | В HTML подключается CSS через ``, JavaScript через ``, а также создаётся `` | HTML запускает загрузку CSS, JS и создаёт область для игры | +| Главное в HTML | `link` заставляет браузер отдельно запросить CSS-файл, `script` заставляет браузер отдельно запросить JavaScript-файл, `canvas` создаёт область для рисования игры | Поэтому один запрос к `/` приводит к дополнительным запросам `/style.css` и `/app.js` | Клиентская страница собирается из нескольких файлов | + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/index.html +``` + +```html + + + + + Static Files Lesson + + + +

Игровой клиент запущен

+ + + + +``` + +```html + +``` + +```html + +``` + +```html + +``` + +--- + +## 🎨 CSS-файл + +| 📌 CSS-файл урока и его полный путь | 📖 Полное содержимое CSS и его назначение | 🧠 Что меняется на странице благодаря CSS | ✅ Итог | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------- | +| `static/style.css` | Файл находится здесь: `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/style.css` | Этот файл задаёт внешний вид страницы: фон, цвет текста, шрифт, центрирование, внешний вид canvas, рамку и отступы | Страница становится оформленной, а canvas получает визуальные границы | + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/style.css +``` + +```css +body { + margin: 0; + background-color: #202124; + color: white; + font-family: Arial, sans-serif; + text-align: center; +} + +canvas { + display: block; + margin: 40px auto; + background-color: #111; + border: 2px solid white; +} +``` + +--- + +## ⚡ JavaScript-файл + +| 📌 JavaScript-файл урока и его полный путь | 📖 Полное содержимое JavaScript и что делает каждая часть | 🧠 Как это связано с canvas и браузерной игрой | ✅ Итог | +| ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| `static/app.js` | Файл находится здесь: `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/app.js` | Код находит canvas, получает 2D-контекст, рисует зелёный квадрат и пишет сообщение в консоль браузера | JavaScript оживляет HTML-страницу и показывает, что скрипт реально загружен | +| Что делает код | 1. Находит canvas. 2. Получает 2D-контекст. 3. Рисует зелёный квадрат. 4. Пишет сообщение в консоль браузера | Это минимальная демонстрация работы клиентского JavaScript, отданного C++ сервером | JS успешно работает | + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/app.js +``` + +```javascript +const canvas = document.getElementById("game"); +const context = canvas.getContext("2d"); + +context.fillStyle = "lime"; +context.fillRect(100, 100, 80, 80); + +console.log("Static files lesson started"); +``` + +--- + +## ❌ Важная ошибка, которую исправили + +| 📌 В чём была ошибка с путём к статической папке | 📖 Подробное объяснение причины, неправильного поведения и исправления | 🧠 Почему сервер возвращал 404 и как это было исправлено | ✅ Итог | +| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| Неправильный `Static root` | Изначально сервер показывал `Static root: "/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/build/static"`, но файлы лежали не там | Правильная папка: `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static` | Сервер искал файлы в `build/static`, где их не было | +| Причина ошибки | Ошибка была в строке `fs::path static_root = fs::current_path() / "static";` | Потому что сервер запускался из папки `build`, а значит `current_path()` указывал на `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/build` | Поэтому сервер искал `build/static/index.html` | +| Последствие | Такой папки не было, поэтому сервер возвращал `404 Not Found` | Файл `index.html` физически лежал на уровень выше, в папке `static` | 404 был ожидаем из-за неправильного пути | +| Исправление | Исправление: `fs::path static_root = fs::current_path().parent_path() / "static";` | Теперь сервер из папки `build` поднимается на уровень выше и берёт правильную папку `static` | `Static root` стал правильным | + +```text +Static root: "/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/build/static" +``` + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static +``` + +```cpp +fs::path static_root = fs::current_path() / "static"; +``` + +```text +build +``` + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/build +``` + +```text +build/static/index.html +``` + +```text +404 Not Found +``` + +```cpp +fs::path static_root = fs::current_path().parent_path() / "static"; +``` + +--- + +## 🧠 Основной код сервера + +| 📌 Файл основного кода сервера и его полный путь | 📖 Что реализует код в `main.cpp` | 🧠 Какие HTTP-сценарии покрывает сервер | ✅ Итог | +| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| `src/main.cpp` | Файл находится здесь: `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/src/main.cpp` | Код реализует HTTP-сервер на Boost.Beast, обработку GET-запросов, чтение файлов, определение MIME-типа, проверку безопасности пути, ответы `200 OK`, `403 Forbidden`, `404 Not Found`, `405 Method Not Allowed` | Сервер умеет отдавать статические файлы и защищаться от опасных путей | + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/src/main.cpp +``` + +| 📌 Возможность сервера | 📖 Что именно реализовано | 🧠 Для чего это нужно | ✅ Итог | +| -------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------- | ---------------------- | +| HTTP-сервер на Boost.Beast | Сервер принимает HTTP-запросы и формирует HTTP-ответы | Это основа сетевой части урока | HTTP работает | +| Обработка GET-запросов | Разрешён основной метод для получения статических файлов | Браузер обычно получает файлы через GET | GET работает | +| Чтение файлов | Сервер открывает файл из папки `static` и отправляет его содержимое | Без этого нельзя отдавать HTML/CSS/JS | Файлы читаются | +| Определение MIME-типа | Сервер выбирает `Content-Type` по расширению файла | Браузер корректно понимает тип ответа | MIME работает | +| Проверка безопасности пути | Проверяется, что итоговый путь остаётся внутри `static_root` | Это защита от path traversal | Безопасность добавлена | +| HTTP-коды | Реализованы ответы `200 OK`, `403 Forbidden`, `404 Not Found`, `405 Method Not Allowed` | Клиент получает корректный статус | Ошибки различаются | + +--- + +## 🔄 Логика обработки запроса + +| 📌 HTTP-запрос клиента | 📖 Что делает сервер при таком запросе | 🧠 Какой файл или статус возвращается | ✅ Итог | +| ---------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------- | +| `GET /` | Если приходит `GET /`, сервер отдаёт `static/index.html` | Корневой URL открывает главную страницу | Главная страница работает | +| `GET /style.css` | Если приходит `GET /style.css`, сервер отдаёт `static/style.css` | CSS загружается отдельно | Стили работают | +| `GET /app.js` | Если приходит `GET /app.js`, сервер отдаёт `static/app.js` | JavaScript загружается отдельно | Скрипт работает | +| `GET /unknown.png` | Если файла нет, сервер возвращает `404 Not Found` | Несуществующий ресурс корректно обрабатывается | 404 работает | +| `GET /../../secret` | Если пользователь пытается выйти из папки `static`, сервер должен вернуть `403 Forbidden` | Защита не даёт читать файлы вне разрешённой папки | 403 работает | + +```text +GET / +``` + +```text +static/index.html +``` + +```text +GET /style.css +``` + +```text +static/style.css +``` + +```text +GET /app.js +``` + +```text +static/app.js +``` + +```text +GET /unknown.png +``` + +```text +404 Not Found +``` + +```text +GET /../../secret +``` + +```text +403 Forbidden +``` + +--- + +## 🔧 Сборка проекта + +| 📌 Шаг сборки проекта из папки урока | 💻 Команда, которую нужно выполнить в терминале | 📖 Что делает команда | ✅ Итог | +| ------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------ | --------------------- | +| Перейти в папку проекта | `cd /home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files` | Открывает корневую папку урока | Готово к сборке | +| Удалить старую сборку | `rm -rf build` | Удаляет старый build-каталог | Сборка будет чистой | +| Создать папку сборки | `mkdir build` | Создаёт новый build-каталог | Папка создана | +| Перейти в build | `cd build` | Открывает папку сборки | Можно запускать CMake | +| Запустить CMake | `cmake ..` | Генерирует файлы сборки | Конфигурация готова | +| Собрать проект | `cmake --build .` | Компилирует `static_server` | Бинарник собран | + +```bash +cd /home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files +rm -rf build +mkdir build +cd build +cmake .. +cmake --build . +``` + +--- + +## ▶️ Запуск сервера + +| 📌 Откуда запускать сервер и какую команду выполнить | 📖 Какой вывод ожидается при правильном запуске | 🧠 Как понять, что путь к `static` исправлен правильно | ✅ Итог | | +| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | ----------------------------------------------- | +| Запуск из папки build | Из папки `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/build` запустить `./static_server` | Ожидаемый вывод: `Server started: http://localhost:8080` и `Static root: "/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static"` | Если в `Static root` написано `build/static`, значит путь исправлен неправильно | Сервер должен показывать правильный static root | + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/build +``` + +```bash +./static_server +``` + +```text +Server started: http://localhost:8080 +Static root: "/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static" +``` + +```text +build/static +``` + +--- + +## 🧪 Проверка через WSL + +| 📌 Что проверяется через curl во втором терминале WSL | 💻 Команда для проверки | 📖 Ожидаемый HTTP-ответ или ключевые заголовки | ✅ Итог | +| ----------------------------------------------------- | -------------------------------------------- | -------------------------------------------------------------------- | ------------------------- | +| Главная страница | `curl -i http://localhost:8080/` | Ожидается `HTTP/1.1 200 OK` и `Content-Type: text/html` | Главная страница отдаётся | +| HTML напрямую | `curl -i http://localhost:8080/index.html` | Ожидается `HTTP/1.1 200 OK` и `Content-Type: text/html` | HTML отдаётся | +| CSS | `curl -i http://localhost:8080/style.css` | Ожидается `HTTP/1.1 200 OK` и `Content-Type: text/css` | CSS отдаётся | +| JavaScript | `curl -i http://localhost:8080/app.js` | Ожидается `HTTP/1.1 200 OK` и `Content-Type: application/javascript` | JS отдаётся | +| Несуществующий файл | `curl -i http://localhost:8080/unknown.png` | Ожидается `HTTP/1.1 404 Not Found` и `Content-Type: text/plain` | 404 работает | +| Попытка выхода из директории | `curl -i http://localhost:8080/../../secret` | Ожидается `HTTP/1.1 403 Forbidden` и `Content-Type: text/plain` | 403 работает | + +```bash +curl -i http://localhost:8080/ +``` + +```http +HTTP/1.1 200 OK +Content-Type: text/html +``` + +```bash +curl -i http://localhost:8080/index.html +``` + +```http +HTTP/1.1 200 OK +Content-Type: text/html +``` + +```bash +curl -i http://localhost:8080/style.css +``` + +```http +HTTP/1.1 200 OK +Content-Type: text/css +``` + +```bash +curl -i http://localhost:8080/app.js +``` + +```http +HTTP/1.1 200 OK +Content-Type: application/javascript +``` + +```bash +curl -i http://localhost:8080/unknown.png +``` + +```http +HTTP/1.1 404 Not Found +Content-Type: text/plain +``` + +```bash +curl -i http://localhost:8080/../../secret +``` + +```http +HTTP/1.1 403 Forbidden +Content-Type: text/plain +``` + +--- + +## 🌐 Проверка через браузер Windows + +| 📌 Что открыть в браузере Windows | 📖 Что должно произойти | 🧠 Что этим проверяется | ✅ Итог | +| ------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------- | ---------------------------- | +| `http://localhost:8080/` | Ожидается HTML-страница `Игровой клиент запущен` и canvas с зелёным квадратом | Проверяется главная страница, CSS, JS и canvas | Браузерная проверка проходит | +| `http://localhost:8080/index.html` | Должна открыться страница | Проверяется прямой доступ к HTML | HTML работает | +| `http://localhost:8080/style.css` | Должен открыться CSS-текст | Проверяется отдача CSS-файла | CSS доступен | +| `http://localhost:8080/app.js` | Должен открыться JavaScript-текст | Проверяется отдача JS-файла | JS доступен | +| `http://localhost:8080/unknown.png` | Должно быть `404 Not Found` | Проверяется несуществующий файл | 404 работает | +| `http://localhost:8080/../../secret` | Должно быть `403 Forbidden` | Проверяется защита от выхода из папки `static` | 403 работает | + +```text +http://localhost:8080/ +``` + +```text +Игровой клиент запущен +``` + +```text +http://localhost:8080/index.html +``` + +```text +http://localhost:8080/style.css +``` + +```text +http://localhost:8080/app.js +``` + +```text +http://localhost:8080/unknown.png +``` + +```text +404 Not Found +``` + +```text +http://localhost:8080/../../secret +``` + +```text +403 Forbidden +``` + +--- + +## 💻 Почему браузер Windows видит сервер из WSL + +| 📌 Что такое WSL и почему Windows может открыть сервер, запущенный внутри Linux-подсистемы | 📖 Расшифровка WSL и объяснение работы `localhost` между Windows и WSL | 🧠 Почему это удобно для разработки | ✅ Итог | +| ------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | +| WSL | WSL — Windows Subsystem for Linux | Расшифровка: Windows — операционная система Windows, Subsystem — подсистема, for — для, Linux — Linux | WSL позволяет запускать Linux-сервер рядом с Windows-браузером | +| Доступ через localhost | Когда сервер в WSL слушает адрес `0.0.0.0:8080`, Windows обычно может открыть его через `http://localhost:8080/` | Это позволяет запускать сервер в WSL, а проверять страницу в обычном браузере Windows | Проверка через Windows-браузер удобна | + +```text +0.0.0.0:8080 +``` + +```text +http://localhost:8080/ +``` + +--- + +## 🔢 Почему использовали порт 8080 + +| 📌 Почему выбран порт `8080`, а не обычный HTTP-порт `80` | 📖 Подробное объяснение причины выбора учебного порта | 🧠 Почему это удобно в разработке | ✅ Итог | +| --------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------ | +| Порт 8080 | Порт `8080` часто используют для учебных HTTP-серверов | Обычный HTTP-порт — `80`, но он может требовать прав администратора или быть занят | Для разработки удобнее использовать `8080` | + +```text +80 +``` + +```text +8080 +``` + +--- + +## ✅ Что такое 200 OK + +| 📌 HTTP-код `200 OK` | 📖 Что означает этот статус | 🧠 Пример из этого урока | ✅ Итог | +| -------------------- | -------------------------------------- | ------------------------------------------------------------------ | --------------------- | +| `200 OK` | Означает, что запрос успешно обработан | Например, `GET /index.html`: файл найден, сервер вернул содержимое | Успешная отдача файла | + +```text +200 OK +``` + +```text +GET /index.html +``` + +--- + +## ⛔ Что такое 403 Forbidden + +| 📌 HTTP-код `403 Forbidden` | 📖 Что означает этот статус | 🧠 Пример из этого урока | ✅ Итог | +| --------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | +| `403 Forbidden` | Означает, что сервер понял запрос, но запрещает доступ | В этом уроке 403 используется для защиты от выхода из папки `static`, например при опасном запросе `/../../secret` | Доступ запрещён по правилам безопасности | + +```text +403 Forbidden +``` + +```text +/../../secret +``` + +--- + +## ❓ Что такое 404 Not Found + +| 📌 HTTP-код `404 Not Found` | 📖 Что означает этот статус | 🧠 Пример из этого урока | ✅ Итог | +| --------------------------- | ------------------------------------------ | ------------------------ | ----------------------------- | +| `404 Not Found` | Означает, что запрошенный ресурс не найден | Пример: `/unknown.png` | Несуществующий файл не найден | + +```text +404 Not Found +``` + +```text +/unknown.png +``` + +--- + +## 🚫 Что такое 405 Method Not Allowed + +| 📌 HTTP-код `405 Method Not Allowed` | 📖 Что означает этот статус | 🧠 Как используется в этом уроке | ✅ Итог | +| ------------------------------------ | ------------------------------------ | -------------------------------------------------------------------------------------------------------- | ----------------------------------- | +| `405 Method Not Allowed` | Означает, что HTTP-метод не разрешён | В этом уроке разрешён только `GET`. Если отправить `POST`, `PUT` или `DELETE`, сервер должен вернуть 405 | Неподдерживаемые методы отклоняются | + +```text +405 Method Not Allowed +``` + +```text +GET +``` + +--- + +## 🛡 Что такое path traversal + +| 📌 Понятие path traversal и пример атаки обхода пути | 📖 Подробное объяснение атаки и защиты от выхода из разрешённой папки `static` | 🧠 Какой код используется для проверки безопасности | ✅ Итог | +| ---------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | +| Path traversal | Path traversal — атака обхода пути | Пример: `/../../secret`. Злоумышленник пытается выйти из разрешённой папки `static` и прочитать файлы вне неё | Это опасный сценарий, который нужно блокировать | +| Защита | Для защиты используется проверка `IsSubPath(requested_path, static_root)` | Она проверяет, что итоговый путь всё ещё находится внутри `static_root` | Файлы вне `static` не отдаются | + +```text +/../../secret +``` + +```text +static +``` + +```cpp +IsSubPath(requested_path, static_root) +``` + +--- + +## 🏁 Итог + +| 📌 Что сделано в практическом уроке по отдаче статических файлов | 📖 Подробное описание результата без сокращений | 🧠 Почему это важно | ✅ Итог | +| ---------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------- | +| Создана отдельная папка урока | Практический урок оформлен в отдельной директории `sprint_18_20_theme_1_4_lesson_3_10_static_files` | Это удобно для самостоятельной сборки и проверки | Папка готова | +| Добавлены статические файлы | Добавлены `static/index.html`, `static/style.css`, `static/app.js` | Браузер получает HTML, стили и JavaScript | Клиентская часть есть | +| Написан C++ HTTP-сервер | Сервер написан на C++ с использованием Boost.Beast | Он умеет принимать HTTP-запросы и отдавать файлы | Сервер работает | +| Реализована отдача статических файлов | Сервер отдаёт HTML, CSS и JS из папки `static` | Это превращает сервер в поставщика браузерного клиента | Static serving работает | +| Добавлено определение MIME-типа | Сервер выставляет правильный `Content-Type` для файлов | Браузер корректно обрабатывает HTML, CSS и JS | MIME работает | +| Добавлена защита от выхода из директории | Запросы вида `/../../secret` должны получать `403 Forbidden` | Это защита от path traversal | Безопасность добавлена | +| Исправлена ошибка с путём `build/static` | Используется `fs::current_path().parent_path() / "static"` | Сервер ищет файлы в правильной папке | Путь исправлен | +| Переиспользован уже существующий Boost из Conan | Использован Boost из Conan-кэша | Не понадобилось ставить системный Boost | Conan помог | +| Не устанавливался `libboost-all-dev` | Мы не стали устанавливать `sudo apt install libboost-all-dev` | Не загружались лишние 500 MB или больше | Место и время сохранены | +| Проверка выполняется через `curl` | Проверяются `/`, `/index.html`, `/style.css`, `/app.js`, `/unknown.png`, `/../../secret` | CLI-проверка показывает HTTP-коды и заголовки | curl-тесты готовы | +| Проверка выполняется через браузер Windows | Открывается `http://localhost:8080/` | Видно HTML-страницу, canvas и работу CSS/JS | Браузерная проверка проходит | + +Главный результат: + +```text +http://localhost:8080/ +``` + +открывает игровой HTML-клиент, который загружает CSS и JavaScript с C++ сервера. diff --git a/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/src/main.cpp b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/src/main.cpp new file mode 100644 index 0000000..925a0a3 --- /dev/null +++ b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/src/main.cpp @@ -0,0 +1,171 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace beast = boost::beast; +namespace http = beast::http; +namespace net = boost::asio; +namespace fs = std::filesystem; + +using tcp = net::ip::tcp; + +std::string GetMimeType(const fs::path& path) { + static const std::unordered_map types = { + {".html", "text/html"}, + {".htm", "text/html"}, + {".css", "text/css"}, + {".js", "application/javascript"}, + {".json", "application/json"}, + {".png", "image/png"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".gif", "image/gif"}, + {".svg", "image/svg+xml"}, + {".ico", "image/vnd.microsoft.icon"}, + {".txt", "text/plain"} + }; + + auto extension = path.extension().string(); + auto it = types.find(extension); + + if (it != types.end()) { + return it->second; + } + + return "application/octet-stream"; +} + +std::string ReadFile(const fs::path& path) { + std::ifstream file(path, std::ios::binary); + + if (!file) { + throw std::runtime_error("Cannot open file"); + } + + return std::string( + std::istreambuf_iterator(file), + std::istreambuf_iterator() + ); +} + +bool IsSubPath(const fs::path& path, const fs::path& base) { + auto canonical_path = fs::weakly_canonical(path); + auto canonical_base = fs::weakly_canonical(base); + + auto path_it = canonical_path.begin(); + auto base_it = canonical_base.begin(); + + for (; base_it != canonical_base.end(); ++base_it, ++path_it) { + if (path_it == canonical_path.end() || *path_it != *base_it) { + return false; + } + } + + return true; +} + +http::response MakeTextResponse( + http::status status, + std::string text, + unsigned version +) { + http::response response(status, version); + response.set(http::field::content_type, "text/plain"); + response.body() = std::move(text); + response.prepare_payload(); + return response; +} + +http::response HandleRequest( + const http::request& request, + const fs::path& static_root +) { + if (request.method() != http::verb::get) { + return MakeTextResponse( + http::status::method_not_allowed, + "Only GET is allowed", + request.version() + ); + } + + std::string target = std::string(request.target()); + + fs::path requested_path; + + if (target == "/") { + requested_path = static_root / "index.html"; + } else { + requested_path = static_root / target.substr(1); + } + + if (!IsSubPath(requested_path, static_root)) { + return MakeTextResponse( + http::status::forbidden, + "403 Forbidden", + request.version() + ); + } + + if (!fs::exists(requested_path) || !fs::is_regular_file(requested_path)) { + return MakeTextResponse( + http::status::not_found, + "404 Not Found", + request.version() + ); + } + + auto body = ReadFile(requested_path); + + http::response response(http::status::ok, request.version()); + response.set(http::field::content_type, GetMimeType(requested_path)); + response.body() = std::move(body); + response.prepare_payload(); + + return response; +} + +void HandleSession(tcp::socket socket, const fs::path& static_root) { + beast::flat_buffer buffer; + + http::request request; + http::read(socket, buffer, request); + + auto response = HandleRequest(request, static_root); + + http::write(socket, response); + + beast::error_code ec; + socket.shutdown(tcp::socket::shutdown_send, ec); +} + +int main() { + try { + const auto address = net::ip::make_address("0.0.0.0"); + const unsigned short port = 8080; + + fs::path static_root = fs::current_path().parent_path() / "static"; + + net::io_context io_context; + + tcp::acceptor acceptor(io_context, {address, port}); + + std::cout << "Server started: http://localhost:8080\n"; + std::cout << "Static root: " << static_root << "\n"; + + while (true) { + tcp::socket socket(io_context); + acceptor.accept(socket); + + HandleSession(std::move(socket), static_root); + } + } catch (const std::exception& ex) { + std::cerr << "Error: " << ex.what() << "\n"; + return 1; + } +} diff --git a/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/app.js b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/app.js new file mode 100644 index 0000000..2e7c7c3 --- /dev/null +++ b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/app.js @@ -0,0 +1,7 @@ +const canvas = document.getElementById("game"); +const context = canvas.getContext("2d"); + +context.fillStyle = "lime"; +context.fillRect(100, 100, 80, 80); + +console.log("Static files lesson started"); diff --git a/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/index.html b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/index.html new file mode 100644 index 0000000..395e174 --- /dev/null +++ b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/index.html @@ -0,0 +1,13 @@ + + + + + Static Files Lesson + + + +

Игровой клиент запущен

+ + + + diff --git a/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/style.css b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/style.css new file mode 100644 index 0000000..84d1814 --- /dev/null +++ b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files/static/style.css @@ -0,0 +1,14 @@ +body { + margin: 0; + background-color: #202124; + color: white; + font-family: Arial, sans-serif; + text-align: center; +} + +canvas { + display: block; + margin: 40px auto; + background-color: #111; + border: 2px solid white; +} diff --git a/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2/CMakeLists.txt b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2/CMakeLists.txt new file mode 100644 index 0000000..72e0260 --- /dev/null +++ b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.11) + +project(web_server_lesson_18_static_files CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(static_server + src/main.cpp +) + +target_include_directories(static_server PRIVATE + /home/ubuntu/.conan/data/boost/1.86.0/_/_/package/4c73f888ee1301ffed212b5fd37391dd45ff1c09/include +) + +target_link_libraries(static_server + pthread +) diff --git a/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2/README.md b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2/README.md new file mode 100644 index 0000000..869ab1a --- /dev/null +++ b/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2/README.md @@ -0,0 +1,461 @@ +# 📘 README — Sprint 18_20 → Theme 1_4 → Lesson 3_10 (Part 2) + +--- + +## 🧩 Тема + +| 🧩 Название темы урока | 📖 Подробное описание темы | 🧠 Что именно расширяется в этом практическом уроке | ✅ Итог | +| ---------------------------------------- | ---------------------------------------- | ------------------------------------------------------- | -------------------------------------------------------- | +| Работа с кэшем и эффективное логирование | Работа с кэшем и эффективное логирование | Практическое расширение: медиа-типы и статические файлы | Урок продолжает развитие HTTP-сервера статических файлов | + +--- + +## 🎯 Цель + +| 🎯 Главная цель урока | 📖 Что должен научиться отдавать HTTP-сервер на C++ | 🧠 Почему это важно для браузера | ✅ Итог | +| ---------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------------- | +| Расширить HTTP-сервер на C++ | Сервер должен уметь отдавать HTML, CSS, JavaScript, изображения PNG/JPG, аудио WAV и видео MP4 | Браузер должен корректно понимать разные типы ресурсов и показывать/воспроизводить их | Сервер становится полноценнее как мини веб-сервер для фронтенда | + +```text +✔ HTML +✔ CSS +✔ JavaScript +✔ изображения (PNG, JPG) +✔ аудио (WAV) +✔ видео (MP4) +``` + +--- + +## 📂 Полный путь проекта + +| 📂 Что указывается | 📖 Полный путь проекта | 🧠 Зачем нужен этот путь | ✅ Итог | +| ------------------------- | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------- | +| Папка практического урока | `/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2` | По этому пути открывается проект, собирается сервер и создаётся README | Путь проекта зафиксирован | + +```text +/home/ubuntu/cppbackend/lessons/sprint_18_20_theme_1_4_lesson_3_10_static_files_part2 +``` + +--- + +## 📁 Структура проекта + +| 📁 Часть проекта | 📖 Что находится внутри | 🧠 Для чего нужна эта часть | ✅ Итог | +| ------------------- | ----------------------- | ------------------------------------------------------------ | ------------------------------------ | +| `CMakeLists.txt` | Файл сборки CMake | Описывает сборку C++ проекта | Проект можно собрать через CMake | +| `README.md` | Документация урока | Объясняет цель, структуру, теорию, сборку, запуск и проверки | Урок оформлен как полноценный README | +| `build/` | Папка сборки | В неё генерируются build-файлы и бинарник | Сборка отделена от исходников | +| `src/main.cpp` | Основной код сервера | Реализует HTTP-сервер, MIME-типы и отдачу файлов | Серверная логика находится в `src` | +| `static/index.html` | HTML-страница | Главная страница браузерного клиента | HTML отдаётся сервером | +| `static/style.css` | CSS-стили | Оформление страницы | CSS отдаётся сервером | +| `static/app.js` | JavaScript-код | Клиентская логика страницы | JS отдаётся сервером | +| `static/images/` | Изображения PNG | Картинки для проверки `image/png` | Изображения доступны через HTTP | +| `static/audio/` | Аудио WAV | Звук для проверки `audio/wav` | Аудио доступно через HTTP | +| `static/video/` | Видео MP4 | Видео для проверки `video/mp4` | Видео доступно через HTTP | + +```text +sprint_18_20_theme_1_4_lesson_3_10_static_files_part2/ +├── CMakeLists.txt +├── README.md +├── build/ +├── src/ +│ └── main.cpp +└── static/ + ├── index.html + ├── style.css + ├── app.js + ├── images/ + │ ├── road_vh.png + │ ├── road_tr.png + │ ├── road_t.png + │ └── tv_architecture.png + ├── audio/ + │ └── front_center.wav + └── video/ + ├── reset_position.mp4 + ├── track_vertical_plane.mp4 + └── track_horizontal_plane.mp4 +``` + +--- + +## 🧠 Теория: MIME-type / media type + +| 🧠 Теоретическое понятие | 📖 Подробное объяснение | 🧩 Формат | ✅ Итог | +| ------------------------ | -------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------- | +| MIME-type | MIME-type также называют media type | `type/subtype` | MIME-type описывает тип содержимого, которое сервер отдаёт браузеру | +| Примеры MIME-type | HTML, изображения, аудио и видео имеют разные media type | `text/html`, `image/png`, `audio/wav`, `video/mp4` | Браузер понимает, как обрабатывать каждый файл | + +```text +type/subtype +``` + +```text +text/html +image/png +audio/wav +video/mp4 +``` + +--- + +## 🧾 Теория: Content-Type + +| 🧾 HTTP-заголовок | 📖 Что он сообщает браузеру | 🧠 Как браузер решает, что делать с данными | ✅ Итог | +| ------------------------- | -------------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------- | +| `Content-Type` | HTTP заголовок сообщает тип содержимого ответа | По `Content-Type` браузер выбирает режим обработки данных | Без правильного `Content-Type` файл может быть обработан неверно | +| `Content-Type: image/png` | Сервер сообщает, что тело ответа — PNG-изображение | Браузер отображает картинку | Изображение открывается как изображение | +| `text` | Текстовый тип | Браузер показывает текст | Текст отображается | +| `image` | Тип изображения | Браузер отображает картинку | Картинка отображается | +| `audio` | Тип аудио | Браузер использует плеер | Звук воспроизводится | +| `video` | Тип видео | Браузер использует видеоплеер | Видео воспроизводится | + +```http +Content-Type: image/png +``` + +```text +text → показать +image → отобразить +audio → плеер +video → видеоплеер +``` + +--- + +## 🧩 Основные типы MIME + +| 🧩 Основной тип | 📖 Что означает | 🧠 Примеры использования | ✅ Итог | +| --------------- | ----------------- | -------------------------- | ------------------------------------------------------- | +| `text/*` | Текстовые ресурсы | HTML, CSS, TXT | Текстовые данные показываются или применяются браузером | +| `image/*` | Изображения | PNG, JPG, GIF, SVG | Картинки отображаются браузером | +| `audio/*` | Звук | WAV, MP3, OGG | Аудио воспроизводится через плеер | +| `video/*` | Видео | MP4, WebM | Видео воспроизводится через видеоплеер | +| `application/*` | Прочее | JSON, PDF и другие форматы | Используется для данных и бинарных форматов | + +```text +text/* → текст +image/* → изображения +audio/* → звук +video/* → видео +application/* → прочее (json, pdf) +``` + +--- + +## 🧯 Fallback + +| 🧯 Что такое fallback | 📖 Какой MIME-type используется по умолчанию | 🧠 Что это значит для браузера | ✅ Итог | +| ------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------- | +| Fallback для неизвестного файла | `application/octet-stream` | Неизвестный бинарный файл обычно воспринимается как файл для скачивания | Сервер всё равно может безопасно отдать неизвестный тип | + +```text +application/octet-stream +``` + +```text +неизвестный бинарный файл → скачивание +``` + +--- + +## 🛠 Реализация + +| 🛠 Что реализуется | 📖 Где реализуется | 🧠 Зачем это нужно | ✅ Итог | +| ------------------ | ---------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | +| MIME-функция | `main.cpp` | Функция определяет `Content-Type` по расширению файла | Сервер отдаёт HTML, CSS, JS, изображения, аудио и видео с правильным MIME | +| Таблица расширений | `std::unordered_map` | Связывает `.html`, `.css`, `.js`, `.png`, `.wav`, `.mp4` и другие расширения с MIME-type | Типы выбираются автоматически | +| Fallback | `application/octet-stream` | Возвращается, если расширение неизвестно | Неизвестные файлы получают безопасный бинарный тип | + +--- + +## 📌 MIME-функция (`main.cpp`) + +| 📌 Элемент функции | 📖 Что делает | 🧠 Почему важно | ✅ Итог | +| --------------------------- | ------------------------------------------ | ------------------------------------------------------ | ------------------------------------------- | +| `GetMimeType` | Принимает путь к файлу | Из пути можно получить расширение | MIME определяется по файлу | +| `types` | Хранит соответствие расширений и MIME-type | Быстрый поиск нужного `Content-Type` | Сервер знает основные media types | +| `path.extension().string()` | Получает расширение файла | Например `.png`, `.wav`, `.mp4` | Расширение используется как ключ | +| `types.find(ext)` | Ищет расширение в таблице | Если расширение известно, возвращается корректный MIME | Известные файлы получают правильный тип | +| `application/octet-stream` | Возвращается для неизвестного расширения | Это fallback | Неизвестные бинарные файлы не ломают сервер | + +```cpp +std::string GetMimeType(const fs::path& path) { + static const std::unordered_map types = { + {".html", "text/html"}, + {".css", "text/css"}, + {".js", "application/javascript"}, + {".json", "application/json"}, + + {".png", "image/png"}, + {".jpg", "image/jpeg"}, + {".jpeg", "image/jpeg"}, + {".gif", "image/gif"}, + {".svg", "image/svg+xml"}, + + {".mp3", "audio/mpeg"}, + {".wav", "audio/wav"}, + {".ogg", "audio/ogg"}, + + {".mp4", "video/mp4"}, + {".webm", "video/webm"}, + + {".txt", "text/plain"} + }; + + auto ext = path.extension().string(); + auto it = types.find(ext); + + if (it != types.end()) { + return it->second; + } + + return "application/octet-stream"; +} +``` + +--- + +## 🌐 HTML (`index.html`) + +| 🌐 HTML-блок | 📖 Что добавлено на страницу | 🧠 Какой файл запрашивает браузер | ✅ Итог | +| ------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------- | ------------------------------ | +| `Изображения` | Добавлены изображения через `` | `/images/road_vh.png`, `/images/road_tr.png`, `/images/road_t.png`, `/images/tv_architecture.png` | Браузер загружает картинки | +| `Звук` | Добавлен `