Сайтостроительство

         

Cookie


Cookie - параметры, которые хранятся на стороне клиента. Перевода этого термина на русский язык пока нет. Дословный перевод - печенье. Произносится - куки. Параметр cookie представляют собой пару name=value. Всякий раз, когда ваш броузер запрашивает документ с веб-сервера, он передает ему Cookie. На основание этих параметров броузер может получить тот или иной HTML-документ. Как использовать Cookie на стороне веб-сервера, будет рассказано в следующих частях книги. Здесь пойдет речь о работе с Cookie в JavaScript. Пока вы должны понять, что такое Cookie, и что эти параметры доступны, как на стороне клиента, так и на стороне веб-сервера. Соответственно добавлять и удалять эти параметры может и сервер, и клиент.

Эти пары можно получить из переменной JavaScript document.cookie. Cookie часто используются для хранения логина и пароля на стороне клиента. Также вы можете хранить и любую другую информацию о клиенте. Перед веб-сайтами с огромным количеством посещений, предоставляющих услуги массового потребления встает задача персонализации веб-сайта. Например, рассмотри поисковую систему типа aport.ru или rambler.ru. Веб-сайты такого рода предоставляют море информации. Помимо поиска там вы можете посмотреть погоду, курс доллара, новости и т.д. Теперь представьте, что у вас подключение к Интернет через телефонную линию со скоростью 1.5Кб в секунду и вам, совершенно, ни к чему все предоставляемые сервисы этой мощной поисковой системы. С помощью Cookie в вашем обозревателе Интернет, можно сохранить настройки, которые позволят сразу загрузить текстовую версию сайта вместо графической, не загружать вам новости и другую информацию, которая вам не нужна.

На основе Cookie построены многие Интернет-магазины, где пользователь сначала складывает товары в корзину, а потом выбирает, что именно он купит, и расплачивается. Информация о товарах в корзине хранится в Cookie. Например, чтобы просмотреть установленные Cookie в Internet Explorer выберите "Свойство обозревателя" в меню "Сервис", затем в разделе "Временные файлы Internet" нажмите кнопку "Настройка" и в появившемся диалоговом окне "Настройка" нажмите кнопку "Просмотр файлов".



Для того чтобы устанавливать, читать и удалять Cookie, я написал ряд функций на языке JavaScript.

<script language="javascript"> <!--

//возвращает количество параметров function GetCookieCount() { var len = document.cookie.length; var n=0;

for(var i=0;i<len;i++) { if(document.cookie.charAt(i) == '=') n++; }

return n; }

//возвращает значение параметра на основании его имени function GetCookieValueByName(name) { var beg = document.cookie.indexOf(name+"="); if(beg==-1) return false;

var end = document.cookie.indexOf(";", beg + name.length); if(end==-1) end = document.cookie.length;

return unescape(document.cookie.substring(beg + name.length + 1, end)); }

//возвращает значение i-го параметра function GetCookieValueByIndex(index) { var len = document.cookie.length; var i,n=-1;

for(i=0;i<len && n!=index;i++) { if(document.cookie.charAt(i) == '=') n++; }

var end = document.cookie.indexOf(";", i); if(end==-1) end = document.cookie.length;

return unescape(document.cookie.substring(i, end)); }

//возвращает имя i-го параметра function GetCookieNameByIndex(index) { var len = document.cookie.length; var i, n=-1;

for(i=0;i<len && n!=index-1;i++) { if(document.cookie.charAt(i) == ';') n++; }

var end = document.cookie.indexOf("=", i);

return unescape(document.cookie.substring(i, end)); }



//Устанавливает значение параметра. //name и value обязательные параметры этой функции, остальные //могут быть опущены. //Время жизни задается в переменной expires //expires задается в следующем виде Thu, 01-Jan-70 00:00:01 GMT //т.е. День недели, число-месяц-год часы:минуты:секунды //path задет префикс пути HTML-документов, в которых будет доступно //значение параметра name //domain задет доменное имя HTML-документов, в которых будет доступно //значение параметра name //secure - если этот параметр указан, то Cookie будут передаваться //только по защищенному протоколу HTTPS function SetCookie(name, value, expires, path, domain, secure) { document.cookie = name + "=" + escape(value) + ((expires) ? "; expires=" + expires.toGMTString() : "") + ((path) ? "; path=" + path : "") + ((domain) ? "; domain=" + domain : "") + ((secure) ? "; secure" : ""); }



// все тоже самое, только время жизни параметра //задается не как абсолютная величина, а как относительная //в неделях относительно текущей даты function SetCookieForNWeeks(name, value, weeks, path, domain, secure) { var today = new Date(); expires = new Date(today.getTime() + weeks*7*24*60*60*1000); document.cookie = name + "=" + escape(value) + "; expires=" + expires.toGMTString() + ((path) ? "; path=" + path : "") + ((domain) ? "; domain=" + domain : "") + ((secure) ? "; secure" : ""); }

//соответственно в днях function SetCookieForNDays(name, value, days, path, domain, secure) { var today = new Date(); expires = new Date(today.getTime() + days*24*60*60*1000); document.cookie = name + "=" + escape(value) + "; expires=" + expires.toGMTString() + ((path) ? "; path=" + path : "") + ((domain) ? "; domain=" + domain : "") + ((secure) ? "; secure" : ""); }

//соответственно в часах function SetCookieForNHours(name, value, hours, path, domain, secure) { var today = new Date(); expires = new Date(today.getTime() + hours*60*60*1000); document.cookie = name + "=" + escape(value) + "; expires=" + expires.toGMTString() + ((path) ? "; path=" + path : "") + ((domain) ? "; domain=" + domain : "") + ((secure) ? "; secure" : ""); }

//в минутах function SetCookieForNMinuts(name, value, minuts, path, domain, secure) { var today = new Date(); expires = new Date(today.getTime() + minuts*60*1000); document.cookie = name + "=" + escape(value) + "; expires=" + expires.toGMTString() + ((path) ? "; path=" + path : "") + ((domain) ? "; domain=" + domain : "") + ((secure) ? "; secure" : ""); }

//и, наконец, в секундах function SetCookieForNSeconds(name, value, seconds, path, domain, secure) { var today = new Date(); expires = new Date(today.getTime() + seconds*1000); document.cookie = name + "=" + escape(value) + "; expires=" + expires.toGMTString() + ((path) ? "; path=" + path : "") + ((domain) ? "; domain=" + domain : "") + ((secure) ? "; secure" : ""); }



//удаляет параметр Cookie, //установив ему время жизни 1 Января 1970 года function DeleteCookie(name, path, domain) { if(GetCookieByName(name)) document.cookie = name + "=" + ((path) ? "; path=" + path : "") + ((domain) ? "; domain=" + domain : "") + "; expires=Thu, 01-Jan-70 00:00:01 GMT"; } //--> </script> Если параметр expires не указан, то cookie хранится в течение одного сеанса, до закрытия броузера. Параметр domain определяет доменное имя веб-сервера, для документов которого будут доступны Cookie. Например, если указано itsoft.ru, то значит всякий раз при запросе документа с сайта itsoft.ru броузер будет передавать на сервер соответствующий параметр Cookie. Если этот параметр не задан, то по умолчанию используется доменное имя сервера, с которого был загружен HTML-документ. Параметр path определяет путь на веб-сервере для HTML-документов, дл которых будут посылаться параметры Cookie. Например, если указано "/guest", то соответствующий параметр будет передан для всех HTML-документов, путь к которым подпадает под маску /guest*, т.е. для /guestbook/index.html, /guestbook/post.html, /guestbook/old/index.html, /guests.html и т.д. Если этот параметр не указан, то значение cookie распространяется только на документы в той же директории, что и документ, в котором было установлено значение cookie. Далее приведена таблица с параметрами Cookie, которые доступны в данный момент и форма для установки новых параметров параметров. После установки нового параметра Cookie, HTML-документ перегружается, чтобы обновилось содержание таблицы.
Параметр Значение
name
value
life time in days OR in seconds
path
domain

Введите имя и значение какого-нибудь параметра и установите его сроком на 30 секунд, после нажатия кнопки "Установить Cookie на энное кол-во секунд" страница перезагрузится, и в таблице вы увидите свой параметр. Через 30 секунд перезагрузите эту страницу, и вы увидите, что параметр исчез.

Ниже приведен код таблицы и HTML-формы для установки параметров Cookie.

<table align="center" width="300" border="0" cellspacing="1" cellpadding="0" bgcolor="#000000"> <tr bgcolor="#CCCCFF" align="center"> <td> Параметр<td> Значение

<script language="javascript"> <!-- function MakeArray(n) { for(var i=0;i<n;i++) this[i] = 0;

return this; }

var i,n=GetCookieCount(); c = new MakeArray(2); c[0]="\"#EEEEEE\""; c[1]="\"#CCCCCC\"";

for(i=0;i<n;i++) document.writeln( "<tr bgcolor=" + c[i%2] + "> <td>" + GetCookieNameByIndex(i) + "<td>" + GetCookieValueByIndex(i) ); --> </script> </table>

<form name="SetCookieForm"> <table> <tr><td> name<td> <input type="text" name="name" size=40> <tr><td> value<td> <input type="text" name="value" size=40> <tr><td> life time in<td> <input type="text" name="days" size=5> days OR in <input type="text" name="seconds" size=5> seconds <tr><td> path<td> <input type="text" name="path" value="/docs/web" size=40> <tr><td> domain<td> <input type="text" name="domain" value="itsoft.ru" size=40> </table> <input type="button" value="Установить Cookie на энное кол-во дней" onClick="SetCookieForNDays( SetCookieForm.name.value, SetCookieForm.value.value, SetCookieForm.days.value, SetCookieForm.path.value, SetCookieForm.domain.value); window.location.href=window.location.href;"> <br><br> <input type="button" value="Установить Cookie на энное кол-во секунд" onClick="SetCookieForNSeconds( SetCookieForm.name.value, SetCookieForm.value.value, SetCookieForm.seconds.value, SetCookieForm.path.value, SetCookieForm.domain.value); window.location.href=window.location.href;">

</form>


Информация о клиенте


Сбор информации о клиенте может пригодится для создания системы статистики или разработки веб-сайтов подстраивающихся под системные настройки пользователя. HTML-документы могут подстраиваться под разрешение экрана и версию обозревателя Интернет пользователя. Вот пример сбора основной информации.

<script language="javascript"> <!-- document.cookie="testparam=testvalue"; document.write( 'Название обозревателя Интернет: ' + navigator.appName + "<br>" + 'Версия обозревателя Интернет: ' + navigator.appVersion + "<br>" + 'Cookies поддерживаются: ' + (document.cookie?"Да":"Нет") + "<br>" + 'Страница с фреймом: ' + ((self!=top)?"Да":"Нет") + "<br>" + 'Java applets поддерживаются: ' + (navigator.javaEnabled()?"Да":"Нет") + "<br>" + 'Разрешение экрана: ' + screen.width + "x" + screen.height + "x" + (screen.colorDepth?screen.colorDepth:screen.pixelDepth) + "<br>" + 'Платформа: ' + navigator.platform + "<br>"); //--> </script>



JavaScript и HTML-формы


Начнем с самого простого - подтверждения отправки HTML-формы. Для совместного использования JavaScript и HTML-формы надо задать параметр name команды form, чтобы из JavaScript можно было обращаться к объектам HTML-формы.

<form method="POST" action="/cgi-bin/gb_show.exe" name="testform"> ... <input type="button" value="Отправить данные формы" onClick="if(confirm('Вы уверены?')){testform.submit();}"> </form>

Обратите внимание, что кнопка "Отправить данные формы" задается посредством type=buttom, а не type=submit, т.к. отправка формы будет происходить не всегда, а в зависимости от подтверждения пользователя. Ниже можете нажать на кнопку и посмотреть результат действия HTML-скрипта. При ответе ДА, данные формы отправятся на сервер, при ответе НЕТ, ничего не произойдет. Такое подтверждение полезно при удалении или редактировании каких-либо данных. Мы использовали такие HTML-формы при создании администраторского доступа к форуму и гостевой книге, где имелась возможность редактировать и удалять записи.

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

<script language="JavaScript"> <!-- function checkInt(form, input, min, max) { var i = eval(form + "." + input + ".value");

if(i>=min && i<=max && i.indexOf(".")==-1) return true; else { void(0); + min + " до " + max + " !"); eval(form + "." + input + ".select()"); eval(form + "." + input + " "); return false; } } --> </script>

Введите целое от -5 до 10 <input type="text" name="int510" size=40 onBlur="checkInt('testform','int510',-5,10);">

При попытке переключиться на другой элемент формы будет вызвана функция JavaScript checkInt. На вход этой функции передается четыре параметра: имя формы, имя объекта ввода данных, минимальное и максимальное значение.



Навигация по сайту


Следующим полезным примером использования JavaScript будет навигация по сайту.

<select onchange="if (this.options[this.selectedIndex].value == '') this.selectedIndex=0; else window.open(this.options[this.selectedIndex].value,'_top')"> <option value="">выберите главу...</option> <option value="">____________________</option> <option value="chapter1.html">Глава 1</option> <option value="chapter2.html">Глава 2</option> <option value="chapter3.html">Глава 3</option> <option value="chapter4.html">Глава 4</option> <option value="chapter5.html">Глава 5</option> <option value="chapter6.html">Глава 6</option> <option value="chapter7.html">Глава 7</option> <option value="chapter8.html">Глава 8</option> <option value="chapter9.html">Глава 9</option> </select>

После выбора в списке нужной главы, она тут же загрузится в текущее окно вашего обозревателя Интернет.

выберите главу...

____________________

Глава 1

Глава 2

Глава 3

Глава 4

Глава 5

Глава 6

Глава 7

Глава 8

Глава 9



Доработайте этот код, чтобы можно


Доработайте этот код, чтобы можно было редактировать и удалять параметры Cookie.


Конфигурирование веб-сервера Apache


Установка веб-сервера Apache не вызывает больших затруднений как под Windows, так и под Unix. Все вопросы при установки не выходят за рамки знаний обычного пользователя. Наиболее сложным моментом является конфигурирование и подключение дополнительных модулей. Предполагая, что наибольшее число читателей будут конфигурировать сначала веб-сервер на своей локальной машине, скорее всего под управлением Windows, то начнем рассмотрение конфигурации веб-сервера Apache именно с этого варианта.

Первое, что вам нужно проверить, это наличие протокола TCP/IP, и так называемой, обратной петли. В командной строке дайте команду route print. Результат ее должен начинаться со следующих строк:

C:\>route print

Активные маршруты:

Сетевой адрес Маска Адрес шлюза Интерфейс Метрика 127.0.0.0 255.0.0.0 127.0.0.1 127.0.0.1 1

Сейчас, наверное, уже сложно представить себе вариант отсутствия обратной петли, т.к. большинство компьютеров имеют выход в Интернет. Однако, года четыре назад я столкнулся с такой проблемой, когда начинал осваивать MS SQL Server 6.5. Если все же вы не получили должного результата команды route print, добавьте протокол TCP/IP. Для этого на рабочем столе щелкните правой кнопки мыши на иконке "Сетевое окружение" и выберете меню "свойства". Далее кнопку "добавить".

По умолчанию, конфигурационный файл Apache httpd.conf лежит в папке C:\Program Files\Apache Group\Apache\conf. При запуске веб-сервера на локальной машине под Windows мне пришлось прописать в файл httpd.conf строчку ServerName localhost. Без этой строки он не запускался. В юникс такая строчка не требуется. Запускать и останавливать веб-сервер надо при помощи иконок лежащих в Пуск->Программы->Apache Web Server. Теперь запускайте веб-сервер, щелкните в меню Пуск->Программы->Apache Web Server->Start Apache. Запустите обозреватель Internet Explorer и введите http://127.0.0.1/. Вы увидите ответ веб-сервера. У меня на экране появилась страничка следующего содержания:



Not Acceptable


An appropriate representation of the requested resource / could not be found on this server.

Available variants:

, type text/html, language ca , type text/html, language cz , type text/html, language de , type text/html, language da , type text/html, language et , type text/html, language en , type text/html, language es , type text/html, language fr , type text/html, language it , type text/html, language ltz , type text/html, language nl , type text/html, language pt , type text/html, language sv

Apache/1.3.12 Server at localhost Port 80

Щелкните на ссылку index.html.en и вы попадете на главную страницу веб-сервера, с которой можно попасть на документацию по веб-серверу - ссылка documentation (http://127.0.0.1/manual/index.html).

Теперь переходим к работе с виртуальными хостами. Веб-сервер Apache, IIS или любой другой обслуживает не один веб-сайт, а несколько десятков. Каждый из них имеет свое символическое имя, но все они указывают на один и тот же IP-адрес в базе данных DNS. На локальной машине мы не будем развертывать DNS-сервер, чтобы завести несколько веб-сайтов. Настройка DNS-сервера выходит за рамки данной книги. В операционных системах Unix и Windows есть, так называемая, локальная база DNS - это файл hosts. В Unix он лежит в каталоге /etc, в Windows - c:\windows, в WinNT - c:\WINNT\system32\drivers\etc. Любое приложение, прежде чем соединится с узлом, вызывает системную функцию gethostbyname, которая возвращает IP-адрес. Система, прежде чем обращаться к DNS-серверу просматривает файл hosts на наличие в нем данного имени хоста. Формат этого файла предельно простой.

# IP-адрес имя хоста 127.0.0.1 localhost 127.0.0.1 it.ru

Таким образом вы можете передопределить для программ своей машины и IP-адрес microsoft.com. Но это переопределение будет действовать только для вашей машины, т.к. бругие компьютеры будут получать IP-адрес microsoft.com с DNS-серверов.

Добавьте в свой файл hosts вторую строчку из приведенного примера. И затем, в конфигурационный файл Apache httpd.conf добавьте следующее строки:

NameVirtualHost 127.0.0.1

<VirtualHost it.ru> ServerAdmin igor@itsoft.ru DocumentRoot c:\projects\www\itsoft ServerName it.ru ErrorLog c:\projects\www\itsoft\logs\error.log CustomLog c:\projects\www\itsoft\logs\custom.log combined ScriptAlias /cgi-bin "c:\projects\www\itsoft\cgi-bin" </VirtualHost>

NameVirtualHost задает IP-адрес, на котором будут висеть веб-узлы. Этих директив может быть несколько. Теоретически, в сервере имеется несколько сетевых карт, и у каждой сетевой карты может быть несколько IP-адресов. Чаще всего у вас будет одна карточка с реальным IP-адресом и одна или две сетевых карты с виртуальным IP-адресом локальной сети, т.к. ваш сервер еще будет обеспечивать доступ компьютеров локальной сети к Интернет. В директиве VirtualHost должен стоять IP-адрес, но можно также указать и имя хоста, указывающего на этот IP. В нашем случае, это имя хоста it.ru, которое указывает на 127.0.0.1. ServerAdmin содержит адрес электронной почты, который будет выдаваться клиентам при возникновение каких-либо ошибок, например, при аварийном заверении CGI-программы. DocumentRoot задает путь к корню веб-сервера. ServerName содержит имя веб-сервера. Далее идут пути к логам веб-сервера. Стого рекомендую размещать логи в корне самого веб-сайта, а не где-нибудь еще. В реальной жизни вы вряд ли будете иметь доступ ко всему жесткому диску сервера. У веб-мастера будет доступ по ftp только к содержимому папки веб-сайта. Так что, если логи будут не внутри папки веб-сайта, то их не сможет просматривать веб-мастер. И наконец, ScriptAlias /cgi-bin "c:\projects\www\itsoft\cgi-bin" задает папку, где будут лежать исполняемые файлы. Имя этой папки не обязательно cgi-bin, но желательно придерживаться исторических традиций. Поясню более детально, что это за каталог. По умолчанию, при запросе любого файла с веб-сервера, он тут же начнется скачиваться клиенту. При запросе же исполняемого файла из папки cgi-bin, этот файл сначала будет запущен веб-сервером, а потом клиенту будет передано то, что напечатает этот исполняемый файл на стандартный поток вывода( STDOUT ). Обратите внимание, что при запросе исполняемого файла из папки, не прописанной в httpd.conf как ScriptAlias /cgi-bin "PathToFolder", вы получите сам исполняемый файл, а не результат его работы. Особо будьте бдительны, если вы будете использовать не бинарные исполняемые файлы, а скриптовые исполняемые файлы. Злоумышленник, заполучив их, сможет прочитать и найти дыру в вашем веб-узле. Невинная гостевая книга или отправка почтового сообщения могут привести не только к неправильной работе этих скриптов и падению веб-узла, но и падению\зависанию всего сервера со всеми размещенными на нем веб-узлами.

Далее создайте папку по адресу c:\projects\www\ с именем itsoft. Путь и имя лучше переправьте. Так вы натолкнетесь на ошибки, связанные с неправильным путем и лучше усвоите материал, исправляя их. В папке itsoft создайте файл index.html, поддиректорию logs с файлами error.log и custom.log. Теперь перезагрузите веб-сервер Apache. Наберите в броузере it.ru и вы увидите содержание index.html. Вот так вы создали свой первый веб-сайт. Теперь можете размещать на нем документы и скрипты. Далее вас ждет увлекательное путешествие по различным технологиям построения веб-сайтов.



Регистрация доменных имен


DNS (Domain Name System) - доменная система имен косвенно относится к созданию сайтов. Однако, как показывает мой опыт, с ней связано наибольшее количество вопросов и проблем у менеджеров Интернет-проектов. DNS - распределенная база данных, основной функцией которой является преобразование имен компьютеров в IP-адреса и наоборот. Далее будет рассказано о том, как зарегистрировать доменное имя, сколько будет стоить поддержание сайта в год, и чего следует опасаться.

В сети Интернет все сервера имеют IP-адреса, например 194.226.32.34. Связь и маршрутизация пакетов в сети осуществляется на основе IP-адресов. Люди разговаривают словами, а машины - цифрами. Когда вы набираете в своем обозревателе имя сайта, допустим www.freebsd.org, то сначала ваш обозреватель обращается к ближайшему DNS-серверу с запросом об IP-адресе сервера с именем www.freebsd.org. Этот DNS-сервер смотрит в своей базе данных, если он находит такое символическое имя, то возвращает ответ с IP-адресом , иначе он отправляет запрос другим DNS-серверам. Так происходит до тех пор, пока не найдется DNS-сервер с данными о требуемом домене или выяснится, что такого домена не существует. DNS позволяет вам не зависеть от конкретного IP-адреса. Допустим, вы сменили провайдера, и ваш веб-сайт переехал на другой IP-адрес. Доменное имя у вас сохранилось, вам надо только обновить информацию в первичном DNS-сервере, который обслуживает ваш домен.

Самое первое, что вам необходимо сделать перед появлением на свет вашего сайта - зарегистрировать его имя. Регистрация доменов второго уровня (ваш_сервер.ru) в зоне ru стоит 36$. Для этого вам необходимо сначала зарегистрироваться в базе данных . Для регистрации доменов в других зонах обращайтесь на сайт Вы можете зарегистрироваться как физическое, так и юридическое лицо. Юридическое лицо, т.е. организация, может регистрировать домены в зонах org.ru, net.ru, com.ru, причем эта услуга бесплатная. Физические лица не могут регистрировать домены в этих зонах. Получить всю необходимую информацию, а также занести себя в базу данных РосНИИРОС можно на сайте . Следующим шагом после регистрации в базе данных будет заключение договора. Текст договора доступен в двух вариантах (WORD HTML) на сайте . Распечатав и заполнив этот документ в двух экземплярах, вы должны подъехать к ним для заключения договора или прислать его по почте.

После заключения договора вы можете регистрировать домены. Для регистрации вам необходимо отправить заявку в РосНИИРОС. Но прежде вам надо проверить свободен ли домен. Это можно сделать в UNIX командой: whois -h whois.ripn.net your_domain.ru или же можно получить эту же информацию на страничке . У них на сайте хороший веб-интерфейс, который позволяет регистрировать домены в online. Квитанцию на оплату можно распечатать там же и сразу оплатить в любом отделение сбербанка. После того, как вы оплатили домен второго уровня, он будет зарезервирован за вами сроком на один год. Каждый год надо оплачивать перерегистрацию.



Следующим шагом будет поиск площадки для размещения вашего сайта. Эта услуга провайдера называется хостингом. Стоимость ее колеблется от 6 до 50 долларов в месяц. Все зависит от объема вашего сайта, наличия базы данных, количества почтовых ящиков и других атрибутов. Дисковое пространство постоянно дешевеет. На сегодняшний день разместить сайт с базой данных и объемом до 50Мб можно за $10/месяц. Выбрав провайдера и оплатив хостинг, вам предстоит установить в соответствие вашему доменному имени IP-адрес провайдера.



РосНИИРОС делегирует доменное имя(зону) после успешного тестирования двух DNS-серверов, в которых имеются записи о вашем домене. Теперь более подробно о том, что такое делегирование, и чем оно отличается от регистрации домена. Дело в том, что купить домен может любой желающий, имеющий достаточное количество денег. Вам даже не потребуется ничего заполнять, можете просто приехать в организацию, предоставляющую Интернет-услуги, и за дополнительные деньги они заполнят за вас все документы. Владельцем домена будете вы. Но реально ему никакой сервер в Интернет не соответствует. После делегирования вашему домену будет соответствовать реальный сервер в Интернет. Если вас не устроит провайдер, то вы всегда можете сменить DNS-сервера на нового провайдера. Управлять своим доменом вы можете через веб-интерфейс на сайте http://www.ripn.ru, отправляя письма по электронной почте или по факсу. Регистрация - это получение прав на владение конкретным доменом. Делегирование - это связывание символьного имени домена с конкретным IP-адресом.

Два DNS-сервера требуется на случай, если один выйдет из строя. Один из этих DNS-серверов является первичным. У него хранится и редактируется информация, касающаяся вашего домена и поддоменов. Вторичный сервер просто дублирует информацию первичного DNS-сервера, регулярно связываясь с ним и скачивая к себе записи о вашем домене. Тем самым, для того чтобы изменить IP-адрес, вам надо исправить информацию в базе данных первичного DNS-сервера. В большинстве случаев эти два DNS-сервера находятся у вашего провайдера. Просто попросите его внести информацию о вашем домене в его DNS-сервера и отправить запрос в РосНИИРОС о делегировании домена. На самом деле, ничего сложного в DNS-серверах нет, однако провайдеры берут за эту услугу деньги, и немалые. Так, например, размещение информации в двух DNS-серверах РосНИИРОС вам обойдется в 70 долларов сроком на один год.



Теперь окончательно подсчитаем расходы на поддержание вашего веб-сайта. Услуги хостинга 10$/месяц, в год это $120. Регистрация домена - $36(в РосНИИРОС), если будете сами регистрировать. Если кому-нибудь поручите, то еще $14 - накладные расходы. Особое внимание обратите на правильность регистрации. Владельцем домена должны быть именно вы. Существует целый ряд жуликов, которые специализируются на перепродаже доменных имен. Не забывайте каждый год вовремя оплачивать перерегистрацию домена в РосНИИРОС, иначе у вас уведут доменное имя и предложат вам его купить снова, но уже не за $36, а за $36К. Цена зависит от раскрученности вашего сайта. Поддержка двух DNS-серверов вам обойдется от $25 до $70. Итого содержание сайта вам обойдется порядка $160-$200 в год.

Теперь о безопасности, или на что стоит обратить особое внимание. Есть проблема перехвата и перепродажи доменных имен. Например, вы являетесь представителем какой-нибудь очень солидной фирмы в России и хотите зарегистрировать домен firma.ru. Про это узнает спекулянт и регистрирует домен на свое имя, а потом предлагает вам его выкупить за очень большие деньги. Может быть и другая ситуация: вы попросили своего Интернет-провайдера зарегистрировать вам домен. Он зарегистрировал, но на свое имя. В последствии, он разорился, или вы от него хотите перейти к другому провайдеру. Но опять получилось, что домен зарегистрирован не на вас и вам не пренадлежит. Есть еще один очень распространенный миф по поводу WWW. Владеть можно только зоной, т.е. доменом второго уровня - zona.ru. Все поддомены третьего и более низких уровней, т.е. любые слова слева после второй точки, принадлежат владельцу зоны. Например, comp1.zona.ru, igor.zona.ru, test.zona.ru & www.zona.ru - это домены третьего уровня, и все они будут принадлежать владельцу домена(зоны) zona.ru. Обратите ОСОБОЕ ВНИМАНИЕ на то, что www.zona.ru это не одно и тоже, что zona.ru. www.zona.ru - это такой же домен третьего уровня, как и test.zona.ru, а zona.ru - это домен второго уровня. Будьте бдительны: есть продавцы воздуха, которые с удовольствием продадут вам www.domain.ru, хотя никакого смысла это иметь не будет. Домен третьего уровня сложился исторически. DNS появилась за долго до World Wide Web (Всемирная паутина). Были и есть компьютеры с именем ftp.zona.ru. В этом случае, файловый сервер находится на машине ftp.zona.ru, а веб-сервер на машине www.zona.ru. В настоящее время уже пошла тенденция отхода от www. Я рекомендую делать автоматическое перенаправление с веб-сайта www.zona.ru на просто zona.ru.

И последнее о чем здесь необходимо сказать - как выбирать доменное имя. Привыборе доменного имени ваша цель выбрать такое имя, которое легче будет запомнить пользователям. Для этого придерживайтесь следующих правил:

Иметь однозначное произношение и написание, чтобы избежать ошибок в написании. Имя должно быть осмысленным, т.е. вызывать ассоциации с вашим проектом. По возможности коротким.

Эти правила перечислены в порядке их значимости. Если имя допускает разлиное произношение и его можно написать с ошибкой, то это плохое имя. Имеет смысл регистрировать несколько доменных имен близких по написанию. Например, если в произношении есть звуки, которые могут быть двояко написаны - ph(f), k(c), i(e), a(o) и другие, то уместно зарезервировать и запасное имя, если позволяют средства. С точки зрения второго правила имена типа Rambler, Yandex, Aport плохие, т.к. ровно ни о чем не говорят. Поисковая машина с именем poisk или даже search запоминалась бы легче. Имена более семи символов плохо запоминаются и сильно растет вероятность сделать орфографическую ошибку.



Server Side Includes


Server Side Includes - включения на стороне сервера. SSI представляет механизм включения одних документов внутрь других. Чем-то это схоже с фреймами по функциональному назначению, но есть существенные отличия. Во-первых, включать в один HTML-документ можно любой файл с HTML-кодом, совершенно не обязательно, чтобы он(HTML-код) представлял собой законченный HTML-документ. Например, у вас часть таблицы может быть в одном файле, часть в другом, а еще часть в третьем. Во-вторых, включать можно программы, которые, в свою очередь, могут связываться с базами данных, вызывать внутри себя другие программы и выдавать какие-либо отчеты и таблицы. Естественно, что эти программы выдают на STDOUT( поток стандартного вывода, для не программистов это экран монитора с командной строкой ) HTML-код, который затем и включается внутрь нашего исходного HTML-документа. И в третьих, SSI поддерживают переменные и элементарные инструкции языка программирования: if, elif и else. Server Side Includes работают следующим образом. У вас на сервере находится html-файл с командами SSI.

<!--#include virtual="/cgi-bin/head.pl?name=О нас" --> <p align=justify> Веб-студия ITSoft состоит из молодых специалистов, работающих в области информационных технологий более пяти лет. Мы занимаемся веб-дизайном, разработкой программного обеспечения, предоставляем услуги хостинга и создания локальных сетей. <!--#include virtual="/include/footer.inc"-->

Когда броузер клиента запрашивает у веб-сервера этот документ, то веб-сервер, прежде чем отдать его броузеру проверяет его на наличие SSI-команд. Когда веб-сервер встретит <!--#include virtual="/cgi-bin/head.pl?name=О нас" --> он запустит программу /cgi-bin/head.pl и передаст ей параметр name=О нас. Программа head.pl выдаст HTML-код с заголовком для нашего HTML-документа, в котором будет содержаться меню и название раздела. О том, как писать такие программы вы узнаете в третей части книги. Когда веб-сервер натолкнется на <!--#include virtual="/include/footer.inc"-->, то он просто включит HTML-код из файла /include/footer.inc в наш HTML-документ.
Там, собственно, хранится код завершающий любую HTML-страницу нашего веб-сайта. Таким образом, SSI позволяет избежать избыточности. Общий код для всех HTML-страниц нашего сайта хранится в одном месте. Теперь, если нам захочется добавить пункт меню или поменять что-либо еще, то нам не придется бегать по всему сайту и изменять каждый файл отдельно. Достаточно будет внести изменения в один файл. Для того чтобы начать экспериментировать с SSI-командами надо внести изменения в файл httpd.conf и перезапустить веб-сервер Apache. Найдите и раскомментируйте следующие две строчки в файле httpd.conf. Они указывают веб-серверу, что файлы с расширением shtml надо обрабатывать прежде, чем выдавать пользователю.

AddType text/html .shtml AddHandler server-parsed .shtml

Если вы собираетесь повсеместно использовать SSI, то добавьте еще и такую строчку. У нас SSI-команды присутствуют в большинстве HTML-документов, поэтому на нашем веб-сервере такая строчка присутствует в файле httpd.conf

AddHandler server-parsed .html

HTML-код для включения в HTML-документы обычно хранится в файлах с расширением inc. Включите и для этих файлов обработку SSI-директив. Это позволит делать рекурсивную обработку SSI-директив. Если в файле footer.inc будет SSI-директива, то она будет обработана.

AddHandler server-parsed .inc

Еще может потребоваться добавить опцию Includes в следующий раздел.

<Directory /> Options FollowSymLinks Indexes MultiViews Includes AllowOverride All </Directory>

На нашем сервере под управлением FreeBSD такая опция стоит. У меня дома на Windows98 ее нет и SSI работают. Возможно тут дело в различных версиях Apache.

Теперь перезапустите веб-сервер. Давайте рассмотрим SSI еще на одном примере, заодно разберем одну из самых распространенных структур организации HTML-документов. На большинстве сайтов, документы состоят из трех частей: заголовок, тело и завершающая часть. Для эксперимента создайте директорию на вашем веб-сайте и назовите ее ssi. В этой директории создайте head.inc со следующим содержанием:



<html> <body>

<table bgcolor=#0000FF width=600 height=100> <tr><td> Здесь будет заголовок и главное меню нашего вебсайта </table>

<table width=600 height=200> <tr><td width=120 bgcolor=#CCCCCC> Левое меню<br> пункт1<br> пункт2<br> пункт3<br> пункт4<br> пункт5<br> <td>

Все SSI-директивы имеют следующую семантику <!--#команда параметр="значение" параметр="значение"--> Создайте index.html:

<!--#include virtual="head.inc"--> Основное содержание HTML-документа. <!--#include virtual="footer.inc"-->

И footer.inc:

</table>

<table bgcolor=#0000FF width=600> <tr><td> Здесь будет завершающая часть HTML-документа, обычно это реклама </table>

</body> </html>

В результате, при запросе этого документа, веб-сервер выдаст:

<html> <body>

<table bgcolor=#0000FF width=600 height=100> <tr><td> Здесь будет заголовок и главное меню нашего вебсайта </table>

<table width=600 height=200> <tr><td width=120 bgcolor=#CCCCCC> Левое меню<br> пункт1<br> пункт2<br> пункт3<br> пункт4<br> пункт5<br> <td>

Основное содержание HTML-документа. </table>

<table bgcolor=#0000FF width=600> <tr><td> Здесь будет завершающая часть HTML-документа, обычно это реклама </table>

</body> </html>

Это одно из самых основных применений SSI. Теперь давайте рассмотрим использование переменных и условных операторов. Команда <!--#printenv --> выводит все переменные окружения, которые доступны по умолчанию. Ниже приведен результат действия этой команды на моем домашнем компьютере.

COMSPEC=C:\WINDOWS\COMMAND.COM DOCUMENT_ROOT=c:/projects/www/web-tehnolog HTTP_ACCEPT=*/* HTTP_ACCEPT_ENCODING=gzip, deflate HTTP_ACCEPT_LANGUAGE=ru HTTP_CONNECTION=Keep-Alive HTTP_COOKIE=testparam=testvalue HTTP_HOST=web.ru HTTP_USER_AGENT=Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt) PATH=C:\Program Files\Apache Group\Apache;C:\WINDOWS;C:\WINDOWS\COMMAND; C:\ARCH;C:\JDK12\BIN;C:\PROGRA~1\ULTRAE~1 REMOTE_ADDR=127.0.0.1 REMOTE_PORT=1546 SCRIPT_FILENAME=c:/projects/www/web-tehnolog/chapter6.html SERVER_ADDR=127.0.0.1 SERVER_ADMIN=igor@itsoft.ru SERVER_NAME=web.ru SERVER_PORT=80 SERVER_SIGNATURE=Apache/1.3.12 Server at web.ru Port 80



SERVER_SOFTWARE=Apache/1.3.12 (Win32) WINDIR=C:\WINDOWS GATEWAY_INTERFACE=CGI/1.1 SERVER_PROTOCOL=HTTP/1.1 REQUEST_METHOD=GET QUERY_STRING= REQUEST_URI=/chapter6.html SCRIPT_NAME=/chapter6.html DATE_LOCAL=Monday, 26-Feb-2001 19:26:02 Московское время (зима) DATE_GMT=Monday, 26-Feb-2001 16:26:02 GMT LAST_MODIFIED=Monday, 26-Feb-2001 19:25:58 Московское время (зима) DOCUMENT_URI=/chapter6.html DOCUMENT_PATH_INFO= DOCUMENT_NAME=chapter6.html

Для того чтобы вывести значение отдельной переменной дайте команду <!--#echo var="var_name" -->. Например, <!--#echo var="LAST_MODIFIED" --> выдаст Monday, 26-Feb-2001 19:25:58 Московское время (зима).

Также вы можете определять и использовать свои собственные переменные. В файл index.html, который мы рассматривали выше, вставьте самой первой строкой <!--#set var="title" value="Заголовок нашего веб-сайта." -->. В head.inc добавьте следующую строчку <title><!--#echo var="title" --></title> после команды <html> и где идут слова "здесь будет заголовок" вставьте <h1><!--#echo var="title" --></h1>

В результате у вас должно получится для index.html

<!--#set var="title" value="Заголовок нашего веб-сайта." --> <!--#include virtual="head.inc"--> Основное содержание HTML-документа. <!--#include virtual="footer.inc"-->

и

<html> <title><!--#echo var="title" --></title> <body>

<table bgcolor=#0000FF width=600 height=100> <tr><td><h1><!--#echo var="title" --></h1> главное меню нашего веб-сайта </table>

<table width=600 height=200> <tr><td width=120 bgcolor=#CCCCCC> Левое меню<br> пункт1<br> пункт2<br> пункт3<br> пункт4<br> пункт5<br> <td>

Таким образом можно передавать параметры во включаемые куски HTML-кода.



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

Пример для графического меню с подсветкой:

<!--#if expr="$DOCUMENT_URI!=/\/about.html/" --><a href=/about.html onMouseOut="MM_swapImgRestore()" onMouseOver="MM_swapImage('about','','/about2.gif',0)" ><!--#endif --><img src=/about1.gif width=212 height=38 border=0 name=about ><!--#if expr="$DOCUMENT_URI!=/\/about.html/" --></a ><!--#endif -->

Данный SSI-код используется на сайте .

Другой не менее важный SSI-код применяется для организации версии страниц для печати. Версия страницы сайта для печати отличается от обычной страницы отсутствием колонститулов, навигации, баннеров и прочей лишней информации. Рассмотрим, как организована версия для печати на примере сайта .

===Файл /include/head.inc === <html> <head> <title><!--#echo var="title"--></title> <meta http-equiv="Content-Type" content="text/html; charset=windows-1251"> <link rel=stylesheet href=/styles.css> </head>

<body bgcolor=FFFFFF text=474C54 link=566A89 vlink=96A7C1 leftmargin=0 topmargin=0 marginwidth=0 marginheight=0> <basefont face=Arial>

<!--#if expr="$QUERY_STRING!=/for_printing/" --> ...здесь навигационный блок, графика, Flash-анимация и т.д. ...

<p > <a href=?for_printing=1&<!--#echo var="QUERY_STRING"-->> Версия для печати </a></p> <!--#endif -->

При нажатии на гиперссылку "версия для печати" страница, на которой находится пользователь, перегружается.


Но поскольку в строке запроса присутствует параметр for_printing, то лишний HTML-код не включается на страницу. Обратите внимание <!--#echo var="QUERY_STRING"-->. Это строка запроса CGI-параметров. В обычном случае, у вас никаких параметров, кроме for_printing нет, но когда появляются на сайте CGI-модули, то появляются и дополнительные параметры. Забегая вперед расскажу, как делать "Версию для печати" В CGI-программе. Пока веб-сервер Apache не может обрабатывать на SSI-директивы вывод CGI-программ. Такую возможность обещают в новых версиях Apache. У CGI-программистов есть два пути либо самим разобрать SSI-директивы в файле head.inc либо, что значительно проще, создать ручками файл phead.inc и подключать его в CGI-программах.

Условные операторы записываются следующим образом:

<!--#if expr="условие1" --> <!-- Здесь HTML-код1 --> <!--#elif expr="условие2" --> <!-- Здесь HTML-код2 --> <!--#else --> <!-- Здесь HTML-код3 --> <!--#endif -->

Для примера приведу SSI-код для вывода времени последней модификации документа в приемлемом виде. Как вы уже видели, команда <!--#echo var="LAST_MODIFIED" --> выдает совершенно не приемлемую строчку - Monday, 26-Feb-2001 19:25:58 Московское время (зима), для размещения ее на солидном веб-сайте. Нам бы хотелось получить что-нибудь, вроде 26 Февраля 2001 года. Для этого существует команда config с параметром timefmt, которая задает формат вывода даты. Например, в нашем случае нам требуется следующий вариант <!--#config timefmt="%e %B %Y"-->, тогда результат <!--#echo var="LAST_MODIFIED" --> будет 26 February 2001. Но это пока все равно не то, что бы нам хотелось. Для определения месяца мы воспользуемся условными операторами. Вот, как будет выглядеть этот код:

<!--#config timefmt="%m" --> <!--#set var="NUM_MONTH" value="$LAST_MODIFIED"--> <!--#if expr="$NUM_MONTH=01" --> <!--#set var="month" value="Января" --> <!--#elif expr="$NUM_MONTH=02" --> <!--#set var="month" value="Февраля" --> <!--#elif expr="$NUM_MONTH=03" --> <!--#set var="month" value="Марта" --> <!--#elif expr="$NUM_MONTH=04" --> <!--#set var="month" value="Апреля" --> <!--#elif expr="$NUM_MONTH=05" --> <!--#set var="month" value="Мая" --> <!--#elif expr="$NUM_MONTH=06" --> <!--#set var="month" value="Июня" --> <!--#elif expr="$NUM_MONTH=07" --> <!--#set var="month" value="Июля" --> <!--#elif expr="$NUM_MONTH=08" --> <!--#set var="month" value="Августа" --> <!--#elif expr="$NUM_MONTH=09" --> <!--#set var="month" value="Сентября" --> <!--#elif expr="$NUM_MONTH=10" --> <!--#set var="month" value="Октября" --> <!--#elif expr="$NUM_MONTH=11" --> <!--#set var="month" value="Ноября" --> <!--#else --> <!--#set var="month" value="Декабря" --> <!--#endif -->



Соответственно, чтобы получить дату в формате 26 Февраля 2001.

<!--#config timefmt="%e"--><!--#echo var="LAST_MODIFIED" --> <!--#echo var="month" --> <!--#config timefmt="%Y"--><!--#echo var="LAST_MODIFIED" -->

Вот полный список значение параметра timefmt:

Формат Описание Пример
%a Аббревиатура названия дня недели Sun
%A Полное название дня недели Sunday
%b Аббревиатура названия месяца Jan
%B Полное название месяца January
%d День месяца 01 (не 1)
%D Дата в формате "%m/%d/%y" 01/31/90
%e День месяца 1
%H Часы в 24-часовом формате 13
%I Часы в 12-часовом формате 01
%j День года 235
%m Номер месяца 01
%M Минуты 03
%p AM|PM AM
%r Время в формате "%I:%M:%S %p" 11:35:46 PM
%S Секунды 34
%s Время в секундах с 01.01.1970 957228726
%T Время в формате "%H:%M:%S" 14:05:34
%U Неделя года 49
%w Номер дня недели 5
%y Год в формате ГГ 95
%Y Год в формате ГГГГ 1995
%Z Временная зона MSK
Далее приведены несколько полезных SSI-директив. Допустим, вы распространяете какие-либо утилиты и выкладываете ссылки на файлы для скачивания. Например, такие ссылки вы найдете на сайте . Для удобства пользователей неплохо было бы указать размер файла и иногда, дату его последней модификации. Каждый раз после выкладывания на сервер новых версий файла, модифицировать самому ручками все размеры и даты долго и мучительно. Server Side Includes приходят на помощь.

<a href=0.gif>Прозрачный gif-файл</a><br> Дата модификации:<!--#config timefmt="%d %B %Y" --> <!--#flastmod file="0.gif" --><br> Размер:<!--#config sizefmt="bytes"--> <!--#fsize file="0.gif" --> байта<br>


Дата модификации: 12 February 2002

Размер: 43 байта

Если у вас файлы больших размеров, то информацию лучше выводить в килобайтах.


Для этого установите формат вывода размера файла следующим образом.

<!--#config sizefmt="abbrev"-->

При возникновение какой-либо ошибки, например, при попытки включить в HTML-документ файл, которого не существует веб-сервер выдаст следующее сообщение:

[an error occurred while processing this directive]

Сообщение не слишком информативно и пугает пользователя, не так ли? Давайте изменим сообщение об ошибке следующей SSI-директивой

<!--#config errmsg="<b>На сервере произошла ошибка связанная с SSI, пожалуйста, напишите </b> <a href=mailto:igor@itsoft.ru>администратору</a>."-->

Теперь сообщение об ошибке вылядит так:

На сервере произошла ошибка связанная с SSI, пожалуйста, напишите .






MySQL


Изначально предполагалось в этой главе описать трудности, которые возникают у начинающих при установки того или иного программного продукта. Можно даже сказать, что в основном эта глава предназначена для тех студентов, слушателей данного курса в Московком Институте Электроники и Математики, которые не имееют достаточного кругозора в области Интернет и сталкиваются с описанными проблемами впервые. Это может показаться странным читателям, которые уже давно варяться в среде Интернет. Но факт тот, что определенная часть слушателей данного курса не является продвинутыми пользователями Интернет и их зачастую приходится обучать самым примитивным вещам, например, как пользоваться электронной почтой, ftp и т.п.

Вообще говоря эту главу можно пропустить и вернуться к ней несколько позже, когда возникнет необходимость установки тех или иных программын продуктов.

Итак, начнем с описания процесса установки MySQL, которая наиболее тривиальная. Под FreeBSD вам необходимо установить два пакета:

pkg_add mysql-client-3.23.32.tgz pkg_add mysql-server-3.23.32.tgz

На этом установка завершена.

Под Windows запустить setup.exe из дистрибутива и выполнить копирование файлов показанных ниже. Допустим вы установили MySQL на диск C:. Тогда вам необходимо: скопировать заголовочные файлы MySQL из C:\mysql\include в C:\Program Files\Microsoft Visual Studio\VC98\Include\mysql, скопировать lib-файл C:\mysql\lib\opt\libmysql.lib в C:\Program Files\Microsoft Visual Studio\VC98\Lib и скопировать динамическую библиотеку C:\mysql\lib\opt\libmysql.dll -> \Windows\System.



Библиотека ITCGI


Первое, с чего хочу начать этот параграф, так это объяснить, зачем, собственно, нам понадобилось изобретать велосипед и чем наш велосипед лучше других.

Изначально, первые свои CGI-программы для WEB мы писали на языке Perl. Они были не очень сложными. Далее, когда возникли задачи более серьезные, связанные с системным программированием, с базами данных, мы приняли решение перейти на Си/C++. Многие сторонники языка Perl утверждают, что он ни чем не хуже. Может быть, если не знать Си/С++. Конечно, это вопрос очень спорный. Можно долго отстаивать свое мнение, но так ни к чему и не прийти. На нашем сайте в форуме как-то возникла такая дискуссия, но так ничем и не закончилась. Мой хороший знакомый, автор и создатель сайта , сторонник языка Perl. В скриптовых языках типа Perl, PHP или Unix-shell я вижу только два существенных преимущества.

автоматическое приведение типов формирование строк на лету

Первое может служить и серьезным недостатком. На мой взгляд, при разработке больших приложений лучше все-таки использовать языки с жесткой типизацией, чтобы не складывать "деньги" с "днями". Самым серьезным недостатком языка Perl является повышенные требования к производительности компьютера. Perl - язык интерпретируемый, поэтому требуется значительно больше памяти и процессорного времени для работы Perl-программы. Мне приходилось наблюдать, как форум, написанный на Perl загружал сервер на 60%. Теперь представьте реальную ситуацию, когда у вас на одном сервере крутится несколько десятков сайтов. Сервер просто может повиснуть.

Теперь приведу свои доводы в пользу языков Си\С++.

Системные вещи пишутся на Си. Не будет проблем интеграции с любым API, например, вы можете использовать функции системных библиотек, использовать OpenGL - библиотеку трехмерной графики для создания графических сцен. Результатом является исполняемый файл, а значит работать будет быстрее. А также в случае, если злоумышленник получит доступ на чтение к файлам в папке cgi-bin, то найти дырку в бинарном файле значительно сложнее, нежели прочитать исходник интерпретируемого языка типа Perl или PHP. Си имеет гораздо большую сферу применения.
Вам не надо изучать синтаксис нового языка, чтобы начать писать игры, приложения баз данных или записную книжку. Язык С++ хорошо соответствует объектно-ориентированной парадигме. Си\C++ языки профессионалов. И решающим фактором был опыт работы, у любого программиста нашей команды более 5 лет программирования на Си\С++.

Вот почему мы выбрали именно этот язык программирования.

Теперь, почему мы решили написать свою библиотеку. Сначала, мы как любые умные люди, попытались воспользоваться уже готовым решением. Под юниксом мы попробовали использовать библиотеки cgiparse и cgihtml. И вот причины, по которым мы пришли к написанию собственной библиотеки.

Перед вызовом функций этих библиотек требуется начальная инициализация, а перед завершением работы, очистка памяти. В программе вы должны объявить переменную llist entries; и вызвать функцию read_cgi_input(&entries); Если начальная инициализация еще не вызывает хлопот, то вызов функции очистки памяти - list_clear(&entries); неприятен тем, что выходов из программы может быть несколько. И перед каждой инструкцией return вам придется писать list_clear(&entries);. В библиотеке ITCGI начальная инициализация выполняется при первом вызове любой функции, а завершающая очистка памяти происходит автоматически при завершении программы.

При получении значения параметра html-формы библиотеки cgiparse и cgihtml возвращают указатель на строку в их внутреннем буфере. Теперь, если вы по этому указателю запишите другое значение, или еще хуже, освободите память под ним, то при следующем запросе значения параметра вы получите уже другое значение - не то, что было в оригинале. Библиотека ITCGI выдает всегда копию строки.

Нам нужно было работать не только под unix, но и под windows. Под Windows мы использовали MFC и класс CString. Библиотека ITCGI имеет одинаковый интерфейс и под Unix и под Windows. В Windows ее реализация написана на С++ с использованием MFC. В Unix мы сделали все на чистом Си, написав свою структуру LString.



Библиотеки cgiparse и cgihtml не имели функций для считывания файла в строку, для замены в html-шаблоне переменных помеченных как %%var%% на их реальные значения, для обработки SSI-директивы include virtual и многих других.

На текущий момент библиотека ITCGI содержит следующие группы функций:

работа с CGI-параметрами Cookie - параметры на стороне клиента Работа с параметрами на стороне сервера. Функции общего назначения. Работа с HTML. Функции для работы с картинками GIF & JPEG

Начнем с рассмотрения простой программы, которая выводит список cgi-параметров. Для работы со строками применяется тип LString, который есть ни что иное как char*.

typedef char* LString;

Любая строка создаётся вызовом CreateString(), а удаляется функцией DeleteString(). Вводить свой тип понадобилось для написания логически правильных программ, на наш взгляд. Кто-то может и не согласится. Рассмотрим в качестве примера функцию -

int GetParamByIndex(int index, LString* value);

Эта функция возвращает по индексу параметра его значение. Например, для строки cgi-запроса text=zero&text=zero&list=0&list2=0 нулевым параметром будет text, а его значение - zero. Теперь давайте подумаем о прототипе функции GetParamByIndex. При просмотре некоторых библиотек для разработки CGI-программ мне попадались функции примерно такие:

char* GetParamByIndex(int index);

При этом, в реализации функции GetParamByIndex будет динамически выделятся память под значение возвращаемой строки. Такой интерфейс не годится, т.к. printf("%s", GetParamByIndex(1)); приведет к утечке памяти. На мой взгляд, выделять динамически память и перекладывать заботу о ее освобождение на кого-то является дурным стилем. Кто память выделяет, тот и должен ее освобождать. При использование LString у вас не болит голова о распределение памяти. Ниже приводится исходный код на Си и Makefile, который использовался для сборки программы под операционной системой FreeBSD4.2.

//listcgi.c #include <itcgi.h>

int main() { LString* name = CreateString(); // строка для хранения имени параметра LString* value = CreateString(); // его значения int i, count;

count = GetCount(); // получаем общее количество параметров

printf("Content-type: text/html\n\n"); // печатаем заголовок документа

// выводим HTML-код таблицы printf("<html><table border=\"1\"><caption>Полный список\ cgi-параметров</caption><tr><td\ bgcolor=\"E5E5E5\">Индекс<td bgcolor=\"E5E5E5\">Имя<td\ bgcolor=\"E5E5E5\">Значение");

// в цикле проходим по всем параметрам for(i=0;i<count;i++) { GetParamByIndex(i, value); // получаем значение параметра по его индексу GetParamNameByIndex(i, name); // получаем имя параметра printf("<tr><td>%d <td> %s <td> %s\n", i, *name, *value); } printf("</table>");

DeleteString(name); // освобождаем память DeleteString(value); return 0; }

===Makefile=== all: listcgi

listcgi: listcgi.c itcgi.a gcc listcgi.c -L/usr/local/lib/mysql -I/usr/local/include/mysql \ -L/usr/local/lib -I/usr/local/include \ -o listcgi -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip listcgi cp listcgi /www/members/cgi-bin/listcgi

<


В качестве примера я взял простенькую форму.

text:

password:

checkbox:

radio1:

radio2:

radio3

one two zero

one two zero ten 11

one two zero ten 11

 

Вот результат ее обработки:

Полный список cgi-параметров
ИндексИмяЗначение
0 text Тестируем
1 password ывфыв
2 checkbox on
3 radio r2
4 textarea Простой текст
5 list 2
6 list2 10
7 list2 1
8 list2 2
9 list2 0
А это строка CGI-парметров:

text=%D2%E5%F1%F2%E8%F0%F3%E5%EC&password=%FB%E2%F4%FB%E2&checkbox=on& radio=r2&textarea=%CF%F0%EE%F1%F2%EE%E9+%F2%E5%EA%F1%F2&list=2&list2=10& list2=1&list2=2&list2=0

Библиотеку ITCGI можно взять на сайте .

Далее мы рассмотрим несколько примеров полезных программ.

Чат


Теперь рассмотрим функции работы с параметрами на стороне сервера, на примере двух программ для реализации чата. Функции, которые пишут информацию непосредственно на сервер, используется очень редко. Здесь мы рассматриваем программу чата, как учебный пример, чтобы продемонстрировать функции чтения параметров. Почему это учебный пример и как правильно написать чат см. главу Часть IV "Базы данных". Основное назначение группы этих функций, именно читать информацию из конфигурационного файла на сервере, а также работать с параметрами, которые сохраняются в базе данных.

Чат представляет из себя три html-файла, одну программу, которая показывает последние 20 сообщений и одну программу, которая добавляет сообщения. В действие чат смотрите .

Код главной html-страницы чата выглядит следующим образом:

<html> <head> <title>Chat</title> </head> <frameset rows="90%, 10%"> <frame src="chat_show.html"> <frame src="chat_add.html"> </frameset> </html> Страница состоит из двух фреймов. В верхнем отображаются сообщения. В нижнем их можно добавлять.

Показ сообщений - chat_show.html, обновляется каждые 30 секунд. <html> <meta http-equiv="refresh" content="30; url=chat_show.html"> <body> <!--#include virtual="/cgi-bin/chat"--> </body> </html>

Добавление сообщения - chat_add.html <html> <body> <form method=post action=/cgi-bin/chat_add> Имя:<input type=text name=name maxlenght=10 size=10> Сообщение:<input type=text name=msg maxlenght=80 size=50> <input type=submit> </form> </body> </html>

Чат устроен предельно просто. На сервере имеется 20 параметров с именами 0, 1, 2, 3 и т.д. Значение каждого параметра есть некоторое сообщение чата. Когда добавляется новое сообщение, то 20-е сообщение исчезает, его место занимает 19-е, на место 19-го становится 18-е и т.д., на место нулевого - новое сообщение.
Программа для вывода текущих сообщений выглядит следующим образом.

#include <itcgi.h>

int main() { LString* str = CreateString(); char buf[128]; int i;

printf("Content-type: text/html\n\n");

// в цикле читаем параметры на стороне сервера for(i=0;i<20;i++) { sprintf(buf, "%d", i); //следующая функция читает из конфигурационного файла // из секции chat i-й параметр GetRCParam(0, "chat", buf, str); printf("%s<br>\n", *str); }

DeleteString(str); return 0; }

=======Makefile======

all: chat

chat: chat.c itcgi.a gcc chat.c -L/usr/local/lib/mysql -I/usr/local/include/mysql \ -L/usr/local/lib -I/usr/local/include \ -o chat -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip chat cp chat /www/members/cgi-bin/chat

//программа добавления сообщения в чат // // #include <itcgi.h>

int main() { LString* name = CreateString(); LString* msg = CreateString(); LString* str = CreateString(); char buf[128]; int i;

// Считываем имя автора и само сообщение GetParamByName("name", name); GetParamByName("msg", msg);

// в цикле переписываем значения параметров // i-й параметр принимает значение (i-1)-го параметра // for(i=20;i>1;i--) { sprintf(buf, "%d", i-2); GetRCParam(0, "chat", buf, str); sprintf(buf, "%d", i-1); SetRCParam(0, "chat", buf, *str); }

// на место 0-го параметра ставим только что полученное сообщение sprintf(buf, "%s: %s", *name, *msg); SetRCParam(0, "chat", "0", buf);

// возвращаемся на страницу, с которой была вызвана // эта программа printf("Location: %s\n\n", getenv("HTTP_REFERER"));

DeleteString(name); DeleteString(msg); DeleteString(str); return 0; }

======Makefile======

all: chat_add

chat_add: chat_add.c itcgi.a gcc chat_add.c -L/usr/local/lib/mysql -I/usr/local/include/mysql \ -L/usr/local/lib -I/usr/local/include \ -o chat_add -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip chat_add cp chat_add /www/members/cgi-bin/chat_add

<







COOKIE: Идентификация пользователей


В данном параграфе мы рассмотрим использование COOKIE при разработке CGI-программ. См. также пятую главу, параграф COOKIE. Часто механизм COOKIE используется для идентификации пользователей. Мы напишем программу, которая выводит приветствие. Если в броузере пользователя уже установлено COOKIE, то выводится приветствие:

Hello, Василий Пупкин!

Иначе, предложение ввести свое имя:


What is your name?

Далее вы видите эту программу в действии. Введите свое имя, нажмите кнопку и после перезагрузки страницы вы увидите приветствие. В данном случае cookie устанавливается на 60 секунд. Через одну минуту, если вы перезагрузите страницу, то этого приветствия не увидите. Здесь это сделанно специально, чтобы была возможность поиграть с cookie. Ниже будут рассмотрены программы, которые позволяют просматривать список всех установленных cookie и самому устанавливать параметры cookie.

[an error occurred while processing this directive]

Исходный код программы приветствия "hello" выглядит следующим образом.

//hello.c #include <itcgi.h>

int main() { LString* value = CreateString();

printf("Content-type: text/html\n\n");

if( GetCookieValueByName("name", value) ) printf("Hello, %s!\n", *value); else printf("<form method=post action=/cgi-bin/setcookie>\ <input type=hidden name=name value=name><br>\ What is your name? <input type=text name=value><br>\ <input type=hidden name=seconds value=60>\ <input type=submit></form>");

DeleteString(value); return 0; }

========Makefile======= all: hello

hello: hello.c itcgi.a gcc hello.c -L/usr/local/lib/mysql -I/usr/local/include/mysql \ -L/usr/local/lib -I/usr/local/include \ -o hello -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip hello cp hello /www/members/cgi-bin/hello

Как вы видите, текст программы предельно простой. Если удалось получить значение параметра cookie с именем "name", то выводим приветствие. Если нет, то выводим HTML-форму, где пользователь сможет указать свое имя.
Рассмотрим эту HTML-форму более подробно

<form method=post action=/cgi-bin/setcookie>\ <input type=hidden name=name value=name><br>\ What is your name? <input type=text name=value><br>\ <input type=hidden name=seconds value=60>\ <input type=submit></form>
Форма обрабатывается программой setcookie, которую мы рассмотрим чуть ниже. На вход этой программы приходит три параметра. Первый - имя параметра cookie, второй - значение и третий - это время в секундах, срок жизни этого cookie. В данном случае первый и третий параметры невидимы, т.к. демонстрируется программа приветствия, в которой нас интересует только второй параметр - ваше имя.

Исходный текст программы setcookie.
//setcookie.c #include <itcgi.h>

int main() { LString* name = CreateString(); LString* value = CreateString(); LString* seconds = CreateString();

//считываем три параметра GetParamByName("name", name); GetParamByName("value", value); GetParamByName("seconds", seconds);

//устанавливаем cookie SetCookieForNSeconds(*name, *value, atoi(*seconds), "/docs/web", 0, 0);

//возвращаем пользователя на страницу, с которой была запущена // данная программа printf("Location: %s\n\n", getenv("HTTP_REFERER"));

DeleteString(name); DeleteString(value); DeleteString(seconds); return 0; }

=====Makefile===== all: setcookie

setcookie: setcookie.c itcgi.a gcc setcookie.c -L/usr/local/lib/mysql -I/usr/local/include/mysql \ -L/usr/local/lib -I/usr/local/include \ -o setcookie -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip setcookie cp setcookie /www/members/cgi-bin/setcookie

Обратите внимание на четвертый параметр функции SetCookieForNSeconds - "/docs/web". Это путь на сайте, где лежит моя книга и все html-файлы, для которых будет действовать cookie. Также интерес представляет заголовок, который печатает данная программа.

printf("Location: %s\n\n", getenv("HTTP_REFERER"));



Переменная окружения HTTP_REFERER содержит URL страницы, с которой была запущена HTML-форма или вызывана данная программа. Заголовок Location: означает, что броузер пользователя должен будет открыть этот URL. Такой прием возврата на страницу используется довольно часто. Я бы, конечно, мог написать в явной форме

printf("Location: http://members.itsoft.ru/docs/web/chapter8.html\n\n");

но тогда, при переносе книги на другой сайт, что собственно и будет сделано после ее завершения, мне придется исправлять исходные коды программ и перекомпилировать их. А рассмотренный пример универсален - не зависит от сайта, на котором будет находится HTML-форма.

Далее мы рассмотрим программу listcookie - полный список cookie-параметров. Вот вы ее видите в действии. [an error occurred while processing this directive] Ниже, форма для установки параметров, которая обрабатывается выше рассмотренной программой setcookie. Установите новый параметр и вы увидите, как он добавится в список.

name

value

seconds

Исходный текст listcookie
// listcookie.c #include <itcgi.h>

int main() { LString* name = CreateString(); LString* value = CreateString(); int i, count;

count = GetCookieCount(); // получаем общее количество параметров

printf("Content-type: text/html\n\n");

// выводим HTML-код таблицы printf("<table border=\"1\"><caption>Полный список\ COOKIE-параметров</caption><tr><td\ bgcolor=\"E5E5E5\">Индекс<td bgcolor=\"E5E5E5\">Имя<td\ bgcolor=\"E5E5E5\">Значение");

// в цикле проходим по всем параметрам for(i=0;i<count;i++) { GetCookieValueByIndex(i, value); // получаем значение параметра по его индексу GetCookieNameByIndex(i, name); // получаем имя параметра printf("<tr><td>%d <td> %s <td> %s\n", i, *name, *value); } printf("</table>"); DeleteString(name); // освобождаем память DeleteString(value); return 0; }

=====Makefile===== all: listcookie

listcookie: listcookie.c itcgi.a gcc listcookie.c -L/usr/local/lib/mysql -I/usr/local/include/mysql \ -L/usr/local/lib -I/usr/local/include \ -o listcookie -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip listcookie cp listcookie /www/members/cgi-bin/listcookie

<


Данная программа почти полностью аналогична программе listcgi. Отличие в названии функций, работающих с CGI и COOKIE параметрами.






Описание протокола


Протокол CGI - Common Gateway Interface служит для связи веб-сервера с другими программами. На его основе работают гостевые книги, форумы, интернет-магазины и любые другие интерактивные сайты. Вы заполняете HTML-форму на сайте, нажимаете кнопку "Отправить", после чего обозреватель передает данные веб-серверу. Веб-сервер запускает соответствующую программу и передает ей данные, полученные от вашего Интренет-обозревателя. Программа на основе этих данных формирует HTML-страницу и возвращает ее веб-серверу. Веб-сервер, в свою очередь, возвращает эту страницу вашему обозревателю. При этом, программа может обращаться к базе данных или запустить другие программы на сервере. Программа может быть как исполняемым файлом, написанным на языках С\С++, Pascal, Assembler и др., так и скриптом на языке Perl или UNIX-shell. О создании HTML-форм вы узнали в первой части книги, далее пойдет речь о разработке CGI-программ.



Программа для отправки писем по E-Mail


В данном параграфе будут рассмотрены функции общего назначения на примере CGI-программы для отправки писем. Часто возникает такая ситуация, когда пользователь зашел на ваш сайт с компьютера, на котором нет электронной почты, но ему необходимо написать вам письмо. На этот случай на сервере удобно иметь веб-интерфейс, который позволит такому пользователю написать и отправить вам письмо. Ниже приведена простейшая HTML-форма такого веб-интерфейса.

email

subj

// HTML-код формы <form method=post action=/cgi-bin/mail> email <input type=text name=email><br> subj <input type=text name=subj><br> <textarea name="text" cols="45" rows="6"></textarea> <input type=submit> </form>

// Текст такой программы тоже предельно простой #include <itcgi.h>

int main() { LString* subj = CreateString(); LString* email = CreateString(); LString* text = CreateString();

// считываем тему, адрес отправителя и текст сообщения GetParamByName("subj", subj); GetParamByName("email", email); GetParamByName("text", text);

// отправляем письмо mail("itsoft.ru", *email, "igor@itsoft.ru", *subj, *text);

// возвращаемся на эту же страницу printf("Location: %s\n\n", getenv("HTTP_REFERER"));

//освобождаем память DeleteString(subj); DeleteString(email); DeleteString(text); return 0; }

========Makefile======== all: mail

mail: mail.c itcgi.a gcc mail.c -L/usr/local/lib/mysql -I/usr/local/include/mysql \ -L/usr/local/lib -I/usr/local/include \ -o mail -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip mail cp mail /www/members/cgi-bin/mail

Создание CGI-программ


Когда пользователь заполняет html-форму и нажимает кнопку submit, данные отправляются веб-серверу. Веб-сервер, будь это Apache, IIS или какой-либо другой, запускает программу, указанную в качестве значения атрибута action. В нашем случае это test.cgi. Веб-сервер запускает test.cgi и передает ей параметры в виде текстовой строки, следующего содержания: name1=value1&name2=value2&....nameN=valueN, т.е. имя_параметра=значение. Эта строка передается на стандартный поток ввода (STDIN) или в качестве значения переменной окружения QUERY_STRING. Соответственно, считать данную строку в программе можно одним из двух способов:

unsigned int len; len = atoi( getenv("CONTENT_LENGTH") ); query = (char*)malloc(len+1); fread(query, 1, len, stdin); query[len] = 0;

или

query=(char*)malloc(strlen(getenv("QUERY_STRING"))); strcpy(query,getenv("QUERY_STRING"));

В первом случае параметры передаются методом POST, а во втором методом GET. В первом случае мы читаем строку из STDIN. Длину строки мы узнаем из значения параметра окружения CONTENT_LENGTH. Во втором она хранится в переменной окружения QUERY_STRING. Значение переменной окружения можно получить, вызвав функцию getenv. Метод, с помощью которого передается строка с параметрами CGI-программе, можно определить следующим образом: strcmp(getenv("REQUEST_METHOD"),"POST"). Далее придется разбирать строку и получать необходимые значения параметров. Для того чтобы не делать это каждый раз, мы написали небольшую, но очень удобную библиотечку ITCGI для написания CGI-скриптов. Эта библиотека позволяет вам полностью абстрагироваться от метода, которым передаются параметры, от кодировки, от разбора строки. Вы просто вызываете функцию GetParamByName, в которую передаете имя интересующего вас параметра и адрес строки, куда сохранить значение. Библиотека также предоставляет вам ряд функций для написания эффективных и защищенных от взлома CGI-скриптов.

В простейшем случае, когда ваша программа не нуждается в параметрах, вам и не потребуется ни самому разбирать и раскодировать строку, ни использовать для этого нашу библиотеку.
Самой простой CGI-программой будет:

#include<stdio.h>

int main() { // выдаем обязательный заголовок // это часть CGI-протокола printf("Content-type: text/html\n\n");

printf("<html>"); printf("<body>");

printf("Hello, World!");

printf("</body>"); printf("</html>"); return 0; }
Заголовок является обязательной частью. Он передается веб-серверу и определяет, что следует за ним. В большинстве случаев у вас будет именно такой заголовок. Он говорит веб-серверу, что дальше идет HTML-код. С другими типами заголовков мы познакомимся чуть позже. В заголовке может быть несколько строк. Конец заголовка обозначается двумя переходами на новую строку - \n\n. Откомпилируйте эту программу, а исполняемый файл положите в каталог /cgi-bin вашего веб-сайта. Переименуйте его в test.cgi. К этому скрипту можно обратится непосредственно через обозреватель, написав в командной строке URL, например у меня это выглядит так http://itsoft.ru/cgi-bin/test.cgi В результате, в вашем обозревателе вы увидите строку: "Hello, World!".

Далее мы рассмотрим CGI-программу такого же типа. Она не принимает никаких параметров, но зато выдает более полезную информацию - список и значения всех переменных окружения. Такой скрипт вам пригодится, когда вы будете отлаживать свои CGI-программы на различных веб-серверах. Дело в том, что переменные окружения различаются на различных веб-серверах. Так, например, для веб-сервера Apache, путь к каталогу веб-сайта хранится в переменной окружения DOCUMENT_ROOT. Для веб-сервера Microsoft Internet Information Server это значение хранится в переменной PATH_TRANSLATED. В операционной системе UNIX скрипт для вывода всех переменных выглядит следующим образом.
#!/bin/sh echo "content-type: text/plain\n\n" echo env
Обратите внимание на CGI-заголовок. Он отличается от того, который у нас был в предыдущем примере. plain означает, что скрипт выдаст не HTML-код, а чистый текст.


Броузер будет воспринимать его, как обычный текст и выводить в точности как есть. Здесь не надо заменять спецсимволы типа < на их эквиваленты &lt;. Скопируйте этот скрипт в директорию /cgi-bin с именем env. Установите атрибут 755 (rwxr-xr-x). Вот результат выполнения такого скрипта на моем unix-сервере:
GATEWAY_INTERFACE=CGI/1.1 REMOTE_USER=itsoft REMOTE_ADDR=192.168.34.134 QUERY_STRING= REMOTE_PORT=1781 HTTP_USER_AGENT=Mozilla/4.0 (compatible; MSIE 5.0; Windows 98; DigExt) DOCUMENT_ROOT=/usr/local/www/itsoft AUTH_TYPE=Basic SERVER_SIGNATURE=<ADDRESS>Apache/1.3.12 Server at itsoft.ru Port 80</ADDRESS>

HTTP_ACCEPT=gif, x-xbitmap, jpeg, pjpeg, */* SCRIPT_FILENAME=/usr/local/www/itsoft/cgi-bin/web/env HTTP_HOST=itsoft.ru REQUEST_URI=/cgi-bin/web/env SERVER_SOFTWARE=Apache/1.3.12 (Unix) PHP/3.0.17 HTTP_CONNECTION=Keep-Alive HTTP_COOKIE=/cgi-bin/authenticate.cgi_LAST=956345778 PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin: /usr/local/bin:/usr/X11R6/bin HTTP_ACCEPT_LANGUAGE=ru SERVER_PROTOCOL=HTTP/1.1 HTTP_ACCEPT_ENCODING=gzip, deflate REQUEST_METHOD=GET SERVER_ADMIN=igor@itsoft.ru SERVER_ADDR=194.226.32.34 SERVER_PORT=80 SCRIPT_NAME=/cgi-bin/web/env SERVER_NAME=itsoft.ru
Программа на языке Си для Windows и веб-сервера Internet Information Server будет выглядеть следующим образом:

#include <stdio.h> #include <stdlib.h> void main() { char *text; char str[1024]; int length; FILE *in;

sprintf(str,"command.com /c set>%s\\temp\\env.dmp",getenv("PATH_TRANSLATED")); system(str);

sprintf(str,"%s\\temp\\env.dmp",getenv("PATH_TRANSLATED")); in = fopen(str, "rb"); if( !in ) { printf("Content-type: text/plain\n\nCan't open file %s.", str); return; }

fseek(in, 0, SEEK_END); length = ftell(in); fseek(in, 0, SEEK_SET); text = (char*)malloc(length+1); fread(text, 1, length, in); text[length] = 0; fclose(in);

printf("Content-type: text/plain\n\n%s", text); free(text); }

<


Сначала выполняется команда command.com /c set>c:\www\mysite\temp\env.dmp. Результатом выполнения такой команды и будет список всех переменных окружения, который затем сохраняется в файл. Далее мы читаем этот файл и выдаем его содержимое веб-серверу. Вы можете заметить, что в данном случае, как и в прошлом примере, мы печатаем не html-код, а чистый текст и поэтому у нас заголовок: Content-type: text/plain. Не забудьте также, что этот cgi-скрипт будет работать только под Internet Information Server. Для веб-сервера Apache следует заменить getenv("PATH_TRANSLATED") на getenv("DOCUMENT_ROOT").

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

COMSPEC=C:\WINNT\SYSTEM32\COMMAND.COM COMPUTERNAME=JUPITER CONTENT_LENGTH=0 GATEWAY_INTERFACE=CGI/1.1 HTTP_ACCEPT=gif, x-xbitmap, jpeg, pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, applic HTTP_ACCEPT_LANGUAGE=ru HTTP_CONNECTION=Keep-Alive HTTP_HOST=www.oxygensoftware.com HTTP_USER_AGENT=Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0) HTTP_ACCEPT_ENCODING=gzip, deflate HTTPS=off INCLUDE=C:\Program Files\Mts\Include INSTANCE_ID=1410 LIB=C:\Program Files\Mts\Lib LOCAL_ADDR=168.144.29.178 NUMBER_OF_PROCESSORS=2 OS2LIBPATH=C:\WINNT\system32\os2\dll; OS=Windows_NT PATH=C:\WINNT\system32;C:\WINNT;C:\Program Files\Mts PATH_TRANSLATED=e:\InetPub\Clients\oxygensoftware.com PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.JS;.VBE;.JSE;.WSF;.WSH PROCESSOR_ARCHITECTURE=x86 PROCESSOR_IDENTIFIER=x86 Family 6 Model 5 Stepping 1, GenuineIntel PROCESSOR_LEVEL=6 PROCESSOR_REVISION=0501 PROMPT=$P$G REMOTE_ADDR=194.226.32.34 REMOTE_HOST=194.226.32.34 REQUEST_METHOD=GET SCRIPT_NAME=/cgi-bin/env.exe SERVER_NAME=www.oxygensoftware.com SERVER_PORT=80 SERVER_PORT_SECURE=0 SERVER_PROTOCOL=HTTP/1.1 SERVER_SOFTWARE=Microsoft-IIS/4.0 SYSTEMDRIVE=C: SYSTEMROOT=C:\WINNT TEMP=C:\temp TMP=C:\temp USERPROFILE=C:\WINNT\Profiles\Default User



Далее, прежде чем перейти к рассмотрению cgi-скриптов, которые принимают и обрабатываю параметры формы, мы напишем простенькую программу, которая выдает строку параметров html-формы. О том, как считываются параметры формы, читайте выше, здесь я привожу исходный код программы и ее результат для html-формы, описанной в четвертой главе.

#include <stdio.h> #include <stdlib.h>

void main() { char* query=NULL;

if( !strcmp(getenv("REQUEST_METHOD"),"POST") ) { unsigned int len; len = atoi( getenv("CONTENT_LENGTH") ); query = (char*)malloc(len+1); fread(query, 1, len, stdin); query[len] = 0; } else if( !strcmp(getenv("REQUEST_METHOD"),"GET") ) { query=(char*)malloc(strlen(getenv("QUERY_STRING"))); strcpy(query,getenv("QUERY_STRING")); } else printf("unknown REQUEST_METHOD\n");

printf("Content-type: text/plain\n\n%s", query); free(query); }
Скомпилируйте этот код. Он платформенно независимый, поэтому можете скомпилировать как под Unix, так и под Windows. Из четвертой главы возьмите HTML-форму, можете взять и любую другую. В поле action пропишите путь к данной программе на вашем веб-сервере. Результат после нажатия на кнопку "Опубликовать":

text=zero&text=zero&list=0&list2=0&textarea=%C7%E4%E5%F1%FC+%F2%E5%EA%F1%F2+%EF%EE+%F3%EC%EE%EB%F7%E0%ED%E8%FE

Это строка, которая содержит список всех параметров Далее, вы можете воспользоваться нашей библиотекой ITCGI или же самим разбирать эту строку параметров. О библиотеке и ее практическом использовании читайте в следующей параграфе.

Модификация программы отправки писем по E-Mail


Модифицируйте рассмотренный веб-интерфейс, чтобы можно было указать свое имя и адрес своего сайта в отдельном поле ввода. Программа mail должна будет считывать их и включать в тело письма.



Опросник


На основе рассмотренной технологии сделайте две программы, реализующие голосование. Такого рода опросник представляет собой HTML-форму с несколькими радиокнопками. Пользователь может выбрать один из нескольких вариантов. На сервере хранятся параметры 0,1,2,3 и т.д., в зависимости от количества вариантов. Например, для вопроса "Ваш пол" требуется только 0 и 1. Программа vote_add будет увеличивать значение соответствующего параметра на единичку. Программа vote будет показывать результаты голосования. Почти полная аналогия с чатом.



Персонализация веб-сайта


Модифицируйте программу hello.c, чтобы она выдавала приветствие заданным цветом. Для этого, на основе программы setcookie напишите свою, которая будет регистрировать новых посетителей, устанавливая им соответсвующие cookie.



Обзор языков программирования, как средств разработки CGI-программ


Целью данной главы является дать некоторый обзор различных языков программирования, как инструментов для разработки CGI-скриптов. Показать их сильные и слабые стороны, а также просто те или иные их особенности. Для начала, рассмотрим общие требования к языку разработки CGI-скриптов. Для этого надо описать общую картину, рассказать о том, где и в каких условиях живут сайты в Интернет. Сайты живут на железных серверах, так называемых, системных блоках. На одном системном блоке живет несколько сотен сайтов. Наиболее распространенной платформой на сегодняшний день является Unix, а точнее две его разновидности: Linux и FreeBSD. Linux - это для детей, что-то наподобие Windows. Если хотите вдоволь натрахаться, увидить свой сервер повисшим, ломать голову, почему что-то работает не так, как должно, то Linux к вашим услугам. Сейчас в меня полетят камни линуксоидов, но простим им, ибо не ведают, что творят. Я сам когда-то был таким. Ну, а когда совсем пешком под стол ходил, так вообще про WinNT и дядю Билла кричал, все оттого, что ничего другого просто не видел. Так вот, самые продвинутые и умные работают на FreeBSD. Наш главный сервер с осени 1999 по весну 2001 года успешно проработал на машине с процессором Pentium 120Mhz, 16Mb оперативной памяти и жестким диском 13Гб под управлением FreeBSD3.3. На сервере было десятка два веб-сайтов с общей посещаемостью от 200 до 700 уникальных посетителей в день. Сервер осуществлял доступ в Интернет для 15 машин. На данной машине также находилось четыре модемных входных линии, через которые мы ходили в Интернет из дома. Более 20 пользователей имели у нас почтовые ящики и ftp-доступ на 50Mb, а также на сервере были установлены firewall, DNS, MySQL, Perl, велись квоты на использование жесткого диска. Средняя загрузка сервера составляла менее одного процента. И самое главное, он ни разу не повис и работал без перезагрузки по нескольку месяцев. Перегружать его приходилось только по необходимости, например, из-за перекомпилирования ядра. Суточный трафик зависит от пропускной способности канала и популярности размещенных сайтов.
Вполне нормальным является трафик в 1-2Гб, а в один и тот же момент времени на сервере может находится 20-50 человек, что можно определить по числу запущенных копий Apache. Анализируя все вышесказанное, делаем выводы:

CGI-программа должна выполняться максимально быстро, чтобы не загружать процессор. Помните, сайтов несколько сотен, на каждом сайте несколько CGI-программ. Все они имеют равный приоритет и равное количество процессорного времени. Средство разработки должно позволять интегрироваться CGI-программе со внешней средой, получать доступ к переменным окружения, соединяться с базами данных, соединяться с другими серверами по сети и т.п.. В один и тот же момент времени может быть запущено несколько экземпляров одной и той же CGI-программы, поэтому средство разработки должно иметь возможность межпроцессорного взаимодействия. Важным фактором является переносимость, которая может быть от версии к версии операционной системы и сопутствующего ПО, например MySQL. Переносимость между клонами ОС FreeBSD Linux. Переносимость на принципиально другую платформу Unix Windows.

На основании перечисленных требований составим сравнительную таблицу характеристик для различных средств разработки.

Средство разработки 1 2 3 4
Си + + + -+
С++ -+ + + -+
MS VisualС + + + -
Delphi -+ + + -
Java - - - +
Unix Shell - + - -
Perl - + + +
PHP - + - +
ASP - + - -
И мы получаем интересную картину, что наиболее подходящим средством для разработки CGI-приложений, являются чистый Си и Perl: причем, Си на первом месте. Авторы многих книг по языку Perl упорно твердят, вторя друг другу, что Perl, именно Perl и только Perl, является самым подходящим средством для разработки программной составляющей веб-сайта. Такого рода авторы - технические писатели, вероятно, не наблюдали, как один perl'овый форум может загружать до 60% ресурсов сервера. Теперь представьте еще раз ситуацию, что у вас сотня сайтов. Меньше никак нельзя, просто нерентабельно - зарплата системного администратора, оплата канала Интернет, затраты на амортизацию оборудования. Если на нескольких сайтах одновременно начнется активность в форумах, то сервер загнется.

Сам я являюсь сторонником языка Си, считая все остальные языки менее пригодными для разработки CGI-программ. Это мнение, конечно, спорное. Есть программисты, которые считают, что вообще все нужно писать на ассемблере или в машинных кодах. Это одна крайность. Другие предпочитают объектно-ориентированный подход и признают только Delphi или С++. Это другая крайность. Выбор инструмента для разработки должен определяться постановкой задачи. При разработке больших информационных систем иногда имеет смысл использовать несколько языков программирования. Так, например, при разработке информационного портала Inforg (http://inforg.ru) мы применяли: MS Visual C++ для разработки офлайновой части программного обеспечения, Си для разр.аботки демона для FreeBSD и CGI-программ, Perl - для обработки документов.

В данном параграфе дан небольшой обзор и авторский взгляд на инструменты разработки веб-сайтов. Популярность Perl или PHP так велика, что можно оспаривать правомерность их использования, но не умалчивать о них. Данная книга ставит задачей освятить общие методы и технологии разработки веб-сайтов. Рассмотреть применение технологий со всеми инструментальными средствами не представляется возможным. Если вы захотите изучить какой-либо язык, то в конце книги есть список литературы и ссылки на Интернет-ресурсы.

Агрегирующие функции


Для получения обобщающих значений в языке SQL имеются агрегирующие функции.

Агрегирующая функцияОписаниеПримерРезультат
SUM([DISTINCT] выражение)Сумма всех значений.SELECT SUM(gb_id) FROM gb274
AVG([DISTINCT] выражение)Среднее арифметическое всех значений.SELECT AVG(gb_id) FROM gb13.5
COUNT([DISTINCT] имя_столбца)Количество ненулевых значений.SELECT COUNT(name) FROM gb16
COUNT(*)Количество выбранных записей.SELECT COUNT(*) FROM gb20
MIN(выражение)Минимальное значениеSELECT MIN(quantity) FROM gb5
MAX(выражение)Максимальное значениеSELECT MAX(quantity) FROM gb20

Вы можете использовать также условия отбора. Например, результатом запроса SELECT COUNT(*) FROM gb WHERE gb_id>5 будет 18. В операторе WHERE использовать агрегирующие функции нельзя, т.е. запрос SELECT * FROM gb WHERE gb_id<AVG(gb_id) синтаксически неверен. Обычные функции, напрмер, косинус или синус можно. Запрос SELECT * FROM gb WHERE COS(gb_id)>0 является синтаксически верным, хотя и не имеет логического смысла. В агрегирующих функциях можно использовать выражения, например: SELECT AVG(2*quantity) FROM gb. При указании ключевого слова DISTINCT повторы не будут учитываться. Можно получать несколько значений одновременно, например: SELECT MIN(quantity) as min, MAX(quantity) as max FROM gb.



INSERT, UPDATE и DELETE


Вы научились получать информацию из базы данных, теперь мы переходим к вставке, редактированию и удалению данных. Эти операции производятся тремя инструкциями INSERT, UPDATE и DELETE соответственно. Рассмотрим их по порядку. Для проведения экспериментов нам понадобятся копии таблиц gb и message. Перейдите в раздел "Таблицы", щелкните мышкой на таблице gb, в меню "Правка" выберите пункт "Копировать", а затем "Вставить". Сохраните таблицу вместе с данными под именем gb2. То же самое проделайте с таблицей message. Инструкция INSERT имеет следующий синтаксис:

INSERT int имя_таблицы [ имя_столбца1 [, имя_столбца2] ...] VALUES ( значение1 [, значение2] ...)

Для регистрации новой гостевой книги и вставки записи в таблицу gb мы будем использовать следующий запрос: INSERT INTO gb2 (name, http, email, quantity, description, it_date) VALUES('Vasya Pupkin', 'pupkin.ru', 'pupkin@pupkin.ru', 7, 'Гостевая книга для сайта Васи Пупкина', NOW()). Этот запрос вставляет в таблицу gb2 новую запись. Обратите внимание, как вставляется дата. В базе данных есть ряд функций, которые можно использовать в SQL-запросах. Функция NOW(), как вы догадались, возвращает текущее время. Список функций зависит от конкретной СУБД, наиболее распространенные будут рассмотрены в этой книге. Инструкция UPDATE - редактирование группы записей - имеет следующий синтаксис:

UPDATE имя_таблицы SET имя_столбца1=значение1, имя_столбца2=значение2, ... WHERE условие

Например, мы хотим изменить адрес электронной почты и количество записей, выводимых на одной странице для гостевой книги Васи Пупкина. Это можно сделать командой UPDATE gb2 SET email='vasya@itsoft.ru', quantity=9 WHERE gb_id=25 или UPDATE gb2 SET email='vasya@itsoft.ru', quantity=9 WHERE name='Vasya Pupkin'. Первый SQL-запрос более правильный, он изменит значения ячеек записи с идентификатором 25. Второй запрос изменит все записи, у которых в поле name стоит Vasya Pupkin. Будьте осторожны, не забывайте писать условия, иначе будут модифицированы все записи. И наконец, последняя инструкция DELETE - удаление группы записей, ее синтаксис:

DELETE FROM имя_таблицы WHERE условие

Для того чтобы удалить гостевую книгу Васи Пупкина, дайте команду DELETE FROM gb2 WHERE gb_id=25. Если вы забудете указать условие, то будут удалены все записи. Дайте команду DELETE FROM gb2, и все записи будут удалены. Выполните эту команду. А теперь восстановим данные при помощи инструкции INSERT. Дело в том, что инструкцию INSERT можно использовать совместно с инструкцией SELECT: INSERT INTO gb2 SELECT * FROM gb. Выполните эту команду, и все записи будут восстановлены.



Инструкция SELECT


Давайте выполним первый SQL-запрос. Для этого перейдите в раздел "Запросы". Выберите создание запроса в режиме конструктора. В появившемся диалоговом окне

нажмите кнопку "Закрыть". Далее в меню "Вид" выберите пункт "Режим SQL". У вас должно появится на экране окно для ввода SQL запросов. Введите: SELECT * FROM message WHERE gb_id=1. В меню "Запрос" выберите пункт "Запуск" или нажмите иконку с восклицательным знаком на экране. Появится результат обработки этого запроса.

Это таблица, содержащая все сообщения, относящиеся к гостевой книге с идентификатором 1. Как вы видите, некоторые столбцы не содержат полезной информации. Оператор SELECT позволяет указать нужные нам столбцы. С помощью меню "Вид->РежимSQL" вернитесь в предыдущее окно и введите SELECT message_id as id,name, http, email, subj, it_text, it_date FROM message WHERE gb_id=1. Нажмите "Выполнить запрос" и вы увидите теперь только перечисленные столбцы. Обратите внимание, что мы переименовали первый столбец. Он показывается под именем id, а не message_id. Теперь, давайте отсортируем по дате в порядке убывания: SELECT message_id as id,name, http, email, subj, it_text, it_date FROM message WHERE gb_id=1 ORDER BY it_date DESC.

Язык SQL довольно простой и интуитивно понятный. Рассмотрим синтаксис инструкции SELECT в общем случае.

SELECT [DICTINCT] имя_столбца [as новое_имя], имя_столбца2 [as новое_имя2], ... FROM таблица1 | имя_курсора1, таблица2 | имя_курсора2, ... [WHERE условие] [GROUP BY имя_столбца1, имя_столбца2, ...] [HAVING условие] [ORDER BY имя_столбца1 [DESC], имя_столбца2 [DESC2], ...]

О курсорах, GROUP BY и HAVING будет рассказано чуть позже, после того, как вы познакомитесь с инструкциями добавления, редактирования и удаления данных. А пока рассмотрим на частных примерах использование инструкции SELECT во всех остальных случаях. Начнем с ключевого слова DISTINCT. Использование этого атрибута позволяет нам избежать повторяющихся записей.
Допустим, мы хотим посмотреть всех авторов сообщений во всех гостевых книгах: SELECT name, http, email FROM message ORDER BY name. Обратите внимание, что теперь нет фильтра - инструкции WHERE, т.к. нас интересуют абсолютно все сообщения, вернее их авторы. Сортировка стоит по имени автора сообщения. Нас интересует сортировка по алфавиту(от меньшего к большему), поэтому атрибут DESC, который означает сортировать в обратном порядке мы не указываем. Результатом этого запроса будет таблица:



Вы видите, что в результате мы получили повторяющиеся записи, т.к. один человек мог написать несколько сообщений. Теперь добавьте слово DISTINCT: SELECT DISTINCT name, http, email FROM message ORDER BY name, и вы увидите, что дублей больше нет.


Наиболее интересным и сложным является инструкция WHERE. Мы будем рассматривать инструкцию: SELECT gb_id as id, name, quantity as q, description, it_date FROM gb, т.е. выборку идентификаторов гостевых книг, владельцев, количества сообщений, выводимых на одну страницу, описание и дату. Некоторые условия не имеют какого-либо осмысленного описания. Они приводятся в демонстративных целях. Для задания условия можно использовать:

Операторы + - * / = < >= <> !=. WHERE (gb_id+2)/2=10. Комбинирование условий AND OR NOT. WHERE gb_id=1 OR NOT quantity=10. Диапазоны: BETWEEN, NOT BETWEEN. WHERE gb_id NOT BETWEEN 10 AND 15. Списки: IN, NOT IN. WHERE name IN ('Игорь Тарасов', 'Евгений'). Нулевые значения: IS NULL, IS NOT NULL. WHERE name IS NULL Поиск по подстрокам: LIKE, NOT LIKE. WHERE description LIKE '*книга*'. Звездочка обозначает любое количество символов. В большинстве баз данных используется %, а не звездочка, как в MS Access.

Выполните все приведенные примеры, поварьируйте параметры, чтобы лучше усвоить материал.


Язык SQL


Получив начальные сведения об использование баз данных, мы перейдем к изучению языка SQL, посредством которого происходит взаимодействие с большинством СУБД. Наиболее доступной большинству читателей является СУБД MS Access. Она также хорошо подходит для изучения основ баз данных. Cкачайте с нашего сервера базу данных . В этой базе нас интересуют только две связанные таблицы: gb и message.

В этих таблицах лежат реальные данные, которые я взял с нашего сервера http://gb.itsoft.ru из СУБД MySQL. В таблице gb нет только столбца с паролями, но он нам и не нужен для усвоения материала.



Разбиение на группы


Далее мы рассмотрим разбиение записей на группы и применение к группам агрегирующих функций. Такого рода приемы используются для подсчета количества писем от каждого автора. Запрос SELECT name from message ORDER BY name вернет нам всех авторов, каждый автор будет повторяться столько раз, сколько писем он написал. Сгруппируем записи по авторам: SELECT name from message GROUP BY name ORDER BY name. Вы видите, что повторов теперь нет. Записи разбиты на группы, и теперь, если автор повторялся несколько раз, эти несколько записей объединены в одну группу. При использовании агрегирующих функций с GROUP BY они применяются не ко всем записям, а к каждой группе. Например: SELECT name, COUNT(*) from message GROUP BY name ORDER BY name возвращает таблицу из двух столбцов, в первом - имя автора, во втором - количество сообщений, которое он написал.

Группировать можно по нескольким полям. В предыдущем запросе мы получили информацию по всем сообщениям, не различая их по гостевым книгам. Если мы хотим получить количество сообщений по каждому автору в каждой гостевой книге, то запрос будет выглядеть так: SELECT name, gb_id, COUNT(*) from message GROUP BY name, gb_id ORDER BY name. Сначала происходит разбиение записей на группы (name, gb_id), затем по каждой группе подсчитывается количество записей в ней.

Если мы захотим получить рейтинг авторов по гостевым книгам, кто написал наибольшее количество сообщений, то надо поставить сортировку по третьему столбцу в обратном порядке: SELECT name, gb_id, COUNT(*) from message GROUP BY name, gb_id ORDER BY 3 DESC.

На записи, попадающие в группы, можно накладывать условия отбора. Делается это отдельным оператором HAVING. Оператор WHERE нам не может помочь, т.к. он действует на все записи. Сначала из таблицы выбираются записи, удовлетворяющие условию WHERE, затем происходит разбиение на группы, вычисляются агрегирующие функции и выполняется оператор HAVING для каждой группы отдельно, который отсеивает лишние строки. Оператор HAVING может обрабатывать те же условия, что и оператор WHERE, но в нем можно использовать агрегирующие функции, т.к. их значение при выполнении оператора HAVING уже известно. Рассмотрим еще раз пример с авторами и количеством писем, которое они написали: SELECT name, COUNT(*) from message GROUP BY name ORDER BY name. Как вы помните, большинство авторов написало по одному письму. Допустим, такие авторы нас не интересуют, мы хотим посмотреть авторов, которые пишут в наши гостевые книги регулярно. Для этого модифицируем запрос следующим образом: SELECT name, COUNT(*) from message GROUP BY name HAVING COUNT(*)>2 ORDER BY name.



Получить среднее количество выводимых сообщений


Напишите следующие SQL-запросы.
Получить среднее количество выводимых сообщений на страницу для гостевых книг с идентификатором больше 5. Получить количество гостевых книг, где заполнено поле name. Получить количество гостевых книг с заполненными полями name, email, http. Получить максимальный идентификатор гостевой книги.


Получите количество сообщений для каждой гостевой книги. Должно получиться

Получите количество сообщений для каждого поля http.






Напишите SQL-запросы:
Добавления своей гостевой книги. Изменения количества выводимых сообщений на страницу с 10 штук на 9. Удаления записей с пустыми полями name или email или description. Добавления сообщения в свою гостевую книгу, т.е. вставки записи в таблицу message2. Редактирования сообщения из своей гостевой книги. Удаления сообщения из своей гостевой книги. Удаления гостевой своей гостевой книги.






Напишите следующие запросы:
Выбрать из таблицы гостевых книг все записи. --"-- все записи, кроме столбца description. --"-- записи с идентификатором больше 10 и количеством страниц выводимых на страницу равным 10. --"-- записи с непустым адресом электронной почты. --"-- записи с непустым адресом электронной почты или полем http, но чтобы их идентификатор был больше 1 и не больше 5 или же не меньше 12 и меньше 19, сортировка должна производится по полям http, email Придумайте сами 20 запросов к таблице message и опубликуйте их в нашем форуме на сайте http://itsoft.ru.






Выведите все записи из таблицы gb, а также количество сообщений в каждой гостевой книге.





в подавляющем количестве случаев не


Тема баз данных применительно к сайтам в подавляющем количестве случаев не представляет особой сложности. Большинство сайтов в Интернет не имеют базы данных и своего собственного программного обеспечения. Для самого простейшего сайта типа "Визитная карточка" этого, как правило, и не требуется. А если все же Заказчик захочет гостевую книгу, опросник(голосование), список рассылки, конференцию, счетчик посетителей или другой стандартный скрипт, то такой CGI-скрипт можно позаимствовать с другого сервера, например, с ;-), где вы найдете все эти сервисы. Такого рода сервисы легко интегрируются в веб-сайт, и для посетителей совсем не заметно, что CGI-программа находится на другом сервере Интернет.

Очень небольшое количество сайтов в Интернет имеет собственную базу данных. Как правило, это интерактивные веб-сайты, которые что-то продают или предоставляют какие-либо виды услуг. Например, среди наших разработок это http://kapitan.ru, http://www.oxygensoftware.com, http://petek.ru/zippo/, http://test.itsoft.ru, http://gb.itsoft.ru и многие другие.

Теперь немного об архитектуре таких информационных систем. Все эти сайты имеют три уровня доступа к базе данных, т.е. все HTML-формы можно поделить на три типа. Первый уровень - пользовательский, предназначен для всех посетителей сайта без каких-либо ограничений. Как правило, в большинстве случаев пользователю предоставляется возможность только читать базу данных, т.е. CGI-скрипты выполняют инструкцию SELECT. Второй уровень для зарегистрированных посетителей сайта, вход осуществляется по логину и паролю. Во втором уровне доступа уже имеются ограниченные возможности редактирования и добавления данных. И третий уровень доступа - администраторский, он самый защищенный, т.к. здесь проверяется не только логин и пароль, но и IP-адрес, с которого произошло соединение. Иногда администраторский доступ находится на другом доменном имени, что позволяет ограничить к нему доступ средствами веб-сервера. Если же это доменное имя вынести на отдельный IP-адрес или на другой сервер, например, во внутреннюю локальную сеть, то доступ к нему можно ограничить firewoll'ом(брендмауером). Такого рода защита наиболее эффективная. В третьем уровне администратор имеет полный доступ к базе и может не только изменять содержимое, но и редактировать структуру базы.

Сайт kapitan.ru предоставляет турфирмам веб-интерфейс для регистрации фирмы в системе, а также веб-интерфейс для размещения путевок. Посетители сайта могут осуществлять поиск по базе путевок. На сайте test.itsoft.ru вы можете создать свой тест по любой тематике, ввести в него свои вопросы, а далее разместить этот тест на любом сайте в Интернет. Посетители вашего сайта смогут проходить тесты, а вы, как автор теста, можете добавлять, редактировать и удалять вопросы, просматривать статистику. Все эти интерактивные веб-сайты основаны на базах данных. Данные из HTML-формы передаются веб-серверу, веб-сервер передает их CGI-программе, CGI-программа обрабатывает их и соединяется с СУБД. CGI-программа может вносить изменения в БД или сделать запрос к базе, а затем полученные данные из базы передать веб-серверу.

Давайте рассмотрим подробнее вопрос: "Для чего необходимо использование базы данных при разработке веб-сайта подобного типа?". До сих пор находится много любителей изобретать велосипед, которые говорят: "А зачем мне база данных? Мы и сами с усами!". Основное назначение базы данных - хранение данных, многопользовательский доступ к ним и операции над ними.

В части III "Интерактивные веб-сайты или CGI" мы рассматривали программу чата. В этой программе используются функции, которые пишут данные в текстовый файл на сервере. Представьте ситуацию, когда в чат одновременно добавят сообщение два посетителя. Сообщение одного из них не будет добавлено, т.к. файл на сервере уже открыт на запись и заблокирован, второй процесс не сможет открыть этот файл на запись. Вероятность такого события, конечно, мала для редкопосещаемого сайта, но она возрастает при росте количества посетителей, поэтому такую ситуацию нельзя не учитывать. Часто начинающие веб-разработчики вместо того, чтобы использовать уже готовое решение на основе какой-либо СУБД, начинают писать скрипты, которые работают с данными в файле на сервере. Я утверждаю, что оправданным является только чтение данных из конфигурационного файла на сервере. В таком файле можно сохранять, например, цвет и шрифт текста, которым выводится сообщение пользователям. Тем самым, ваш скрипт поддается настройкам без модификации исходного кода. Достаточно по ftp залить новый конфигурационный файл на сервер, и вы поменяете настройки CGI-программы. Если же вы напишите свои функции записи, удаления, поиска, сортировки и выборки данных, которые будут работать в многопользовательском режиме, то по сути изобретете велосипед - напишите еще одну Систему Управления Базами Данных.

В заключение данного параграфа надо сказать несколько слов о том, что представляет собой СУБД и база данных. СУБД - это программное обеспечение, которое устанавливается на сервер и осуществляет работу с отдельными базами данных. На одном сервере может находится много баз данных. База данных в простейшем случае состоит из таблиц. Таблица в реляционной БД является единицей информации. Результатом любого запроса к БД будет таблица. Таблица может состоять из одной записи и одной ячейки, но, тем не менее, это именно таблица, а не отдельно взятая ячейка. Здесь и далее мы рассматриваем только реляционные СУБД. Бывают и нереляционные модели, например, объектно-ориентированные. Для примера я сделал два снимка таблицы из MS Access.


Таблица в режиме конструктора.



Содержимое таблицы.
Таблица состоит из записей(строк, кортежей) и колонок(столбцов, атрибутов). В скобках даны другие названия, которые вы можете встретить в публикациях по данной тематике. Каждый столбец имеет свое уникальное в пределах таблицы имя. Двух столбцов с одинаковыми именами в одной таблице быть не может. Данные в одном столбце имеют один тип. Типы данных бывают следующие:
char(n) - строка в точности из n символов, недостающие символы дополняются пробелами integer - целое date - дата varchar(n) - строка из меньше, чем n символов text - текст неопределенного размера
Здесь перечислены не все типы. Список типов может варьироваться в зависимости от используемой СУБД. Каждый столбец может иметь свойства: уникальности, непустой, значение по умолчанию. Уникальность означает, что в одном столбце данной таблицы не может быть двух одинаковых ячеек. Например, такое свойство надо указывать для столбца, обозначающего номер машины или страховки. Непустой (NOT NULL) означает, что в ячейках данного столбца должно присутствовать какое-либо значение. Если это свойство не указано, то в столбце могут присутствовать пустые ячейки, см. рисунок "содержимое таблицы": в столбце browser все ячейки пустые. Группа столбцов может образовывать первичный или внешний ключ. В нашем случае первичный ключ образует один столбец с именем cnt_id. Первичный ключ представляет собой набор ячеек, однозначно идентифицирующих запись, т.е. значение любой ячейки, не входящей в первичный ключ, должно зависеть от первичного ключа. Рассмотрим для примера таблицу сотрудников с полями: имя, фамилия, дата рождения, пол, должность.


Таблица сотрудников.
В данной таблице первичным ключом будут два столбца: фамилия и дата рождения. Значение всех остальных столбцов однозначно определяется по этому первичному ключу. Одной фамилии или любой другой комбинации недостаточно, т.к. в компании могут работать отец и сын или просто однофамильцы. Первичный ключ всегда уникален, двух записей в таблице с одинаковым первичным ключом быть не может. Это пример вымышленный, он служит просто демонстрацией того, что первичный ключ может состоять из нескольких полей. В действительности эти два поля не являются первичным ключом, т.к. в одной компании могут работать два близнеца. О проектировании баз данных и о выборе первичных ключей мы детальной поговорим в 14 главе.

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


Таблицы информационной системы "Гостевые книги".
Первая таблица описывает гостевые книги и содержит поля: gb_id - идентификатор книги - первичный ключ, http - URL сайта, name - имя владельца, email владельца, описание гостевой книги, количество сообщений, выводимых на одну страницу, и дата создания. Вторая таблица служит для хранения сообщений и содержит поля: message_id - идентификатор сообщения - первичный ключ, gb_id - ссылка на гостевую книгу - внешний ключ, имя автора сообщения, email автора, тема сообщения и т.п.. На одну запись в таблице "гостевые книги" приходится много записей в таблице сообщений, т.е. у каждой гостевой книги может быть несколько сообщений. Такого рода связь между таблицами называется "один ко многим". Она реализуется посредством внешнего ключа в таблице message. Для того чтобы получить все сообщения первой гостевой книги, в СУБД делается запрос: SELECT * FROM message WHERE gb_id=1. Посмотреть эти гостевые книги вы можете на наших сайтах http://itsoft.ru и http://perl.org.ru, а зарегистрировать свою гостевую книгу на сайте http://gb.itsoft.ru.



Выборка данных из нескольких таблиц


Теперь перейдем к рассмотрению запросов из нескольких таблиц. У нас в двух таблицах присутствует поле с одним и тем же названием, поэтому язык SQL предусматривает возможность указывать название поля в виде имя_таблицы.имя_столбца. В нашем случае: gb.gb_id или message.gb_id. Давайте выберем пары: идентификатор гостевой книги, идентификатор сообщения. На первый взгляд приходит следующее: SELECT gb.gb_id, message_id FROM gb, message. Однако результатом будет декартово произведение, т.е. всевозможные комбинации - 20 записей из таблицы gb умножить на 52 записи из таблицы message получается 1040 записей.

Но нас интересуют только осмысленные комбинации, т.е. там, где совпадают значения полей gb_id, поэтому необходимо добавить условие WHERE gb.gb_id=message.gb_id. Теперь записей стало 52, как и положено.

Для того чтобы выбрать другие поля, просто добавьте их в запрос, только лучше писать полное имя, т.е. таблица.поле: SELECT gb.gb_id, gb.name, message.name, message_id FROM gb, message WHERE gb.gb_id=message.gb_id. Теперь давайте посчитаем количество записей в каждой гостевой книге, только при этом выберем еще и название гостевой книги.

SELECT gb.name, COUNT(*) from gb, message WHERE gb.gb_id=message.gb_id GROUP BY gb.name



Заключение, или что осталось за кадром и почему


В данной главе мы познакомились с наиболее общими моментами использования языка SQL. В следующих главах на конкретных примерах мы познакомимся с созданием таблиц, изменением таблиц, созданием курсоров, более детально познакомимся с построением запросов к нескольким таблицам и другими вариантами применения языка SQL, которые лучше продемонстрировать и усвоить на конкретных примерах.



Добавление сообщения в базу


Минимальный набор программ, который необходим, чтобы пользователи сайта могли работать с гостевой книгой - это программа добавления сообщений и программа, которая будет выводить сообщения из нашей базы данных. На первом этапе администрирование гостевой книги можно вести через оболочку MS Access. Но это не всегда возможно, например, если ваш веб-сайт находится на другом континенте. Поэтому, в качестве самостоятельного упражнения вы будете разрабатывать программы для администрирования гостевой книги через Internet. Это программы просмотра, редактирования и удаления сообщений. Ну а пока приступим к рассмотрению первой CGI-программы - программы добавления сообщений в гостевую книгу. Запустите Microsoft Visual Studio и в меню File подменю New.

В появившемся диалоговом окне синим маркером выберите Win32 Console Application. Имя проекту дайте gbadd. Нажмите ОК и в следующем окне выберите тип приложения "An application that supports MFC".

Нажмите Finish и в следующем окне ОК. Первым делом выберите в меню Build подменю Set Active Configuration. И установите gbadd - Win32 Release. По умолчанию проект создается в отладочном режиме. Как правило, отладкой Visual Studio приходится не так уж часто пользоваться. Для отладки CGI-программ наиболее эффективно использовать отладочную печать. Но об отладке CGI-приложений мы поговорим отдельно, в одной из последующих глав данной книги.

В меню Projects выберите подменю Setting... и перейдите на вкладку Link. В поле Object/library modules добавте библиотеки: itcgi.lib и Ws2_32.lib, как показано на рисунке ниже.

В файл StdAfx.h после строки

#include <afxdtctl.h> // MFC support for Internet Explorer 4 Common Controls

добавьте строчку

#include <afxdb.h> // MFC ODBC database classes

Теперь переходим к редактированию функции _tmain. Слева на панели проекта разверните содержимое папки Globals и дважды щелкните на функцию _tmain.

Подключите библиотеку itcgi.h и отредактируйте функцию _tmain следующим образом.

///////////////////////////////////////////////////////////////////////////// // The one and only application object

#include <itcgi.h> CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { //Если не удается подключить библиотеку MFC, то завершаем работу if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) return -1;

CDatabase db; //объект db для связи с базой данных CString dsn, query; // dsn нашей базы данных и SQL-запрос

//dsn получаем из HTML-формы, см. выше // HTML-код - <input type="hidden" name="dsn" value="gb"> GetParamByName("dsn", dsn);

//формируем строку SQL-запроса query="INSERT INTO message(name,email,http,subj,it_text,smile,it_date,ip)\ VALUES ('%s', '%s', '%s', '%s', '%s','%s', NOW(), '##ip##')";

//Заменяем %s на значения соответствующих параметров, полученных //из HTML-формы. //Обратите внимание, что поля в SQL-запросе и //параметры в HTML-форме //следуют в одинаковом порядке. //Единичка означает, что заменять поля надо, начиная //со второго параметра //HTML-формы. Если бы вместо 1 стоял бы 0, то первый %s //заменился бы не на //значение параметра name, а на значение параметра dsn. //Параметры у нас нумеруются //с нуля, как это принято в языке Си. GetFullSQLQuery(query, 1);

//Последним параметром у нас идет IP адрес, с которого //пользователь зашел на наш сайт. Этот параметр мы получаем во время //выполнения нашего CGI-скрипта. Обратите внимание на прием, которым //мы воспользовались для вставки одной строки в другую, далее этот //прием будет очень часто применяться при работе со строками. query.Replace("##ip##", getenv("REMOTE_ADDR"));

//соединяемся с базой данных //в случае ошибки переходим на метку LABEL_END см. ниже try { db.OpenEx("DSN="+dsn, CDatabase::noOdbcDialog); } catch(CDBException* e) { //Если не удалось соединится с базой, выдаем сообщение //об ошибке и выходим. printError("Внимание! Ошибка!!!", "Error: %s\nState: %s\n", e->m_strError, e->m_strStateNativeOrigin); goto LABEL_END; }

//выполняем SQL-запрос try { db.ExecuteSQL(query); } catch(CDBException* e) { //Если не удалось выполнить SQL-запрос, выдаем сообщение //об ошибке и выходим. printError("Внимание! Ошибка!!!", "%s\n%s\nquery=%s", e->m_strError, e->m_strStateNativeOrigin, query); goto LABEL_END; }

//Выдаем HTTP-заголовок и возвращаем пользователя обратно //в гостевую книгу. printf("Location: %s\n\n",getenv("HTTP_REFERER"));

//Если во время выполнения скрипта произойдет ошибка, //то будет напечатано сообщение при помощи функции printError //и программа завершит свою работу. LABEL_END: if (db.IsOpen()) db.Close(); return 0; }

Соберите этот проект, кнопка F7. Затем скопируйте файл gbadd.exe из директории Release данного проекта в директорию /cgi-bin вашего веб-сайта. Вышеприведенную HTML-форму, как вы помните, мы сохранили в файле index.html в директории gb вашего веб-сайта. В броузере откройте страницу гостевой книги http://yoursite.ru/gb/index.html. Заполните поля ввода и нажмите кнопку "Опубликовать". Сообщение будет добавлено в базу данных. Можете открыть в MS Access созданную вами базу данных и убедится, что сообщения добавляются.



Отображаем сообщения из базы


Следующим шагом будет написание CGI-программы, которая выдает сообщения из базы данных. Вызываться эта программа будет несколько иначе. Добавьте в файл /gb/index.html после HTML-формы следующую строчку:

<!--#include virtual="/cgi-bin/gbshow.exe?dsn=gb"-->

При запросе пользователем файла /gb/index.html будет вызвана CGI-программа /cgi-bin/gbshow.exe и ей методом GET будет передан параметр dsn. Более подробно технологию Server Side Includes мы рассматривали в шестой главе.

Описанным выше образом создайте проект с названием gbshow. Код функции _tmain приведен ниже:

#include <itcgi.h>

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { //Если не удается подключить библиотеку MFC, то завершаем работу if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) return -1;

CDatabase db; //объект db для связи с базой данных CString dsn; // dsn нашей базы данных и SQL-запрос CString HTML; //шаблон HTML-сообщения

//dsn получаем из CGI-запроса, см. выше GetParamByName("dsn", dsn);

//соединяемся с базой данных //в случае ошибки переходим на метку LABEL_END см. ниже try { db.OpenEx("DSN="+dsn, CDatabase::noOdbcDialog); } catch(CDBException* e) { //Если не удалось соединится с базой, выдаем сообщение //об ошибке и выходим.

printError("Error: %s\nState: %s\n", e->m_strError, e->m_strStateNativeOrigin); goto LABEL_END; }

//Инициализируем HTML-шаблон сообщения HTML="<br><br>\ <table align=center width=100% bgcolor=#000000 cellspacing=1 cellpadding=2>\ <tr>\ <td bgcolor=#EEEEEE width=85%><img src=\"%%smile%%\"> %%subj%%\ <tr>\ <td bgcolor=#FFFFFF>\ <p align=justify>%%it_text%%\ <div >%%it_date%%<br>\ <a href=\"mailto:%%email%%\">%%name%%</a><br>\ <a href=\"http://%%http%%\">http://%%http%%</a>\ </div>\ </table>";

//печатаем HTTP-заголовок printf("Content-type: text/html\n\n");

//получаем записи из таблицы базы данных //первый параметр db - объект для связи с базой //второй SQL-запрос //третий HTML-шаблон одной записи, в шаблоне названия //полей должны соответствовать названиям в таблице базы данных //поля в шаблоне могут идти в любом порядке и повторяться несколько раз GetHTMLFromSQL(db, "SELECT * FROM message", HTML, 0);

//после выполнения функции GetHTMLFromSQL // в переменной HTML будет сохранен результат SQL-запроса

printf("%s", HTML);

LABEL_END: if (db.IsOpen()) db.Close();

return 0; }

Соберите проект и скопируйте файл gbshow.exe в папку /cgi-bin вашего веб-сайта. В броузере откройте страницу гостевой книги http://yoursite.ru/gb/index.html и вы увидите сообщения из гостевой книги.



с вами написали гостевую книгу,


В данной главе мы с вами написали гостевую книгу, но она далека от совершенства. Первое, что необходимо реализовать - это постраничный вывод сообщений, т.к., если в гостевой книге будет, скажем, порядка 100 сообщений, то смотреть их на одной странице неудобно. Вторым шагом разработайте четыре программы для администрирования гостевой книги. Это программа gbadminshow - показывает сообщения с IP адресами тех, кто их написал, и кнопками для редактирования и удаления сообщения. Кнопка удаления сообщения ссылается на вторую программу - gbdel, которая удаляет сообщение. Программа gbdel получает на вход dsn и id сообщения, которое необходимо удалить из базы. Соответственно, она выполняет запрос DELETE FROM message WHERE id=идентификатор сообщения. Кнопка редактирования сообщений ссылается на программу gbeditshow, которая отображает наше сообщение в HTML-форме, где мы сможем его редактировать. Эта HTML-форма аналогична HTML-форме по добавлению сообщений за исключением кнопки "Отправить". Кнопка "Отправить" ссылается на четвертую программу gbedit. Программа gbedit отличается от программы gbadd тем, что выполняет SQL-запрос UPDATE, а не INSERT.


Введение в ODBC


ODBC - O... Data Base Conn представляет собой надстройку над различными драйверами к системам управления базами данных. Поясним в чем смысл этой надстройки. В принципе можно работать с СУБД напрямую через драйвер, но это в случае если



Выполняем SQL-запросы к базе данных


База данных MS Access и многие другие локальные (или, как их еще называют, десктопные СУБД) не позволяют подключаться к ним по сети, также бывает, что невозможно осуществить соединение по порту базы данных из-за межсетевых экранов (firewall'ов, брендмауера или прокси-серверов). В таких случаях полезно иметь CGI-программу, которая позволит нам выполнять SQL-запросы через Internet. Такая CGI-программа - просто незаменимый инструмент при разработке интерактивных веб-сайтов на основе баз данных. HTML-форма для выполнения sql-запросов выглядит следующим образом:

HTML-код приведен ниже:

<form action="/cgi-bin/sql_query.exe"> <input type=hidden name=dsn value=gb>

<textarea name=query rows=10 cols=80></textarea> <input type=submit value="Отправить запрос"> </form>

Данная программа соединяется с источником данных dsn, выполняет sql-запрос query и возвращает пользователя на страницу с HTML-формой для SQL-запроса, в случае, если результатом запроса не является таблица. Создайте еще один проект в среде MS Visual Studio с именем sql_query. Ниже приведен исходный код функции _tmain с комментариями:

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) {

if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) return -1;

CString test; CDatabase db; CString dsn, query;

//получаем параметры из HTML-формы GetParamByName("dsn", dsn); GetParamByName("query", query);

//соединяемся с базой try { db.OpenEx("DSN="+dsn, CDatabase::noOdbcDialog); } catch(CDBException* e) { printError("Error: %s\nState: %s\n", e->m_strError, e->m_strStateNativeOrigin); goto LABEL_END; }

//определяем тип запроса //если SELECT, то результатом будет таблица, которую мы и печатаем //иначе просто выполняем запрос, ничего не печатая test=query; test.MakeUpper(); if( test.Find("SELECT",0)==0 ) {

CRecordset rs(&db);

//выполняем sql-запрос try { rs.Open(CRecordset::dynaset,query); } catch(CDBException* e) { printError("%s\n%s\nquery=%s\n", e->m_strError, e->m_strStateNativeOrigin, query); goto LABEL_END; }

//если запрос выполнен успешно, печатаем HTTP-заголовок printf("Content-type: text/html\n\n");

//и выводим таблицу printf("<table border=1><tr>\n");

CODBCFieldInfo info; int i;

//печатаем названия колонок for (i=0;i<rs.GetODBCFieldCount();i++) { rs.GetODBCFieldInfo(i,info); printf("<td><font color=green>%s</font></td>\n", info.m_strName); } printf("</tr>");

//печатаем записи таблицы while (!rs.IsEOF()) { printf("<tr>\n"); CString col; for (int i=0;i<rs.GetODBCFieldCount();i++) { rs.GetFieldValue(i,col); printf(" <td>%s\n",col); } rs.MoveNext(); } printf("</table>\n");

if (rs.IsOpen()) rs.Close();

}

else { try { db.ExecuteSQL(query); } catch(CDBException* e) { printError("%s\n%s\nquery=%s", e->m_strError, e->m_strStateNativeOrigin, query); goto LABEL_END; }

printf("Location: %s\n\n",getenv("HTTP_REFERER")); }

LABEL_END: if (db.IsOpen()) db.Close();

return 0; }

Соберите этот проект и выполните несколько sql-запросов при помощи этой программы.



Взаимодействие через ODBC c MS Access


В этом параграфе мы рассмотрим создание гостевой книги на основе СУБД MS Access и приложения на языке Си, разработанного в среде MS Visual C++ 6.0. У вас должен быть запущен и настроен веб-сервер Apache, и работать веб-сайт с директорией /cgi-bin (см. 6 главу), также вам необходимо иметь MS Visual Studio 6.0 и библиотеку itcgi под windows, которую вы можете взять на нашем сайте http://itsoft.ru/. Библиотека itcgi представляет из себя два заголовочных файла - itcgi.h и lstring.h, которые необходимо скопировать в директорию C:\Program Files\Microsoft Visual Studio\VC98\Include и файла itcgi.lib, который необходимо положить в директорию C:\Program Files\Microsoft Visual Studio\VC98\Lib. Прежде, чем приступить к созданию базы данных и программированию, давайте создадим HTML-форму добавления сообщения в гостевую книгу, а также шаблон для вывода сообщений из гостевой книги. Для добавления сообщений в гостевую книгу мы будем использовать следующую HTML-форму.

Ваше имя:
Email:
http://
Тема сообщения:

Ниже приведен ее HTML-код:

<form method=post action="/cgi-bin/gbadd"> <input type="hidden" name="dsn" value="gb"> <table> <tr><td width=20%> Ваше имя: </td><td width=80%> <input type="text" name="name" size="60" MAXLENGTH="80" > </td></tr> <tr><td width=20%> Email: </td><td width=80%> <input type="text" name="email" size="60" MAXLENGTH="80" ><br> </td></tr> <tr><td width=20%> http:// </td><td width=80%> <input type="text" name="http" size="60" MAXLENGTH="80" ><br> </td></tr> <tr><td width=20%> Тема сообщения: </td><td width=80%> <input type="text" name="subj" size="60" MAXLENGTH="80" ><br> </td></tr> <tr><td width=100% colspan=2> <textarea name="it_text" wrap cols="66" rows="7" MAXLENGTH="1024"></textarea><br> <input type=radio checked name="smile" value="/icon1.gif"> <img src="/icon1.gif"> <input type=radio name="smile" value="/icon2.gif"> <img src="/icon2.gif"> <input type=radio name="smile" value="/icon3.gif"> <img src="/icon3.gif"> <input type=radio name="smile" value="/icon4.gif"> <img src="/icon4.gif"> <input type=radio name="smile" value="/icon5.gif"> <img src="/icon5.gif"> <input type=radio name="smile" value="/icon6.gif"> <img src="/icon6.gif"> <input type=radio name="smile" value="/icon7.gif"> <img src="/icon7.gif"> <input type=radio name="smile" value="/icon8.gif"> <img src="/icon8.gif"> <input type=radio name="smile" value="/icon9.gif"> <img src="/icon9.gif"> <input type=radio name="smile" value="/icon10.gif"> <img src="/icon10.gif"> <br> <input type=radio name="smile" value="/icon11.gif"> <img src="/icon11.gif"> <input type=radio name="smile" value="/icon12.gif"> <img src="/icon12.gif"> <input type=radio name="smile" value="/icon13.gif"> <img src="/icon13.gif"> <input type=radio name="smile" value="/icon14.gif"> <img src="/icon14.gif"> <input type=radio name="smile" value="/icon15.gif"> <img src="/icon15.gif"> <input type=radio name="smile" value="/icon16.gif"> <img src="/icon16.gif"> <input type=radio name="smile" value="/icon17.gif"> <img src="/icon17.gif"> <input type=radio name="smile" value="/icon18.gif"> <img src="/icon18.gif"> <input type=radio name="smile" value="/icon19.gif"> <img src="/icon19.gif"> <input type=radio name="smile" value="/icon20.gif"> <img src="/icon20.gif"> <br> <br><center> <input type="submit" value="Опубликовать"> </center> </td></tr></table> </form>


Создайте директорию gb на вашем веб-сайте и в ней index.html, в котором и разместите вышеприведенную HTML-форму.

Каждое сообщение будет выведено в следующем шаблоне.

%%subj%%
%%it_text%% %%it_date%%


Вот его HTML-код:

<br><br> <table align="center" width="100%" bgcolor="#000000" cellspacing="1" cellpadding="2"> <tr> <td bgcolor="#EEEEEE" width="85%"><img src="%%smile%%"> %%subj%% </td> </tr>

<tr> <td bgcolor="#FFFFFF"> <p align="justify">%%it_text%% <div align="right">%%it_date%%<br> <a href="mailto:%%email%%">%%name%%</a><br> <a href="http://%%http%%" target="_parent">http://%%http%%</a> </div> </td> </tr>

</table>

Создайте в Access базу данных с именем gb - сокращение от guestbook, а в этой базе таблицу message.



Далее идет список полей таблицы.

Название Тип
subj текстовый 64 символа
name текстовый 32 символа
email текстовый 64 символа
http текстовый 32 символа
it_text поле МЕМО
it_date Дата/время
smile текстовый 64 символа
id счетчик, длинное целое, значения последовательные, совпадения не допускаются
ip текстовый 16 символа
В панели управления(меню Пуск->Настройки->Панель управления) выберите значок "Источники данных ODBC" и щелкните на нем дважды. У вас должно появиться следующее диалоговое окно.



Перейдите на вкладку "Системный DSN".



Нажмите кнопку "Добавить".



В появившемся диалоговом окне синим маркером должна выделяться запись Microsoft Access Driver (*.mdb). Если это не так, то найдите такую запись и щелкните на ней мышкой. Затем нажмите кнопку "Готово". В появившемся диалоговом окне вам необходимо указать "Имя источника данных" и нажать кнопку "Выбрать", чтобы указать саму базу данных. Имя источника данных задайте gb - сокращение от guestbook.В результате, это окно должно выглядеть примерно следующим образом.



В этом и следущем диалоговом окне нажмите кнопку "OK". Так вы создали DSN с помощью которого наши CGI-программы далее будут соединяться и работать с базой данных.

Здесь рисунок для демонстрации работы через ODBC


Здесь же рассказываются основные понятия, описывается DSN, классы библиотеки MFC CDatabase, CRecordset.



Администрирование


В наших разработках мы очень часто используем СУБД MySQL. Хотя MySQL не является полноценной СУБД, т.к. не поддерживает очень важных элементов БД, например, таких как: внешние ключи, курсоры, триггеры, ограничение на значение, подзапросы и др. Однако, на сегодняшний день, MySQL является одной из самых быстрых и популярных СУБД в сети Интернет, и может использоваться для большинства информационных систем небольшого масштаба. Для серьезных информационных систем лучше использовать более функциональные СУБД, например ORACLE или InterBase. К обсуждению необходимых механизмов СУБД мы вернемся в главе "Проектирование баз данных".

Основными задачами администрирования системы управления баз данных являются:

запуск, мониторинг и завершение работы сервера управление пользователями и их привилегиями создание и удаление баз данных резервирование и копирование данных

Для взаимодействия администратора и пользователей с СУБД MySQL в комплект поставки MySQL входят следующие утилиты командной строки:

mysqladmin Основная утилита администратора СУБД MySQL, ее применение мы детально рассмотрим ниже.

mysqlshow Позволяет просматривать список баз данных и по каждой базе данных список таблиц.

mysql Позволяет выполнять SQL-запросы.

mysqldump Позволяет получить содержимое отдельных баз данных.

mysqlimport Загружает данные из текстового файла в таблицу.

Для запуска СУБД MySQL в Windows запустите mysqld.exe из директории mysql\bin\. В Unix демон mysql стартует автоматически при загрузке системы. Он запускается программой /usr/local/etc/rc.d/mysql-server.sh. Для завершения работы сервера MySQL используется команда 'mysqladmin shutdown'. Состояние сервера можно узнать командой 'mysqladmin status'. Ниже приведен результат ее выполнения на нашем сервере, правда с авторскими комментариями. Также имеется команда 'mysqladmin extended-status' для получения более детальной информации.

Uptime: 1437330 //время работы сервера в секундах Threads: 1 //число потоков, взаимодействующих //с базой в данный момент

Questions: 2717244 //всего запросов Slow queries: 10 //количество медленных запросов Opens: 3013 // число таблиц, открытых с //момента запуска сервера Flush tables: 1 Open tables: 64 // количество открытых таблиц Queries per second avg: 1.890 // среднее количество запросов в секунду

Работа с СУБД MySQL в операционной системе Unix начинается c установки пароля администратора:

mysqladmin -u root password 'пароль'.

Если вы работаете в Windows98, то такую операцию проделывать не надо. Аутентификация в Windows не работает, да она вам для учебных целей и не потребуется. Следующие несколько абзацев посвящены разграничению доступа и администрированию пользователей. Если у вас не стоит задачи держать на своем сервере несколько баз данных и разграничивать к ним доступ по пользователям, то можете перейти сразу к следующему этапу администрирования - созданию и удалению баз данных.

Разграничение доступа в MySQL основано на пяти таблицах из базы данных mysql. Это таблицы:

user db host tables_priv columns_priv

Первый этап разграничения доступа основывается на таблице user, вот ее структура:

CREATE TABLE user ( Host char(60) NOT NULL default '', User char(16) NOT NULL default '', Password char(16) NOT NULL default '', Select_priv enum('N','Y') NOT NULL default 'N', Insert_priv enum('N','Y') NOT NULL default 'N', Update_priv enum('N','Y') NOT NULL default 'N', Delete_priv enum('N','Y') NOT NULL default 'N', Create_priv enum('N','Y') NOT NULL default 'N', Drop_priv enum('N','Y') NOT NULL default 'N', Reload_priv enum('N','Y') NOT NULL default 'N', Shutdown_priv enum('N','Y') NOT NULL default 'N', Process_priv enum('N','Y') NOT NULL default 'N', File_priv enum('N','Y') NOT NULL default 'N', Grant_priv enum('N','Y') NOT NULL default 'N', References_priv enum('N','Y') NOT NULL default 'N', Index_priv enum('N','Y') NOT NULL default 'N', Alter_priv enum('N','Y') NOT NULL default 'N', PRIMARY KEY (Host,User) )

При попытке соединится с сервером баз данных некоторого пользователя alex с компьютера alex.host.ru, MySQL сначала проверяется, разрешено ли этому пользователю соединятся с сервером с компьютера alex.host.ru. Эта проверка выполняется на основании значений полей Host и User таблицы user. Если в таблице есть запись для пользователя alex и компьютера alex.host.ru, то соединение произойдет успешно. Если же такой записи нет, то соединение будет отвергнуто. В таблице user для пользователя alex должны быть перечислены все компьютеры, с которых ему разрешено подключаться к системе. В поле Host можно задавать значения, используя знак %, который означает произвольный набор символов. Так, например, если вы укажете просто знак процента, то это будет означать, что пользователю alex разрешено подключаться к серверу баз данных с любого компьютера. Если же вы напишите %.ru, то будет означать, что alex'у можно подключаться с любого компьютера в зоне .ru. Одновременно с проверкой полей User и Host проверяется третье поле - Password. Если оно пустое, то пароль не требуется. Если же в этом поле есть какое-то значение, то происходит проверка пароля. Если и пароль совпадает, то соединение с сервером происходит успешно. Как вы, наверное, обратили внимание в таблице user есть и другие атрибуты. Эти атрибуты задают полномочия пользователя по отношению ко всем базам данных, размещенным на данном сервере. Для простых пользователей все полномочия должны быть запрещены, т.е. установлены в значение 'N'. Вы можете некоторых пользователей наделить особыми правами и дать им некоторые полномочия, но мы не рекомендуем так делать, т.к. эти полномочия относятся абсолютно ко всем базам данных. Мы рекомендуем наделять конкретного пользователя полномочиями на уровне конкретной базы данных, таблиц и столбцов баз данных, о чем будет рассказано ниже.

На самом деле, вам вряд ли придется строить системы, расположенные на разных компьютерах, т.е. чтобы веб-сервер с CGI-программами крутился на одной машине, а сервер баз данных работал на другой. У такого подхода, конечно, есть свои плюсы и минусы. К минусам такого решения относится бОльшая стоимость, задержки при передаче данных по сети между сервером и клиентом, возможные аппаратные или программные сбои сети или одного из компьютеров ведут к неработоспособности всей системы в целом. Плюсом является то, что вы разделяете задачи по различным компьютерам. Настроить и управлять работой одной программы будь то MySQL, Apache или Sendmail проще, чем при их одновременной работе. Вы застрахованы от проблем, связанных с нехваткой ресурсов, конфликтов приложений. Нам видится, что такой подход будет оправдан при построение систем в больших масштабах. Мы используем централизованный подход, у нас все работает на одном сервере. Соединения с других машин с сервером MySQL вообще закрыты межсетевым экраном (firewall'ом).



После соединения с сервером MySQL пользователь соединяется с конкретной базой данных на этом сервере. Далее разграничение доступа происходит на основании таблицы db:

CREATE TABLE db ( Host char(60) NOT NULL default '', Db char(64) NOT NULL default '', User char(16) NOT NULL default '', Select_priv enum('N','Y') NOT NULL default 'N', Insert_priv enum('N','Y') NOT NULL default 'N', Update_priv enum('N','Y') NOT NULL default 'N', Delete_priv enum('N','Y') NOT NULL default 'N', Create_priv enum('N','Y') NOT NULL default 'N', Drop_priv enum('N','Y') NOT NULL default 'N', Grant_priv enum('N','Y') NOT NULL default 'N', References_priv enum('N','Y') NOT NULL default 'N', Index_priv enum('N','Y') NOT NULL default 'N', Alter_priv enum('N','Y') NOT NULL default 'N', PRIMARY KEY (Host,Db,User) )

На основе этой таблицы проверяется имеет ли доступ к данной базе данных пользователь с компьютера Host. Если подходящей записи нет, то в соединении с выбранной базой данных будет отказано. Если соответствующая запись будет найдена, то соединение пройдет успешно, и пользователь будет наделен полномочиями по работе с данной базой. Под пользователем здесь, конечно, надо понимать набор CGI-программ, которые будут работать с базой. Здесь вы уже смело можете ставить 'Y' во все позиции. Значение поле Host задается точно также, как и для таблицы user. В случае, если в этом поле пустая строка, то просматривается таблица host, на предмет ограничения привилегий.

CREATE TABLE host ( Host char(60) NOT NULL default '', Db char(64) NOT NULL default '', Select_priv enum('N','Y') NOT NULL default 'N', Insert_priv enum('N','Y') NOT NULL default 'N', Update_priv enum('N','Y') NOT NULL default 'N', Delete_priv enum('N','Y') NOT NULL default 'N', Create_priv enum('N','Y') NOT NULL default 'N', Drop_priv enum('N','Y') NOT NULL default 'N', Grant_priv enum('N','Y') NOT NULL default 'N', References_priv enum('N','Y') NOT NULL default 'N', Index_priv enum('N','Y') NOT NULL default 'N', Alter_priv enum('N','Y') NOT NULL default 'N', PRIMARY KEY (Host,Db) )

Привилегии из таблицы host перекрывают привилегии из таблицы db. Но это только при условии, что в таблице db не задано имя компьютера. Также учтите, что это привилегии для всех пользователей, соединившихся с компьютера host. Как правило, эта таблица не используется.

Аналогичным образом, для разграничения доступа в сложных системах, имеются таблицы для разграничения доступа на уровне таблиц и столбцов.

CREATE TABLE tables_priv ( Host char(60) NOT NULL default '', Db char(64) NOT NULL default '', User char(16) NOT NULL default '', Table_name char(64) NOT NULL default '', Grantor char(77) NOT NULL default '', Timestamp timestamp(14) NOT NULL, Table_priv set('Select','Insert','Update','Delete','Create', 'Drop','Grant','References','Index','Alter') NOT NULL default '', Column_priv set('Select','Insert','Update','References') NOT NULL default '', PRIMARY KEY (Host,Db,User,Table_name) )

CREATE TABLE columns_priv ( Host char(60) NOT NULL default '', Db char(64) NOT NULL default '', User char(16) NOT NULL default '', Table_name char(64) NOT NULL default '', Column_name char(64) NOT NULL default '', Timestamp timestamp(14) NOT NULL, Column_priv set('Select','Insert','Update','References') NOT NULL default '', PRIMARY KEY (Host,Db,User,Table_name,Column_name) )

Для внесения изменений в таблицы разграничения прав доступа имеется две возможности. Первая - непосредственное выполнение следующих SQL-запросов:

INSERT INTO user (host, user, password) VALUES( 'localhost', 'alex', password('HofdWf67') )

INSERT INTO db (host, user, db, select_priv, Insert_priv, Update_priv, Delete_priv, Create_priv, Drop_priv, References_priv, Index_priv, Alter_priv) VALUES( 'localhost', 'alex', 'music', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y', 'Y' )

Мы вносим в таблицу user нового пользователя alex. Обратите внимание, что пароли хранятся в зашифрованном виде, т.е., даже если кто-то получит резервную копию ваших баз данных или пароль администратора, то узнать пароли пользователей он не сможет. И второй запрос вносит в таблицу db полный список разрешений на использование пользователем alex базы данных music. Для выполнения этих запросов запустите

mysql -u root -p -Dmysql // в Unix mysql -Dmysql // в Windows

При вводе SQL-запросов через mysql ставьте в конце точку с запятой - ';'. После выполнения данных запросов выйдите из утилиты mysql и выполните команду 'mysqladmin reload' для перезагрузки таблиц прав доступа с внесенными изменениями.

Второй способ - это использование SQL-команд GRANT и REVOKE. Ниже приведен синтаксис и примеры использования этих команд.

GRANT priv_type [(column_list)] [, priv_type [(column_list)] ...] ON {tbl_name | * | *.* | db_name.*} TO user_name [IDENTIFIED BY 'password'] [, user_name [IDENTIFIED BY 'password'] ...] [WITH GRANT OPTION]

REVOKE priv_type [(column_list)] [, priv_type [(column_list)] ...] ON {tbl_name | * | *.* | db_name.*} FROM user_name [, user_name ...]

priv_type может быть одним из следующих ключевых слов: ALL PRIVILEGES FILE RELOAD ALTER INDEX SELECT CREATE INSERT SHUTDOWN DELETE PROCESS UPDATE DROP REFERENCES USAGE

//следующий SQL-запрос равносилен двум SQL-запросам, //которые мы рассмотрели выше GRANT ALL ON music.* TO alex@localhost IDENTIFIED BY 'HofdWf67' WITH GRANT OPTION

После внесения изменений этими командами, перезагрузка таблиц при помощи команды 'mysqladmin reload' не требуется. В случае, если пользователя alex не было, то он создается с паролем 'HofdWf67'. Если же он уже был в таблице user, то для него меняется пароль. WITH GRANT OPTION позволяет alex'у давать привилегии на базу music другим пользователям.

И последнее, о чем хотелось бы здесь сказать, о смене паролей. Пароли могут перехватываться, теряться, оказаться известными посторонним людям и т.д. и т.п., поэтому рекомендуется с определенной периодичностью их менять. Периодичность зависит от уровня надежности защиты вашей системы. На гостевую книгу пароль можно не менять совсем. Если с ней что-то случится, вы всегда сможете восстановить ее с резервной копии, ну потеряете в худшем случае пару последних новых сообщений. При функционировании сложной многопользовательской системы, смена паролей может происходить раз в 3-6 месяцев. Производить смену паролей все же удобнее командой:

UPDATE user SET password=password('newpwd') WHERE user='username'

После чего не забудьте сделать 'mysqladmin reload'. Команда GRANT здесь не подходит, т.к. она завязана еще и на привилегиях пользователей, вспоминать которые нам ни к чему.



Далее мы переходим к созданию и удалению баз данных. Это можно сделать либо командой SQL-запроса 'CREATE DATABASE databasename', либо при помощи команды 'mysqladmin -u root -p create databasename'. Удаление аналогично - 'DROP DATABASE databasename' или же 'mysqladmin -u root -p drop databasename'.

Копирование баз данных в MySQL осуществляется простым копированием файлов. Все базы данных MySQL обычно лежат либо в директории C:\mysql\data в операционной системе Windows, либо же в /var/db/mysql в операционной системе Unix. Для копирования базы вам нужно просто скопировать соответствующую директорию. Для переименования просто переименовать директорию. Хотите создать дубль базы, скопируйте директорию в этот же каталог под другим именем. Резервное копирование баз данных делается опять же таки простым копированием, но лучше также еще и заархивировать, т.к. базы ужимаются в десять раз. Небольшое пояснение по поводу использования mysqldump, эта утилита не предназначена для резервного копирования баз данных! С помощью mysqldump можно просматривать структуру таблиц баз данных, извлекать сами данные и сохранять их в текстовые файлы. Например, 'mysqldump -d mysql' выдаст SQL-код приведенных выше таблиц. Флаг -d означает, что нужно вывести только структуру таблиц без данных. Для полного дампа базы дайте команду

mysqldump -T /tmp --fields-terminated-by="|" databasename

Особо хочется отметить веб-интерфейс phpMyAdmin, который позволяет администрировать MySQL через веб и выполнять все рассмотренные выше задачи. Если вы собираетесь плотно работать с MySQL, то обязательно установите phpMyAdmin. Последнюю версию phpMyAdmin можно взять по следующим адресам:

http://phpwizard.net/projects/phpMyAdmin/ http://phpmyadmin.sourceforge.net/



Анализ посетителей веб-сайта


Рассмотрим одни из самых простейших и первых скриптов, которые появились на веб-сайтах - это учет и анализ посетителей сайта. Этим скриптам необходимо сохранять, редактировать и удалять информацию на сервере, поэтому без СУБД никак не обойтись. Каждый раз при заходе посетителя на страницу, будет вызываться CGI-скрипт счетчика, который будет записывать информацию об одном посещении в нашу базу. Ниже приведена таблица, где мы будем хранить информацию о посещении пользователем страницы нашего сайта:

CREATE TABLE hit ( owner_id int unsigned NOT NULL default '0', // id счетчика it_date datetime default NULL, // дата посещения

shref varchar(255) default NULL, // сервер, с которого пользователь // пришел на нашу страницу

href varchar(255) default NULL, // страница сервера, с которой // пользователь пришел на нашу // страницу

ip varchar(16) default NULL, // ip-адрес пользователя os varchar(32) default NULL, // операционная система browser varchar(32) default NULL,// броузер version varchar(32) default NULL,// версия броузера x int unsigned default NULL, // разрешение экрана по горизонтали y int unsigned default NULL, // разрешение экрана по вертикали depth int unsigned default NULL, // количество цветов (глубина цвета) cookie enum('0','1') default '0',// поддерживается ли механизм Cookie java enum('0','1') default '0', // поддерживается ли java spage varchar(255) default NULL, // url посещаемого сервера page varchar(255) default NULL, // url посещаемой страницы frame enum('0','1') default '0', // страница с фреймом или без js char(3) default '0' // версия javascript )

Данная информация собирается простым кодом на JavaScript, см. главу пять, ниже приведен код счетчика: <script language="JavaScript">js=10;</script><script language="JavaScript1.1"> js=11;</script><script language="JavaScript1.2">js=12</script> <script language="JavaScript1.3">js=13;</script>

<script language="JavaScript"> d=document;n=navigator;s=screen;d.cookie="testparam=testvalue"; d.write('<img width=1 height=1 src="http://itsoft.ru/common/counter?id=1' + '&r='+escape(d.referrer)+ '&n='+escape(n.appName)+ '&v='+escape(navigator.appVersion)+ '&c='+(d.cookie?"1":"0")+ '&f='+((self!=top)?"1":"0")+ '&j='+(n.javaEnabled()?"1":"0")+ '&x='+s.width+ '&y='+s.height+ '&d='+(s.colorDepth?s.colorDepth:s.pixelDepth)+ '&js='+js+ '&o='+n.platform+'&'+Math.random()+'">'); </script>


Обратите внимание, что код на JavaScript состоит из двух блоков. В первом определяется версия JavaScript, а во втором идет код самого счетчика. Самым последним параметром передается случайное число, оно не используется, но необходимо, чтобы броузеры и прокси-сервера не кэшировали наш счетчик. Если бы этого случайного числа не было, то скрипт счетчика вызывался бы только один раз, при первом заходе пользователя, а в остальных случаях броузер или прокси-сервер выдавал бы прозрачный GIF из кэша.

Нас интересуют отчеты, которые показывают: количество уникальных посетителей сайта, общее количество запрошенных страниц, среднее количество страниц, запрашиваемых одним пользователем, самые популярные страницы нашего сайта, страницы, с которых пользователи приходят на наш сайт, динамика визитов, операционные системы и броузеры пользователей.

Сам скрипт counter, который вносит в таблицу hit новую запись об очередном открытии нашей страницы, очень прост. Но он отличается от CGI-скриптов, рассмотренных ранее тем, что выдает прозрачный GIF-файл размером 1х1 пиксел. Все счетчики устроены по такому принципу, т.к. единственная возможность вызвать CGI-скрипт одновременно с загрузкой HTML-страницы - это вызвать его в команде img.

/* * (c) Copyright 1995-2000, Igor Tarasov * http://itsoft.ru * FidoNet: 2:5020/370.2 620.20 1103.5 * email: igor@itsoft.ru itarasov@rtuis.miem.edu.ru * Phone: (095)916-89-51 916-89-63 */

#include <stdio.h> #include <mysql/mysql.h> #include <itcgi.h>

int main() {

MYSQL* pDB;

LString* sref = CreateString(); LString* pref = CreateString(); LString* spage = CreateString(); LString* ppage = CreateString();

LString* id = CreateString(); LString* ref = CreateString(); LString* browser = CreateString(); LString* version = CreateString(); LString* cookie = CreateString(); LString* frame = CreateString(); LString* java = CreateString(); LString* x = CreateString(); LString* y = CreateString(); LString* depth = CreateString(); LString* js = CreateString(); LString* os = CreateString(); char str[4096];



pDB = mysql_init(NULL);

if(!pDB) { printError(mysql_error(pDB)); return -1; }

//получаем значения CGI-параметров, //сформированных javascript'ом, приведенным выше GetParamByName("id", id); GetParamByName("r", ref); GetParamByName("n", browser); GetParamByName("v", version); GetParamByName("c", cookie); GetParamByName("f", frame); GetParamByName("j", java); GetParamByName("x", x); GetParamByName("y", y); GetParamByName("d", depth); GetParamByName("js", js); GetParamByName("o", os);

//соединяемся с базой if( !mysql_real_connect(pDB, NULL, "counter", "хххххх", "counter", 0, NULL, 0) ) { printError("mysql_real_connect: %s\n", mysql_error(pDB)); goto LABEL_END; }

// получаем адрес сайта, на который пришел пользователь //эти адреса будут различаться в случае, если ваш счетчик //установлен на несколько ваших веб-сайтов GetServer(getenv("HTTP_REFERER"), spage); //получаем адрес страницы сайта, на которую пришел пользователь GetPage(getenv("HTTP_REFERER"), ppage);

//получаем адрес сайта, с которого пришел пользователь GetServer(*ref, sref); //получаем адрес страницы сайта, с которой пришел пользователь GetPage(*ref, pref);

//формируем строку SQL-запроса //обратите внимание, что мы используем функцию snprintf //застраховавшись от переполнения буфера snprintf(str, 4096, "INSERT INTO hit ( owner_id, shref, href, browser, version, \ cookie, frame, java, x, y, depth, js, os, ip, spage, page, it_date) VALUES (\ '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', \ '%s', '%s', '%s', '%s', NOW())", *id, *sref, *pref, *browser, *version, *cookie, *frame, *java, *x, *y, *depth, *js, *os, getenv("REMOTE_ADDR"), *spage, *ppage);

//вставляем новую запись в таблицу hit if( mysql_query(pDB, str) ) { printError("mysql_query: SQL=%s<br> %s\n", str, mysql_error(pDB)); goto LABEL_END; }



// выдаем прозрачный GIF размером 1х1 пиксел printf("Content-type: gif\n\n"); fwrite("GIF89a ? яяя !щ , @D ;", 1, 43, stdout);

LABEL_END: //закрываем соединение с базой mysql_close(pDB);

//освобождаем память DeleteString(sref); DeleteString(pref); DeleteString(spage); DeleteString(ppage);

DeleteString(id); DeleteString(ref); DeleteString(browser); DeleteString(version); DeleteString(cookie); DeleteString(frame); DeleteString(java); DeleteString(x); DeleteString(y); DeleteString(depth); DeleteString(js); DeleteString(os);

return 0; }

******Makefile*********** all: counter

counter: counter.c itcgi.a gcc counter.c -L/usr/local/lib/mysql \ -L/usr/local/lib -I/usr/local/include \ -o counter -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip counter

Создайте базу данных counter, а в ней таблицу hit, приведенную выше. Соберите данный CGI-скрипт и разместите код счетчика на ваших страницах.

Далее мы напишем программу построения отчетов.

#include <stdio.h> #include <stdlib.h> #include <mysql/mysql.h> #include <itcgi.h>

int main() { MYSQL* pDB; MYSQL_RES* res; MYSQL_ROW row;

//переменные для хранения дня, за который строится отчет, и запроса LString* day = CreateString(); LString* sql_query = CreateString();

pDB = mysql_init(NULL);

if(!pDB) { printError("Внимание! Ошибка!!!", mysql_error(pDB)); return -1; }

if( !mysql_real_connect(pDB, NULL, "counter", "xxxx", "counter", 0, NULL, 0) ) { printError("Внимание! Ошибка!!!", "mysql_real_connect: %s\n", mysql_error(pDB)); goto LABEL_END; }

//выдаем HTTP-заголовок printf("Content-type: text/html; charset=windows-1251\n\n");

//получаем день GetParamByName("day", day); if(!strlen(*day)) { //если из CGI-параметров день не удалось получить, //берем текущую дату //CURDATE() вернет дату в виде 2002-01-08, т.е. 8 января 2002 года LString_Format(sql_query, "SELECT CURDATE()"); mysql_query(pDB, *sql_query); res = mysql_store_result(pDB); row = mysql_fetch_row(res); LString_SetString(day, row[0]); mysql_free_result(res); }



// Создаем ссылки для навигации по дням. //Этот код непосредственно к отчетам не имеет отношения, //поэтому его комментировать не будем. LString_Format(sql_query, "SELECT DATE_SUB('%s', INTERVAL 1 DAY), DATE_ADD('%s', INTERVAL 1 DAY)", *day, *day); mysql_query(pDB, *sql_query); res = mysql_store_result(pDB); row = mysql_fetch_row(res); printf("<table width=95%%><tr><td align=left> \ <a href=/common/report?day=%s>Предыдущий день</a> \ <td align=center> %s <td ><a href=/common/report?day=%s>Следующий день</a></table>", row[0], *day, row[1]); mysql_free_result(res);

//далее идут отчеты

//сначала печатаем заголовок printf("<h4>Общее количество хостов и хитов</h4>"); //затем результат SQL-запроса printTable(pDB, "SELECT COUNT(DISTINCT ip) as host, COUNT(*) as hit FROM hit");

printf("<h4>Динамика визитов по дням</h4>"); printTable(pDB, "SELECT count(distinct ip) as host, COUNT(*) as hit, \ DATE_FORMAT(it_date, '%Y-%m-%d') as date FROM hit \ GROUP BY date");

printf("<h4>Распределение хитов и хостов по часам</h4>"); LString_Format(sql_query, "SELECT count(distinct ip) as host, COUNT(*) as hit, \ DATE_FORMAT(it_date, '%%H') as hour FROM hit \ WHERE DATE_FORMAT(it_date, '%%Y-%%m-%%d')='%s' \ GROUP BY hour", *day); printTable(pDB, *sql_query);

printf("<h4>Страницы - распределение хостов и хитов по страницам</h4>"); LString_Format(sql_query, "SELECT COUNT(DISTINCT ip) as host, COUNT(*) as hit, \ CONCAT(spage,page) as url FROM hit \ WHERE DATE_FORMAT(it_date, '%%Y-%%m-%%d')='%s' \ GROUP BY url ORDER BY host DESC, hit DESC LIMIT 0,20", *day); printTable(pDB, *sql_query);

printf("<h4>Страницы - распределение по доменным именам</h4>"); LString_Format(sql_query, "SELECT COUNT(DISTINCT ip) as host, COUNT(*) as hit, spage FROM hit \ WHERE DATE_FORMAT(it_date, '%%Y-%%m-%%d')='%s' \ GROUP BY spage ORDER BY host DESC, hit DESC LIMIT 0,20", *day); printTable(pDB, *sql_query);



printf("<h4>Ссылки - распределение по страницам</h4>"); LString_Format(sql_query, "SELECT COUNT(*) as q, shref, href FROM hit \ WHERE DATE_FORMAT(it_date, '%%Y-%%m-%%d')='%s' \ GROUP BY shref, href ORDER BY q DESC LIMIT 0,20", *day); printTable(pDB, *sql_query);

printf("<h4>Ссылки - распределение по доменным именам</h4>"); LString_Format(sql_query, "SELECT COUNT(*) as q, shref FROM hit \ WHERE DATE_FORMAT(it_date, '%%Y-%%m-%%d')='%s' \ GROUP BY shref ORDER BY q DESC LIMIT 0,20", *day); printTable(pDB, *sql_query);

printf("<h4>Распределение хитов по хостам</h4>"); LString_Format(sql_query, "CREATE TEMPORARY TABLE IF NOT EXISTS hh \ SELECT COUNT(*) as hit, ip FROM hit \ WHERE DATE_FORMAT(it_date, '%%Y-%%m-%%d')='%s' \ GROUP BY ip ORDER BY hit DESC", *day);

mysql_query(pDB, *sql_query); printTable(pDB, "SELECT * FROM hh LIMIT 0,20");

printf("<h4>Среднее количество страниц, открываемых одним пользователем</h4>"); printTable(pDB, "SELECT AVG(hit) FROM hh");

printf("<h4>Распределение по операционным системам </h4>"); printTable(pDB, "SELECT COUNT(*) as q, os FROM hit \ GROUP BY os ORDER BY q DESC");

printf("<h4>Распределение по броузерам</h4>"); printTable(pDB, "SELECT COUNT(*) as q, browser FROM hit \ GROUP BY browser ORDER BY q DESC");

printf("<h4>Распределение по версиям броузеров</h4>"); printTable(pDB, "SELECT COUNT(*) as q, browser, version FROM hit \ GROUP BY browser, version ORDER BY q DESC ");

printf("<h4>Распределение по разрешению экрана</h4>"); printTable(pDB, "SELECT COUNT(*) as q, CONCAT(x, 'x', y) as res FROM hit \ GROUP BY res ORDER BY q DESC ");

printf("<h4>Распределение по количеству цветов</h4>"); printTable(pDB, "SELECT COUNT(*) as q, depth FROM hit GROUP BY depth ORDER BY q DESC");



printf("<h4>Распределение по версиям javascript</h4>"); printTable(pDB, "SELECT COUNT(*), js FROM hit GROUP BY js ORDER BY 1 DESC");

printf("<h4>Распределение по наличию java</h4>"); printTable(pDB, "SELECT count(*), java FROM hit GROUP BY java ORDER BY 1 DESC");

printf("<h4>Распределение по наличию cookie</h4>"); printTable(pDB, "SELECT count(*), cookie FROM hit GROUP BY cookie ORDER BY 1 DESC");

/* printf("<h4></h4>"); printTable(pDB, ""); */

LABEL_END: mysql_close(pDB);

DeleteString(day); DeleteString(sql_query); return 0; }

=========Makefile========== all: report

report: report.c itcgi.a gcc report.c -L/usr/local/lib/mysql \ -L/usr/local/lib -I/usr/local/include \ -o report -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip report

Имеет смысл обратить особое внимание на следующий код.

printf("<h4>Распределение хитов по хостам</h4>"); LString_Format(sql_query, "CREATE TEMPORARY TABLE IF NOT EXISTS hh \ SELECT COUNT(*) as hit, ip FROM hit \ WHERE DATE_FORMAT(it_date, '%%Y-%%m-%%d')='%s' \ GROUP BY ip ORDER BY hit DESC", *day);

mysql_query(pDB, *sql_query); printTable(pDB, "SELECT * FROM hh LIMIT 0,20");

printf("<h4>Среднее количество страниц, открываемых одним пользователем</h4>"); printTable(pDB, "SELECT AVG(hit) FROM hh");

Во-первых, обратите внимание на то, как формируется строка SQL-запроса. При использовании LString_Format вы застрахованы от переполнения буфера и имеете возможность формировать строку с заранее неизвестным размером. Во-вторых, обратите внимание на сам SQL-запрос. Мы сначала создаем временную таблицу hh, а затем выполняем для нее два SQL-запроса. Необходимость использовать временную таблицу вызвана тем, что получить одним запросом среднее количество страниц, открываемых одним пользователем, невозможно. Мы сначала должны посчитать сколько страниц открыл каждый пользователь, а затем посчитать среднее арифметическое.Временная таблица уничтожается автоматически, после завершения соединения с базой либо же после явного вызова DROP TABLE. Помните об этом!


Результат работы CGI-скрипта report за 7 января 2002 года представлен ниже.

2002-01-07

Динамика визитов по дням


hosthitdate
03(null)
31250162002-01-05
31425292002-01-06
34532412002-01-07
32627012002-01-08



Распределение хитов и хостов по часам


hosthithour
209400
3011801
2011302
1711303
87304
82405
51806
79007
73208
112209
93510
2010411
157912
2120413
258914
1821515
2111916
3220717
2117218
1917119
2437620
3024421
2242522
2210423



Распределение хитов по хостам


hitip
477194.67.67.194
319195.42.120.42
140159.148.175.76
102195.62.129.22
96212.199.23.199
60195.250.65.226
58217.70.107.198
50212.38.113.239
47195.151.42.2
4662.118.128.1
44195.13.227.39
43194.27.68.220
42213.247.140.169
38194.190.217.17
37195.239.77.249
32128.112.71.29
31212.49.3.180
30193.193.211.37
30195.22.231.224
29195.34.32.11



Распределение по броузерам


qbrowser
12948Microsoft Internet Explorer
401Netscape
74Opera
63Konqueror
3(null)
3Microsoft
1



Распределение по количеству цветов


qdepth
536916
471732
330924
768
91
50
54
3(null)



Распределение по наличию cookie


count(*)cookie
133661
1210
6

На основании полученной статистики любопытно отметить, что абсолютное большинство пользователей наших сайтов работает на платформе Win32 в броузере Internet Explorer, имеют разрешение экрана не меньше 800х600, а каждый второй имеет не меньше 1024х768, количество цветов не менее 65 тысяч и у них поддерживаются технологии Java, JavaScript и Cookie.

Распределение по операционным системам


qos
13301Win32
66LinuxELF2.2
63X11
44Linux i686
5Linux
4MacPPC
4
3(null)
2SunOS5.5.1
1IRIX6.5



Распределение по разрешению экрана


qres
6468800x600
55191024x768
8121152x864
4341280x1024
78640x480
76960x720
431280x960
311600x1200
8720x480
71280x768
61400x1050
40x0
3(null)
21152x900
1512x384
12048x1120



Распределение по версиям броузеров


qbrowserversion
4482Microsoft Internet Explorer4.0 (compatible; MSIE 5.5; Windo
3504Microsoft Internet Explorer4.0 (compatible; MSIE 5.0; Windo
3232Microsoft Internet Explorer4.0 (compatible; MSIE 5.01; Wind
1380Microsoft Internet Explorer4.0 (compatible; MSIE 6.0; Windo
261Microsoft Internet Explorer4.0 (compatible; MSIE 4.01; Wind
88Netscape4.04 [en] (Win95; I)
55Netscape4.76 [en] (Win98; U)
54Microsoft Internet Explorer4.0 (compatible; MSIE 6.0b; Wind
50Netscape4.76 [ru] (X11; U; Linux 2.2.18-
44Netscape5.0 (X11; en-US)
33Netscape5.0 (Windows; en-US)
32Opera5.12 (Windows 98; U)
30Konqueror5.0 (compatible; Konqueror/2.1;
26Konqueror5.0 (compatible; Konqueror/2.1.1
26Netscape4.77 [en] (Windows NT 5.0; U)
24Netscape4.7 [en] (Win98; I)
23Opera6.0 (Windows 98; U)
15Opera6.0 (Windows 2000; U)
15Microsoft Internet Explorer4.0 (compatible; MSIE 5.0; MSNIA
15Netscape4.7 [ru] (Win95; I)
12Netscape4.04 [en] (Win95; I ;Nav)
10Netscape5.0 (Windows 98; U)
8Netscape4.61 [en] (Win98; I)
7Microsoft Internet Explorer4.0 (compatible; MSIE 4.0; Windo
7Konqueror5.0 (compatible; Konqueror/2.2-1
6Netscape4.75 [en] (X11; U; Linux 2.2.16-
6Netscape4.74 [en] (Win98; U)
5Microsoft Internet Explorer4.0 (compatible; MSIE 5.0; Linux
4Opera6.0 (Windows ME; U)
4Netscape4.77 [en] (X11; U; Linux 2.2.12
4Microsoft Internet Explorer4.0 (compatible; MSIE 5.0; AOL 6
3Netscape4.72 [en] (X11; U; Linux 2.2.14-
3Netscape4.78 [en] (Win98; U)
3Netscape4.76 [en] (X11; U; Linux 2.4.2-2
3Microsoft
3(null)(null)
2Microsoft Internet Explorer4.0 (compatible; MSIE 5.0; Macin
2Netscape4.73 [en] (X11; I; SunOS 5.8 sun
2Netscape4.6 [en] (WinNT; I)
2Microsoft Internet Explorer4.0 (compatible; MSIE 5.01; AOL
1Netscape4.7 [en] (Win95; I)
1Netscape4.76 [en]C-CCK-MCD QXW03200 (Wi
1Netscape4.75 [en] (X11; U; IRIX64 6.5 IP
1
1Netscape4.72 [en] (WinNT; I)
1Netscape4.75C-CCK-MCD {C-UDP; EBM-APPLE}
1Netscape4.78 (Macintosh; U; PPC)
1Netscape4.7 [ru] (WinNT; I)



Ссылки - распределение по доменным именам


qshref
1515photo.itsoft.ru
644www.opengl.org.ru
382
262www.perl.org.ru
121www.yandex.ru
82itsoft.ru
24www.google.com
20nit.itsoft.ru
15www.ya.ru
14learning.itsoft.ru
14catalog.aport.ru
14yandex.ru
13futuris.itsoft.ru
11miem.itsoft.ru
10search.rambler.ru
10ya.ru
8linux.itsoft.ru
8list.mail.ru
7vbstreets.ru
5world.altavista.com



Ссылки - распределение по страницам


qshrefhref
382
166www.opengl.org.ru/
105photo.itsoft.ru/
72www.opengl.org.ru/books/open_gl/
70www.perl.org.ru/
52www.opengl.org.ru/cgi-bin/ultra/UltraBoard.cgi
49www.opengl.org.ru/books/open_gl/index.html
45www.perl.org.ru/documentation/
37itsoft.ru/
31photo.itsoft.ru/l1.html?cn=human&rus=а427а415а41Bа41Eа412а415а41A
30www.opengl.org.ru/download/
24www.yandex.ru/yandsearch?text=opengl
19photo.itsoft.ru/l1.html?cn=animals&rus=а416а418а412а41Eа422а41Dа42Bа415
19photo.itsoft.ru/l1.html?cn=other&rus=а414а420а423а413а41Eа415
19www.opengl.org.ru/docs/pg/index.html
18www.perl.org.ru/cgi-bin/forum/UltraBoard.cgi
18www.opengl.org.ru/docs/
16photo.itsoft.ru/l1.html?cn=landscape&rus=а41Bа410а41Dа414а428а410а424а422а42B
16www.opengl.org.ru/download/examples.html
15www.opengl.org.ru/cgi-bin/ultra/UltraBoard.cgi?action=Headlines&BID=1&SID=



Страницы - распределение хостов и хитов по страницам


hosthiturl
73140www.opengl.org.ru/
5061www.perl.org.ru/
3785photo.itsoft.ru/
3546www.perl.org.ru/download/software.html
2936www.opengl.org.ru/download/libraries.html
2742www.opengl.org.ru/download/
2449www.perl.org.ru/documentation/
2021www.opengl.org.ru/download/docs.html
1964www.opengl.org.ru/books/open_gl/
1832www.perl.org.ru/cgi-bin/forum/UltraBoard.cgi
1825www.opengl.org.ru/cgi-bin/ultra/UltraBoard.cgi
1724www.opengl.org.ru/download/examples.html
1719www.opengl.org.ru/download/programs.html
1657www.opengl.org.ru/cgi-bin/ultra/UltraBoard.cgi?action=Headlines&BID=1&SID=
1635www.opengl.org.ru/books/open_gl/index.html
1517www.opengl.org.ru/docs/
1417www.opengl.org.ru/books/open_gl/chapter1.1.html
1213www.opengl.org.ru/index.html
1113www.opengl.org.ru/download/libs_html/opengl_st1.html
1028photo.itsoft.ru/l1.html?cn=human&rus=ЧЕЛОВЕК



Страницы - распределение по доменным именам


hosthitspage
138996www.opengl.org.ru
88395www.perl.org.ru
411587photo.itsoft.ru
2647nit.itsoft.ru
2478itsoft.ru
1032futuris.itsoft.ru
1012gb.itsoft.ru
912mobile.org.ru
830learning.itsoft.ru
620linux.itsoft.ru
44www.yandex.ru
44members.itsoft.ru
317miem.itsoft.ru
15world.altavista.com
12www.ya.ru



Доработать скрипт анализа посетителей веб-сата


Придумайте свои запросы к таблице hit и дополните ими программу report. Модифицируйте также запросы из программы report так, чтобы пользователю показывались не только абсолютные величины, но и процентное соотношение там, где это по смыслу оправдано. Например, интересно знать, сколько в процентах пользователей имеет разрешение 800х600.



Гостевая книга


Реализуйте гостевую книгу из предыдущей главы на основе СУБД MySQL.



Выполняем SQL-запросы к базе данных


В качестве первого примера CGI-скрипта, работающего с СУБД MySQL рассмотрим программу из предыдущей главы, которая выполняет SQL-запросы. Хотя MySQL наиболее часто используется под операционной системой UNIX, существует версия MySQL и для Windows. Мы начнем с версии CGI-скрипта под Windows, т.к. операционная система Windows знакома подавляющему большинству читателей. Вам необходимо установить библиотеку itcgimysql. itcgimysql - это версия ITCGI под Windows, с поддержкой функций для работы с СУБД MySQL. Данную версию библиотеки вы можете взять на нашем сайте http://itsoft.ru/.

HTML-форма для выполнения sql-запросов выглядит следующим образом:

База данных:

Пользователь:

Пароль:

HTML-код приведен ниже:

<form action="/cgi-bin/sql_query_mysql.exe"> База данных: <input type=text name=db><br> Пользователь: <input type=text name=user><br> Пароль: <input type=password name=pwd><br>

<textarea name=query rows=10 cols=80></textarea> <input type=submit value="Отправить запрос"> </form>

Запустите MS Visual Studio и создайте приложение по образу и подобию gbadd из предыдущей главы. Назовите его sql_query_mysql. В список подключаемых библиотек включите Ws2_32.lib, libmysql.lib, itcgimysql.lib.

Отредактируйте sql_query_mysql.cpp следующим образом:

// sql_query.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include "sql_query.h"

#ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif

///////////////////////////////////////////////////////////////////////////// // The one and only application object

#include <winsock2.h>

#include <mysql/mysql.h> #include <itcgi.h>

CWinApp theApp;

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) { if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) return -1;

/* Ниже идет непосредственно код программы, который мы впоследствии перенесем под UNIX. */ MYSQL* pDB; MYSQL_RES* res;

//используем наш тип LString для работы со строками в библиотеке ITCGI //мы не используем MFC-класс CString, чтобы обеспечить //переносимость кода под UNIX LString* db = CreateString(); LString* user = CreateString(); LString* pwd = CreateString(); LString* sql_query = CreateString();

//инициализируем библиотеку функций для работы с MySQL pDB = mysql_init(NULL); if(!pDB) { printError("Внимание! Ошибка!!!", mysql_error(pDB)); return -1; }

//получаем CGI-параметры из HTML-формы GetParamByName("user", user); GetParamByName("pwd", pwd); GetParamByName("db", db); GetParamByName("query", sql_query);

//осуществляем соединение с конкретной базой данных if( !mysql_real_connect(pDB, NULL, *user, *pwd, *db, 0, NULL, 0) ) { printError("Внимание! Ошибка!!!", "mysql_real_connect: %s\n", mysql_error(pDB)); goto LABEL_END; }

//выполняем SQL-запрос if( mysql_query(pDB, *sql_query) ) { printError("Внимание! Ошибка!!!", mysql_error(pDB)); goto LABEL_END; }

//Сохраняем результат выполнения SQL-запоса. //Результатом любого SQL-запроса типа SELECT является таблица. //Результатом любых других запросов отличных от SELECT будет NULL. res = mysql_store_result(pDB); if( res ) { //Если результатом является таблица, печатаем ее. MYSQL_ROW row; MYSQL_FIELD* field; int i;

printf("Content-type: text/html\n\n"); printf("<table border=1>\n");

//печатаем названия столбцов printf("<tr bgcolor=\"#CCCCCC\">\n"); while(field=mysql_fetch_field(res)) printf("<td>%s</td>", field->name); printf("</tr>\n");

//печатаем записи таблицы while( (row = mysql_fetch_row(res))!=NULL ) { printf("<tr>\n");

for(i=0;i<(int)res->field_count;i++) printf("<td>%s</td>", row[i]);

}//while

printf("</table>");

//освобождаем память, выделенную под таблицу mysql_free_result(res); }

//если результатом SQL-запроса был NULL, т.е. //мы выполнили запрос типа INSERT, UPDATE, DELETE и т.п., //то возвращаем пользователя на страницу с HTML-формой else printf("Location: %s\n\n",getenv("HTTP_REFERER"));

LABEL_END: //закрываем соединение с базой данных mysql_close(pDB);

//освобождаем память, выделенную под строки DeleteString(db); DeleteString(user); DeleteString(pwd); return 0; }

Соберите и протестируйте данный CGI-скрипт в действии.

В связи с тем, что основная часть приложений с MySQL создается в среде UNIX рассмотрим этот же скрипт и Makefile для него.

#include <stdio.h> #include <stdlib.h> #include <mysql/mysql.h> #include <itcgi.h>

int main() { MYSQL* pDB; MYSQL_RES* res; LString* db = CreateString(); LString* user = CreateString(); LString* pwd = CreateString(); LString* sql_query = CreateString();

pDB = mysql_init(NULL);

if(!pDB) { printError("Внимание! Ошибка!!!", mysql_error(pDB)); return -1; }

GetParamByName("user", user); GetParamByName("pwd", pwd); GetParamByName("db", db); GetParamByName("query", sql_query);

if( !mysql_real_connect(pDB, NULL, *user, *pwd, *db, 0, NULL, 0) ) { printError("Внимание! Ошибка!!!", "mysql_real_connect: %s\n", mysql_error(pDB)); goto LABEL_END; }

if( mysql_query(pDB, *sql_query) ) { printError("Внимание! Ошибка!!!", mysql_error(pDB)); goto LABEL_END; }

res = mysql_store_result(pDB); if( res ) { MYSQL_ROW row; MYSQL_FIELD* field; int i;

printf("Content-type: text/html\n\n"); printf("<table border=1>\n");

printf("<tr bgcolor=\"#CCCCCC\">\n"); while(field=mysql_fetch_field(res)) printf("<td>%s</td>", field->name); printf("</tr>\n");

while( (row = mysql_fetch_row(res))!=NULL ) { printf("<tr>\n");

for(i=0;i<(int)res->field_count;i++) printf("<td>%s</td>", row[i]);

}//while

printf("</table>"); mysql_free_result(res); } else printf("Location: %s\n\n",getenv("HTTP_REFERER"));

LABEL_END: mysql_close(pDB);

DeleteString(db); DeleteString(user); DeleteString(pwd); return 0; }

******Makefile******* all: sql_query2

sql_query2: sql_query2.c itcgi.a gcc sql_query2.c -L/usr/local/lib/mysql \ -L/usr/local/lib -I/usr/local/include \ -o sql_query2 -lmysqlclient /usr/lib/itcgi.a -Wall -O3 strip sql_query2

Содержание функции main осталось неизменным. Мы убрали заголовочные файлы и директивы библиотеки MFC. Данная программа была собрана и оттестирована на платформе FreeBSD, но она также должна работать на любом клоне Unix.