Наш магазин на eBay Наш магазин на AliExpress Наш канал в telegram

Простой http-сервер на C++ Builder (Indy)

Сегодня мы будем писать на C++ Builder простейший http-сервер, но для начала, как всегда, немного теории (просто чтобы было понятно, что мы вообще собираемся делать).

Итак, http (HyperText Transfer Protocol) — протокол передачи гипертекста (например, html-страничек). Это протокол прикладного уровня, из набора протоколов TCP/IP. В настоящий момент он является основным протоколом передачи данных в интернете (ну просто потому, что большая часть интернета — это и есть так или иначе сгенерированные html-странички).

Протокол http заточен под «клиент-серверную» архитектуру и построен на основе обмена между клиентом и сервером специальными сообщениями. При этом сообщение клиента называется request (запрос), а сообщение сервера — response (ответ). Подробно почитать про этот протокол можно вот здесь: RFC2616, а я объясню «на пальцах» только базовые вещи.

Когда мы хотим зайти на какую-то страничку в интернете, набираем в адресной строке её адрес и жмём Enter, фактически происходит следующее:

1) наш браузер (это и есть программа-клиент) отправляет серверу, на котором расположена нужная страничка, запрос (request) по протоколу http (именно поэтому адреса всех страничек начинаются с «http://», — мы указываем браузеру каким нужно пользоваться протоколом);

2) программа-сервер, расположенная на сервере, этот запрос обрабатывает и отсылает нашему браузеру ответ, содержащий нужную информацию.

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

Вот именно такую программу мы и собираемся написать. Всё, что эта программа будет уметь — это обрабатывать входящие по протоколу http GET-запросы (это единственный тип запросов, который любой http-сервер должен уметь обрабатывать в обязательном порядке) и отдавать в теле ответа (сообщения в http состоят из стартовой строки, заголовков и тела) содержимое одной единственной странички. Для начальных экспериментов этого вполне достаточно, а дальше можете копать сами.

Самым простым вариантом реализации http-сервера в среде C++ Builder является использование готового компонента IdHTTPServer. Это в общем-то уже готовый http-сервер, умеющий разбирать «по косточкам» входящие запросы и формировать правильно структурированные ответы. Всё, что нам остаётся сделать — это проанализировать динамически создаваемую этим компонентом структуру RequestInfo (содержащую различные параметры запроса: команду, заголовки, путь, имя хоста …) и, в зависимости от содержания этой структуры, заполнить структуру ResponseInfo (содержащую параметры ответа: код ответа, текст ответа, заголовки, тело ответа…).

Итак, создаём новый проект и кидаем на форму компонент IdHTTPServer (его можно найти на вкладке Indy Servers):

расположение компонента IdHTTPServer

Все его свойства пока оставим по умолчанию (только свойство active установим в true, чтобы http-сервер сразу запускался при старте приложения (можно прикрутить для старта и останова сервера отдельные кнопки и менять значение свойства active в обработчиках событий OnClick этих кнопок).

Теперь добавим на форму компонент Memo (его содержимое мы будем передавать в теле ответа) и впишем туда код тестовой html-странички:

код тестовой html-странички

Почти всё, осталось только написать обработчик события OnCommandGet (что мы будем делать, получив GET-запрос) компонента IdHTTPServer.

Давайте напишем его следующим образом:

void __fastcall TForm1::IdHTTPServer1CommandGet(TIdPeerThread *AThread,
      TIdHTTPRequestInfo *RequestInfo, TIdHTTPResponseInfo *ResponseInfo)
{ if(RequestInfo->Document=="/" || RequestInfo->Document=="/index.html")
  {     ResponseInfo->ResponseNo=200;
        ResponseInfo->ContentText=Memo1->Lines->Text;
  }
  else
  {     ResponseInfo->ResponseNo=404;
  }
}

Параметр RequestInfo->Document, как вы уже поняли, содержит путь запрошенного документа. Таким образом мы написали, что будем отдавать тестовую страничку при обращении к корню или к странице index.html, а во всех остальных случаях будем возвращать код 404 (страница не найдена).

Проверим, что получилось. Компилируем и запускаем наше приложение. Далее запускаем браузер и пишем в командной строке localhost или localhost/index.html. В итоге видим нашу страничку (или не видим её, если введём любой другой адрес):

тестирование http-сервера

Понятно, что страничку мы можем загружать не только из Memo, но и из любого файла на диске (настоящие http-серверы именно этим и занимаются).

Для отладки очень удобно пользоваться браузером Firefox (последний нормальный браузер остался). Так вот, если щёлкнуть правой кнопкой мыши по открытой в этом браузере страничке, во всплывающем меню выбрать пункт «исследовать элемент», далее выбрать «сеть», а затем обновить страницу, то можно увидеть подробности отправленного запроса и полученного ответа. На рисунках ниже можно увидеть как это выглядит для страничек, которые мы тестировали выше:

получение страницы index.html в отладчике
получение страницы another_page.html в отладчике

Теперь давайте добавим в запрос каких-нибудь параметров. Параметры GET-запроса указываются в адресной строке, после указания пути к файлу. Выглядит это следующим образом: ?parameter1_name[=value1]&parameter2_name[=value2]

Если свойство ParseParams компонента IdHTTPServer уcтановить в true, то параметры запроса будут автоматически распарсены в динамическую структуру. Получить их можно следующим образом:

  • общее количество распарсенных параметров: RequestInfo->Params->Count
  • имя i-го параметра: RequestInfo->Params->Names[i]
  • значение параметра с именем Name: RequestInfo->Params->Values[«Name»]

В 8-м Indy похоже есть глюк с распарсиванием. Для правильного распарсивания обязательно должны присутствовать и имя, и значение параметра, в противном случае количество параметров будет определено верно, но имена параметров, у которых не указано значение, окажутся пустыми.

Строка нераспарсенных параметров заносится в RequestInfo->UnparsedParams независимо от состояния флага ParseParams.

Итак, добавляем обработку параметров. Пусть, например, если параметр Show равен 1, — в страницу добавляется строка «Первое действие», а если он равен 2, то в страницу добавляется строка «Второе действие». Для этого изменим код обработчика следующим образом (не забудьте изменить свойство ParseParams компонента IdHTTPServer на true):

void __fastcall TForm1::IdHTTPServer1CommandGet(TIdPeerThread *AThread,
      TIdHTTPRequestInfo *RequestInfo, TIdHTTPResponseInfo *ResponseInfo)
{ AnsiString Answer;
  int EndBodyPos;
 
  if(RequestInfo->Document=="/" || RequestInfo->Document=="/index.html")
  {     ResponseInfo->ResponseNo=200;
        Answer=Memo1->Lines->Text;
        EndBodyPos=Answer.Pos("</body>");
 
        if(RequestInfo->UnparsedParams.Pos("Show"))             // если среди параметров есть параметр Show
        { int i=StrToInt(RequestInfo->Params->Values["Show"]);  // получаем его значение
          switch(i)                                             // и решаем что с этим делать
          { case 1: Answer.Insert("&lt;p&gt;Первое действие&lt;/p&gt;",EndBodyPos);
                    break;
            case 2: Answer.Insert("&lt;p&gt;Второе действие&lt;/p&gt;",EndBodyPos);
                    break;
          }
        }
        ResponseInfo->ContentText=Answer;
  }
  else
  {     ResponseInfo->ResponseNo=404;
  }
}

Снова компилируем и запускаем. Теперь, если ввести в адресной строке, например, такой запрос: «http://localhost/index.html?Show=2», то в браузере мы увидим, что сервер распознал строку параметров и изменил страничку в соответствии с тем, как мы его запрограммировали:

изменение страницы в зависимости от параметров запроса

На этом пока всё. Подробнее про структуры RequestInfo и ResponseInfo можно почитать в хэлпе.

Добавить комментарий