Skip to content

Latest commit

 

History

History
146 lines (128 loc) · 19.1 KB

File metadata and controls

146 lines (128 loc) · 19.1 KB

HTTP сервер на языке Си для Windows


ВАЖНО: Эта программа - учебный проект, и существует лишь для ознакомления. Не стоит пользоваться этой программой в реальных задачах/брать её за основу реальных проектов.


🇷🇺 Русская версия | 🇺🇸 English Version

Содержание:


Техническая составляющая сервера

Сервер написан на языке Си и собран в среде разработки Microsoft Visual Studio. В своём коде я использовал некоторые функции специфичные для MSVC (к примеру strcpy_s), следственно сборка программы через gcc невозможна без костылей. Сервер использует библиотеку WinSock2.h, и специфичен только для Windows.

Кратко о возможностях и функционале сервера

Мой сервер оперирует протоколами HTTP/1.0 и HTTP/1.0 для передачи данных, протоколы HTTP/2 и HTTP/3 не поддерживаются.

Я НЕ РЕАЛИЗОВЫВАЛ на 100% весь функционал и разбиение на чёткие версии HTTP, основное отличие что имеет мой сервер - фильтрация на основе контекста и типа запроса

Сервер имеет работать со следующими методами HTTP:

  • GET
  • HEAD
  • OPTIONS

Остальные методы не поддерживаются.

Основная фишка моего сервера - фильтрация контента (подробнее смотри главу "Система фильтрации запросов") Сервер умеет фильтровать HTTP запросы от клиента на основе того, что именно хочет получить пользователь, и какие данные есть у самого пользователя в запросе. Сервер также умеет обрабатывать тип файлов в соотвествии с MIME. Мой сервер использует многопоточную систему для обработки нескольких клиентов одновременно.

Система фильтрации запросов

Как уже было сказано, сервер умеет фильтровать запросы на основе того как был сделан запрос. Текущая реализация позволяет фильтровать запросы по:

  • Версии HTTP
  • Имени файла/Пути к файлу
  • Полю Host:
  • User-Agent
  • Accept-Langauge

К примеру, если будет отправлен HTTP/1.1 запрос без поля "Host:" то сервер забракует его и выдаст BAD_REQUEST. Это позволяет создавать правила отдачи файлов клиенту или специфические настройки ответов. В текущей реализации это сделано достаточно топорно, но это можно было бы возвести в абсолют используя не только blacklist и whitelist списки для файлов, но и полноценные массивы структур с флагами, правами достура и фильтрацией запроса под каждый файл.
Нынешняя система фильтрации:

  • HTTP/1.1 -> Требует наличия поля Host:
  • Файл Chrome.txt (как и информация о нём) может быть получен только если запрос содержит поле 'User-Agent: Google'.
  • Файл Firefox.txt (как и информация о нём) может быть получен только если запрос содержит поле 'User-Agent: Mozilla'.
  • Файл File1.txt (как и информация о нём) может быть получен только если запрос НЕ СОДЕРЖИТ en-US в после 'Accept-Language'.
  • Файл File.ini (как и информация о нём) не может быть получена никак.
  • Запрос обязан содержать путь начинающийся с поля SERVER\
  • Запрос не должен содержать последовательностей %00, //// или Linux-way путей

Стоит также отметить и то, что отсутствие того или иного поля по которому происходит контроль не позволит получить информацию о файле.
Текущая система забракует запрос сразу, если увидит, что нет необходимого поля по которому происходит фильтрация.


Специфика запросов

Так как сервер написан под Windows окружение, я решил слегка изменить запросы к файлам по пути. Текущая реализация сервера вместо полного пути к файлу требует иметь входной ключ для запроса к файлу, то самое поле SERVER. Без этого поля сервер выдаст BAD_REQUEST. Проще всего это ассоциировать с корневой директорией Linux. Для запроса к файлу испольуется ключ "SERVER", на основе которого уже далее достраивается путь. (К примеру, SERVER\site\index.html) Таким образом, получается решить несколько проблем:

  • Теперь невозможно опуститься по директории ниже чем входной ключ (через path-traversal), моя схема это не позволит, так как она является КОРНЕМ сервера.
  • Теперь путь к серверу может быть любым, а смена директории сервера даже не будет замечена внешним пользователем. (Путь скрывается за абстракцией ключа SERVER)
  • Путь к директории сервера полностью скрыт.

Вот пример запроса что сервер примет: OPTIONS SERVER\random.txt HTTP/1.1\r\nHost: My-site\r\n\r\n


Техническая информация о сервере

Сервер написан на языке Си, использет функции MSVC и специфичен только для Windows (strcpy_s, strcat_s). Сервер использует библиоткеки WinSock2.h для сокетов и Windows.h (WinAPI) для многопоточных функций и работы с файловой системой Windows. Сервер не использует динамическую память, в приоритете только выделение на стеке. Сервер запускается на 127.0.0.1:8080 (localhost:8080, Локальный запуск на порту 8080) и ожидает подключения.

Архитектура сервера

Архитектура сервера - это, пожалуй, то к чему я пришёл не сразу. К текущей архитектуре я пришёл лишь со второго раза и долго думал как бы её улучшить. Текущая схема представляет собой код функций разделённый на уровни абстракций от L0 до L6, где L0 - корневой слой, от него произрастают все остальные слои, постепенно раскрываясь в дерево. Такая система позволяет управлять уровнем абстракций, как бы спускаясь вниз на всё более и более конкретные действия. Вот примерное описание каждого уровня:

  • L0 - Корневой узел, отвечает за первичную инициализацию сервера. Запускает мастер-функцию и запускает цикл по принятию клиентов
  • L1 - Мастер-поток, узел отвечающий за принятие клиентов и начало контакста с ними. К примеру контролирует получение HTTP запроса, формирование ответа, отправку, и завершения соединения.
  • L2 - Узел отвечающий за проверку корректности запроса, вызов принятия, создания и отправки запроса.
  • L3 - Уровень отвечающий за получение данных, парсинг HTTP запроса в структуру, создание заголовков HTTP, обработку разных методов и не только.
  • L4 - Уровень отвечающий за проверки строк в запросе HTTP, поиске файлов в системе, усиленную фильтрацию по ключевым словам, и формирование успешного ответа от сервера.
  • L5 - Уровень отвечающий за проверку поля Hosts, получения метода из запроса, определение решения о получения/отказа доступа к контенту, получения типов контента и получение сведений из запроса пользователя.
  • L6 - Уровень отвечающий за инспекцию строк в запросах, суммаризацию логики, и переферии сервера.

Если упростить описания то вот что мы получаем:

  • L0 - Основной поток, запуск сервера
  • L1 - Запуск отдельного потока для каждого клиента
  • L2 - Отправка, принятие, вызов функций для анализа запроса HTTP
  • L3 - Анализ заголовков по протоколу HTTP, правильное завершение согласно стандарту HTTP
  • L4 - Поверхностный анализ самого запроса HTTP (путь, контент)
  • L5 - Анализ строки с запрошенным контентом. Принятие решения на основе списков фильтрации
  • L6 - Работа со строками, переферия, остаточные действия, финальные выводы о намерениях пользователя.

Я достаточно горд такой структурой проекта (особенно учитывая что это учебный проект) так как она применима к любой "тяжёлой" программе. Основной проблемой является сложность возврата информации с самых низких слоёв (L7) на управляющие слои (L2) без искажения смысла.


Личное отношение к проекту и советы на будущее

Лично для меня этот проект первоначально не был основным. Основной целью было доказать себе что я смогу написать, а главное осознать, как создаётся такая структура кода, и что она вообще делает..как она работает.

По себе могу лишь сказать, что работая с этим проектом, который как я думал у меня займёт пару вечеров, (а занял месяц) я сделал много важных выводов для себя, которыми и хочу поделиться с вами.

  • Перечисления в Си в разы лучше #define для создания константных флагов.
  • Использование enum в качестве типов функций повышает предсказуемость функции
  • Структуры великолепны для передачи данных между функциями, а тайпдеф перечисления позволяют удобно хранить статусы/возвратные коды в читаемом для человека виде не используя magic numbers.
  • Массивы структур не страшны, и очень даже удобны.
  • Лучше сделать много функций но маленьких и понятных, чем одну в которой ты сам запутаешься
  • Если есть несколько маленьких функций повторяющих функционал друг друга - совмести.

Как протестировать сервер

Для тестирования сервера я предоставил папку 'serverdata' и скрипт servertest.ps1. В папке serverdata находятся файлы-пустышки, на которых и тестируется мой сервер. Файлы не занимают много места, но позволяют увидеть работу системы MIME и фильтрацию запросов наглядно. Для автоматизации всех тестов и удобства я написал маленький powershell скрипт, что протестирует сервер используя классические паттерны запросов. Эти запросы специально созданы для получения соответствующей реации от сервера. Для тестирования нужно сначала запустить .exe файл (из любой локации на диске), а потом уже запустить servertest.ps1. Примерно за секунду тест будет закончен, и вы сможете посмотреть результат. Если вы хотите изменить настройки скрипта - пожалуйста. Мой файл - лишь шаблон для тестрования основных фильтров.

Как собрать из исходников

Так как проект написан полностью под Windows и несовместим даже с gcc (без костылей) приходится использовать извращённые методы сборки. Наиболее простым вариантом будет просто открыть .sln файл (Для этого нужен полноценный Microsoft Visual Studio любой версии) -> Вид -> Обозреватель решений -> Выбрать файл main.c -> Нажмите на зелёную стрелку вверху экрана (Запустить локальный отладчик Windows или кнопка правее неё).

Вторым вариантом является сборка .exeшника при помощи Makefile. Для этого вам понадобится "Build Tools for Visual Studio". Это не IDE а именно среда для сборки. (если у вас есть VS то он у вас уже есть) После установки в меню пуск наберите "Developer Command prompt for VS (ваша версия)" После запуска переходите в папку где находятся исходники (при помощи команды cd в консоли) и пишете nmake. Должен появится .exe файл.


Важная информация для использования:

  • Это НЕ ПОЛНОЦЕННЫЙ ПРОДУКТ, а тестировочный, учебный образец.
  • Программа server.exe запускает ЛОКАЛЬНЫЙ HTTP СЕРВЕР на порту 8080 (127.0.0.1:8080). Для смены порта придётся лезть в код.
  • Сервер первоначально ищет файлы по пути C:\Server\serverdata, так что лучше создайте папку Server в корне диска, скопирейте данный репозиторий и пользуйтесь. Что бы ИЗМЕНИТЬ ПУТЬ ПОИСКА ФАЙЛОВ отправляйтесь в файл server.h и ищите #define SERV_WORKPATH. Подставьте туда свой путь, пересоберите приложение.
  • Скрипт poweshell может вызвать предупреждения (что то связанное с политикой запуска) и не запустится, запустите его через консоль Windows вот этой командой:
  • powershell.exe -ExecutionPolicy Bypass -File "путь_к_скрипту.ps1" ```
  • Сервер может работать не только с файлами из папки, можете экспериментировать и использовать свои файлы.