Сегодня мы будем писать на 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):
Все его свойства пока оставим по умолчанию (только свойство active установим в true, чтобы http-сервер сразу запускался при старте приложения (можно прикрутить для старта и останова сервера отдельные кнопки и менять значение свойства active в обработчиках событий OnClick этих кнопок).
Теперь добавим на форму компонент Memo (его содержимое мы будем передавать в теле ответа) и впишем туда код тестовой 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. В итоге видим нашу страничку (или не видим её, если введём любой другой адрес):
Понятно, что страничку мы можем загружать не только из Memo, но и из любого файла на диске (настоящие http-серверы именно этим и занимаются).
Для отладки очень удобно пользоваться браузером Firefox (последний нормальный браузер остался). Так вот, если щёлкнуть правой кнопкой мыши по открытой в этом браузере страничке, во всплывающем меню выбрать пункт «исследовать элемент», далее выбрать «сеть», а затем обновить страницу, то можно увидеть подробности отправленного запроса и полученного ответа. На рисунках ниже можно увидеть как это выглядит для страничек, которые мы тестировали выше:
Теперь давайте добавим в запрос каких-нибудь параметров. Параметры GET-запроса указываются в адресной строке, после указания пути к файлу. Выглядит это следующим образом: ?parameter1_name[=value1]¶meter2_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("<p>Первое действие</p>",EndBodyPos); break; case 2: Answer.Insert("<p>Второе действие</p>",EndBodyPos); break; } } ResponseInfo->ContentText=Answer; } else { ResponseInfo->ResponseNo=404; } } |
Снова компилируем и запускаем. Теперь, если ввести в адресной строке, например, такой запрос: «http://localhost/index.html?Show=2», то в браузере мы увидим, что сервер распознал строку параметров и изменил страничку в соответствии с тем, как мы его запрограммировали:
На этом пока всё. Подробнее про структуры RequestInfo и ResponseInfo можно почитать в хэлпе.