C/C++. Сетевое программирование. Создание веб-сервера. Часть 1

Создание веб - сервера. Громко сказано конечно :) Да и есть ли в этом необходимость, когда уже созданы такие веб - сервера, как, из самых известных:
- Apache и Apache Tomcat (Sun Microsystems);
- разработка Игоря Сысоева - Nginx;
- разработка компании Microsoft - IIS (Internet Information Services).
Но помимо этого есть и альтернативные разработки: Ascet HTTPd, CERN httpd и другие. С таким списком можно ознакомиться на Wiki.

Лично для меня архитектура и логика работы веб - сервера вызывает интерес.
Все мы знаем, что многоликий интернет базируется на работе раскинутых веб - серверов, связанных между собой в глобальную сеть за счет различных сетевых устройств. Например - роутеров, маршрутизаторов, коммутаторов и тому подобное. Опустим технические нюансы. Потребители интернета со своих машин также подключены к этой сети. И через соответствующие сетевые программы, так называемые -  User-agent - браузеры (от англ. browse - проссматривать) просматривают (точнее - запрашивают) веб - странички, которые располагаются на веб - серверах. Помимо содержания веб - страниц, веб - сервер может содержать и скриптовую часть. Мини-экскурс :) Но я пошел дальше.

Как можно разработать свой веб - сервер на C/C++?

Для этого воспользуемся прикладным интерфейсом - Windows Socket API (Winsock).

Прежде, чем приступать к программированию на данном интерфейсе, рассмотрим общие характеристики веб - серверов:
- в простейшем случае используют http - протокол и зарезервированный порт - 80, 8080;
- сервер принимает запросы от браузеров и отправляет им ответы в строго определенном виде. Определение запросов и ответов представлены в специальных документах, так называемым - Internet Request For Comment (RFC), в частности - RFC 2086. Рассмотрим пример диалогов HTTP (более подробно можно ознакомиться на Wiki).

Запрос клиента:
GET /wiki/страница HTTP/1.1
Host: ru.wikipedia.org
User-Agent: Mozilla/5.0 (X11; U; Linux i686; ru; rv:1.9b5) Gecko/2008050509
Firefox/3.0b5
Accept: text/html
Connection: close
(пустая строка) 
 
Ответ сервера:
HTTP/1.1 200 OK
Date: Wed, 11 Feb 2009 11:20:59 GMT
Server: Apache
X-Powered-By: PHP/5.2.4-2ubuntu5wm1
Last-Modified: Wed, 11 Feb 2009 11:20:59 GMT
Content-Language: ru
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Connection: close

(далее следует запрошенная страница в HTML) 
 
Отсюда видна структурированность HTTP диалога между клиентами и веб - серверами.
Так как мы рассматриваем разработку серверной стороны, стоит более внимательно
посмотреть на процесс формирования ответа сервера на сторонние запросы. Для
этого приведем схему;
 
 
 
 
 
 





- GET и POST методы передачи данных на сервер;
- скриптовая часть. Для этого организовываются внешние потоки, в которых происходит инициализация внешних скриптов на основе cgi, php, perl и даже bat - файлов;


C/C++. Сетевое программирование. Windows Socket

Что такое Winsock? В свое время для разработки сетевых приложений на операционных системах Berkeley Software Distribution (BSD) UNIX была разработана парадигма сетевых сокетов (Berkeley socket). Сетевой сокет - программный интерфейс обеспечивающий информационный обмен между сетевыми процессами. Данная парадигма  также была подхвачена разработчиками операционной системы Windows компании Microsoft. Так был разработан прикладной интерфейс для сетевого программирования - Windows Sockets 1.1 и в настоящее время - Windows Sockets 2.

Для ознакомления с данным интерфейсом можно воспользоваться технической библиотекой компании Microsoft - msdn Windows Socket 2, либо скачать в off-line режиме отличный (на мой взгляд) FAQ, либо в on-line -  http://tangentsoft.net/wskfaq/. Данный FAQ на английском. Здесь представлены примеры реализации данного интерфейса и есть ответы на различные интересные вопросы.

PHP on Windows. Работа с изображениями

Go to http://www.php.net/downloads.php and download the 'PHP 5.x.x zip package' if you don't allready have it. This package contains the GD-Library for windows. Find php_gd2.dll (or php_gd.dll if you need gif-support, but you will have less good image quality) and put it in your php folder (Usual c:\php). Find in the php.ini in your windows folder:

;extension=php_gd.dll
;extension=php_gd2.dll


Uncomment extension="php_gd2".dll by deleting the ';' in front of it.

Combination Html, Javascript and C/C++. Standalone dinamic application









Мысль. Создание "десктоповых" приложений с помощью сочетания Html, Javascript и C/C++.
Идея изображена выше. Зачем?  

Html - инструмент создания графического пользовательского инструмента (Graphical user interface - GUI).

Javascript - придание динамичности интерфейсу и для взаимодействия с окружением (Ajax).

C/C++ - разработка модулей окружения (функциональная начинка) на основе сетевых сокетов, для операционных систем семейства NT - WinSock.

К тому же такая среда разработки как Visual C++ 2008 Express Edition имеет компонент (Toolbox) - WebBrowser, который может "обернуть" html и javascript файлы в исполняемый exe-файл, а модули внедрить в компонент WebBrowser.  

Кроссдоменный AJAX. In action

В последнее время никак не мог разгадать одну загадку, касающуюся объекта XMLHttpRequest, ну или - XHR. В моем случае на локальном диске располагалась html - страничка, которая при определенном событии (нажатии кнопочки Send) создавала "фоновый" запрос к веб-серверу. Для этого использован асинхронный Javascript (AJAX). Также данная страничка была размещена на веб - сервере в соответствующей корневой директории.

Для тестирования воспользовался нетрадиционным веб - сервером автора Максима Калабзина - Ascet HTTPd (http://ascethttpd.ruall.org/). Связано это с тем, что для меня важно было видеть весь код, дабы при случае можно было самому отлавливать те или иные баги. На текущий момент доступна версия Ascet HTTPd v.1.4. Она написана на C, из скриптовой поддержки - cgi, хотя при желании можно прикрутить и покруче функционал. Развернул данный веб - сервер на виртаулке (Linux Ubuntu Server v.12.04 32bit).

Загадка состояла в том, что  "фоновый" запрос к веб-серверу в свойствах объекта XMLHttpRequest - responseText и responseXML, в Internet Explorer (v.8) возвращались с ожидаемым содержимым, в то время как FireFox (v.11) возвращал либо ничего, либо null. Так происходило только если данный запрос отправлялся с локальной странички. Если же обращение происходило непосредственно к веб - серверу, который подгружал туже страницу, то запрос возвращал в обоих случаях все так, как и полагалось.
Сравним относительные адреса этих страниц:
- локальное расположение file:///C:/index.htm
- на веб - сервере http://127.0.0.1/index.htm
Отсюда видно, что они в своем роде - сайты с разных доменов. Это я клоню к так называемой безопасности браузера FireFox - "Same Origin Policy" (Политика одного источника), то есть каждому сайту своя песочница, чтобы свести к минимуму их взаимодействие. Например об этом написано на сайте Mozilla Developer Network:

Cross-site HTTP requests initiated from within scripts have been subject to well-known restrictions, for well-understood security reasons.  For example HTTP Requests made using the XMLHttpRequest object were subject to the same-origin policy.  In particular, this meant that a web application using XMLHttpRequest could only make HTTP requests to the domain it was loaded from, and not to other domains.  Developers expressed the desire to safely evolve capabilities such as XMLHttpRequest to make cross-site requests, for better, safer mash-ups within web applications.

К тому же каждый запрос анализировался (громко сказано) с помощью плагина FireFox - LiveHTTPHeaders v.017 (http://livehttpheaders.mozdev.org/). Откуда было замечено, что браузер отправляет в сторону веб - сервера http - заголовок "Origin". Который указывает на источник запроса, то есть место, откуда посылается запрос. В случае с локальной страницей - Origin указывал на null. В случае веб - сервера - http://127.0.0.1/index.htm
И снова мысли наводили на "Same Origin Policy". Все точки над "и" расставила статья на хабре - "Кроссдоменный AJAX" (http://habrahabr.ru/post/114432/). Вот ее текст:

На вопрос, как сделать AJAX запрос к другому домену, я всегда отвечал, что никак, и предлагал в качестве альтернативы jsonp, прокси, флеш, фреймы. Но, оказывается, большинство современных браузеров (IE8+, FF3.5+, Chrome 6+ и Safari 4+) вполне поддерживает кроссдоменный XMLHTTPRequest.

С клиентской стороны все остается без изменений. Только теперь браузер не блокирует запрос при отправке, а добавляет к нему заголовок с именем домена, откуда делается запрос: Origin: example.com

Ответ от сервера он так просто назад не пропускает, сервер должен добавить специальный заголовок:
Access-Control-Allow-Origin: *
Вместо звездочки сервер может указать конкретный домен, которому разрешено получить ответ.

Вот такое элегантное решение. Без лишних HTTP запросов, без изменения клиентского API, без нарушения безопасности существующих приложений (ведь подгрузить картинку с другого домена или отправить форму в ифрейм можно было и раньше), и наконец, в отличие от Флеша, с гибкой настройкой прав доступа к индивидуальным страницам.


Как только в ответы веб - сервера было заложено то, что здесь рекомендовано: добавлен специальный заголовок:

Access-Control-Allow-Origin: *

FireFox отобразил то, чего так долго не мог от него добиться. Действительно - элегантное решение. Как следствие данный подход можно реализовать на уровне скриптов.
Например, - в cgi скрипте:

// Some code
printf("Access-Control-Allow-Origin: *\r\n");
printf("\r\n");
// Some code

- в php: 

 // Some code
header("Access-Control-Allow-Origin: *");
// Some code

   
Под конец приведу код той самой html - страницы, которую то и дело тропошил во время решения данной головоломки. Удачи нам в этом нелегком деле :)

<html>
    <head>
        <title>Testing web page...</title>
        <script language="javascript" type="text/javascript">
            function createRequest() {
                var request = null;
                try {
                    // Mozilla, FireFox, Safari, Opera и последние версии IE
                    request = new XMLHttpRequest();
                }     catch (trymicrosoft) {
                    try {
                        // большинство IE
                        request = new ActiveXObject("Msxml2.XMLHTTP");
                    }    catch (othermicrosoft) {
                        try {
                            // для некоторых IE
                            request = new ActiveXObject("Microsoft.XMLHTTP");
                        }    catch (failed) {
                            request = null;
                            }
                        }
                    }  
                // Проверяем на успешность создание объекта request
                if (request == null) {
                    alert("Error creating request object!");
                } else {
                    return request;
                }
            }
      
            function getResponse() {
                var myrequest = createRequest();
                // URL сценарий на сервере
                var url = "http://127.0.0.1";
                url = url + "?dummy=" + new Date().getTime();
                myrequest.open("GET", url, true);
                myrequest.send(null);
                myrequest.onreadystatechange = catchIT(myrequest);              
            }
      
            function catchIT(myrequest) {
              
                if(myrequest.readyState == 0) {
                    alert("0");
                }
                if(myrequest.readyState == 1) {
                    alert("1");
                }
                 if(myrequest.readyState == 2) {
                    alert("2");
                }
                 if(myrequest.readyState == 3) {
                    alert("3");
                }
                if(myrequest.readyState == 4) {
                    alert(myrequest.responseText);
                }              
            }
        </script>
    </head>
    <body>
        <form method="GET" action="#">
            <p id="inname">Type your name:</p>
            <p><input type="text" size="10" name="usname" id="usname" /></p>
            <!-- getResponse() -->
            <p><input value="Send" type="button" onClick="getResponse();"/></p>          
        </form>
    </body>
</html>