СGI программирование вывода в клиентскую среду
Взаимодействие между пользовательской интерактивной HTML-страницей (среда браузера, клиент) и сервером обслуживается протоколом CGI. При передаче данных от клиентской формы серверу, последний кодирует входные данные, а сценарий CGI декодирует их, а затем функционально обрабатывает и возвращает выходные данные браузеру.
Судя по литератере, вопросам передачи и обработки данных, включая их запись на диск или в базу данных уделено достаточно много внимания, эти вопросы в той или иной степени затронуты практически в любом руководстве по веб-программированию. Вместе с тем задачи, а в ряде случаев и проблемы, связанные с генерированием выходных данных в среду браузера, освещаются в публикациях и монографиях на уровне фона. Применение же встроенных HTML-функций Perl, в задачу которых входит облегчить генерацию HTML-включений непосредственно из CGI-сценария, приводит к тому, что программист и дизайнер должны выступать в одном лице.
В принципе, самый эффективный (с точки зрения разделения CGI-программирования и HTML-дизайна) способ возврата данных клиенту — это генерация HTML-шаблонов на лету. Шаблон читается CGI-сценарием, который выполняет подстановку переменных вместо параметрических включений и потом на лету генерирует страницу в окне браузера. Самый неэффективный способ — это размещение полных HTML-страниц в теле CGI-скрипта. Любое изменение дизайна HTML-страницы, в последнем случае, потребует модифицировать и CGI-программу в целом. К чему это приводит, думаю знакомо всем веб-программистам. Вместе с тем, как во втором, так и в первом случаях гибкость и опереративность вывода данных оставляет желать лучшего.
В данной статье мы остановимся на некоторых промежуточных вариантах вывода данных, обеспечивающих гибкость и опреративность, смысл которых будет понятен ниже. Представим себе ситуацию, когда в действующую HTML-страницу (файл) потребуется оперативно выводить данные и при этом не затрагивать существующий дизайн. Такими данными могут быть изображения, численные значения счетчиков, ленты новостей, котировки валют on-line, прогнозы погоды и много другое. При этом, в ряде случаев потребуется избавить пользователя от перезагрузки HTML-страницы. На сайтах часто можно встретить предупреждение типа: "Для обновления данных перезагрузите страницу!". Прямолинейным решением может служить перезапись HTML-файлов с помощью CGI-программы, которая предварительно читает файл с диска и изменяет содержимое параметрических включений. Такой подход плох уже тем, что снижает безопасность, открывая доступ каждому (everyone) к записи на диск. Мы рассмотрим другие варианты, а именно, формирование запросов CGI-ресурсов путем использования контейнерных HTML-элементов: APPLET, SCRIPT, OBJECT, IFRAME (ILAYER для Netscape), ну и конечно элемент IMG. С последнего и начнем.
загрузка ресурса IMG
задача
Вы хотите, чтобы на вашей HTML-странице случайным образом загружались бы графические изображения, перечень которых может постоянно изменяться и пополняться.
решение
Используем атрибут SRC эленента IMG для загрузки CGI-ресурса:
<IMG src="../cgi-bin/rand_imgages.cgi">
Надо отметить, что этот прием достаточно часто используется в веб-программировании. Все сложности сводятся к написанию приемлемого CGI-скрипта, который отвечал бы поставленной задаче. В качестве наглядного примера приведем довольно простой скрипт, обеспечивающий случайный вывод изображения из ограниченного списка графических файлов:
#!/usr/bin/perl
$path = "c:/apache/htdocs";
@pic=('c0.gif','c1.gif','c2.gif','c3.gif','c4.gif','c5.gif','c6.gif','c7.gif','c8.gif');
srand;
$id=int(rand(9));
$gif=$path.'/images/'.$pic[$id];
print "Content-Type: image/gif\n\n";
open G,$gif;
binmode( G );
binmode( STDOUT );
print <G>;
close G;
exit;
Для полноты картины приведем и текст HTML-страницы:
<html>
<head>
<title>Copyright 2002 The Web Production</title>
</head>
<body>
<b>TOP</b>Text text ......<br><br>
<center>
<IMG SRC="../cgi-bin/rand_imgages.cgi">
</center>
<br>
<b>BOTTOM</b>Text text ......<br><br>
</body>
</html>
В этом примере видно, что дизайн может быть реализован отдельно от загружаемых изображений (но с учетом их ширины и высоты) и может изменяться независимо от CGI-сценария и независимо от CGI-программиста. Более сложный вариант CGI-программы, обеспечивающий случайный выбор из переменного списка изображений, должен включать код открытия обособленной директории файлов изображений:
...
...
...
$DIR_PICTURES = $path.'/picturies';
opendir (ETC, $DIR_PICTURES)||die "no directory!: $!";
$i=0;
foreach $filename (readdir(ETC)) {
if($i >1){ $k=$i-2; $my_pic[$k]=$filename;}
$i++;}
close (ETC);
$leng=@my_pic;
srand;
$id=int(rand($leng));
$gif=$path.'/images/'.$my_pic[$id];
...
...
...
В этом скрипте имена файлов заносятся в массив @my_pic, причем для пропуска точек (. и ..) в верхей части директории выполняется условый оператор в зависимости от значения счетчика (переменная $i).
В заключение отметим, что элемент IMG и сответствующий CGI-сценарий часто используются в качестве скрытых счетчиков посетителей страниц. Такие скрипты могут также содержать процессы MAIL, которые позволяют, в сою очередь, известить владельца о посещении страницы визитером, имеющего такой-то IP-адрес, зашедший с такой-то страницы и т.п.
загрузка CGI-сценариев в контейнере IFRAME
Основной привлекательностью использования элемента IFRAME является возможность выделения в произвольном месте HTML-страницы прямоугольной области произвольных размеров. Этот прием часто применяется для внедрения в статическую HTML-страницу, новостной, часто меняющейся информации, как текстовой, так и графической. При этом, как правило, новостная вставка сама представляет собой HTML-страницу (файл). Применение в качестве загрузочного ресурcа CGI-сценария позволит автоматизировать процедуру смены новостей.
задача
Автоматизировать процесс смены новостной информации (текстовой) в поле статической HTML-страницы.
решение
CGI-программа считывает текстовый файл с диска, или выполняет выборку из базы данных (БД), генерирует на лету документ HTML и загружает ресурс в элемент IFRAME:
<IFRAME SRC="../cgi-bin/news_bulletin.cgi" HSPACE="0" VSPACE="0" FRAMEBORDER="0" MARGINHEIGHT="0" MARGINWIDTH="0" WIDTH="320" HEIGHT="100"></IFRAME>.
Начнем рассмотрение с CGI-скрипта:
#!/usr/bin/perl
print "Content-Type: text/html\n\n";
$path = "c:/apache/htdocs";
$FILE_NEWS=$path.'/txt/bulletin.txt';
open(LIST,"<$FILE_NEWS");
@lines=<LIST>;
close(LIST);
#-------HTML-include:
print <<EOT;
<html>
<head>
<style type="text/css">P.just{text-align: justify;}</style>
</head><body bgcolor='#FFFFFF' leftmargin='0'>
<table border="0" cellspacing="0" cellpadding="20">
<tr>
<td><p><font color='#00007b' size='4'>@lines</font></p></td>
</tr>
</table>
</body></html>
EOT
exit;
В процессе загрузки в браузер новостной HTML-страницы управление передается CGI-скрипту, который считывает обычный текстовый файл bulletin.txt с диска сервера, создает на лету HTML-вставку и выводит ресурс в элемент IFRAME. Для наглядности, HTML-вставка размещена в теле CGI-скрипта. В реальной ситуации, HTML-вставка существует в виде шаблона в каталоге шаблонов на сервере или находится в таблице шаблонов в БД. Ниже представлена собственно новостная HTML-страница (news.html):
<html>
<head>
<title>Copyright 2002 The Web Production</title>
<style type="text/css">P.just{text-align: justify;}</style>
</head>
<body bgcolor='#00007b' text="white">
<center><h2>News Bulletin</h2></center><hr>
<table border='1' cellspacing="0" cellpadding="10">
<tr>
<td align="left" valign="top" width='400'>Examples examples examples examples examples: </td>
<td align="left" valign="top">
<IFRAME SRC="../cgi-bin/cgi_programming/news_bulletin.cgi" HSPACE="0" VSPACE="0"
FRAMEBORDER="0" MARGINHEIGHT="0" MARGINWIDTH="0" WIDTH="320" HEIGHT="100">
</IFRAME>
<ILAYER SRC="../cgi-bin/cgi_programming/news_bulletin.cgi" left="0" top="0"
VISIBILITY="show" WIDTH="320" HEIGHT="100">
</ILAYER>
</td>
</tr>
</table>
</body>
</html>
Из этого примера видно (рис.1), что динамически обновляемая информация отделена от статического дизайна, который может модифицироваться дизайнером без ущерба CGI-cкрипту.
Рис. 1.
Ниже представлен простой пример CGI-скрипта, выполняющий единичную выборку новостей из базы данных MySQL:
...
...
...
$table_name="news_arjive";
$dbh = DBI->connect("DBI:mysql:$database:$host:$port",$login,$pass);
$sth = $dbh->prepare("SELECT text_news FROM $table_name WHERE id = 1");
$sth->execute;
$news = $sth->fetchrow_array ();
$sth->finish;
$dbh->disconnect;
#-------HTML-include:
print <<EOT;
<html>
<head>
<style type="text/css">P.just{text-align: justify;}</style>
</head><body bgcolor='#FFFFFF' leftmargin='0'>
<table border="0" cellspacing="0" cellpadding="20">
<tr>
<td><p><font color='#00007b' size='4'>$news</font></p></td>
</tr>
</table>
</body></html>
EOT
exit;
Отметим, что изменение кода HTML-вставки выразилось лишь только в замене массива @lines на переменную $news.
Автоматическая выборка новостной информации из БД — хороший пример сокращения затрат на содержание сайтов средних и крупных размеров, порталов, интернет-магазинов и.т.п. Редактор новостей корпоративной сети, по мере поступления новостной информации, заносит ее в БД. Дальнейшее распространение и публикация новостей происходит без его участия и без помощи дизайнеров и программистов.
CGI-JavaScript программирование
В рассмотренных выше примерах вставка возвращаемых данных осуществлялась, со стороны браузера, пассивно. Активное взаимодействие сценариев CGI и скриптов языка JavaScript в значительной степени расширяет возможности представления и обработки этих данных. В качестве примера, рассмотрим задачу охраны авторского права на публикуемую новостную информацию.
задача
Автоматизировать процесс смены новостной информации (текстовой) в поле статической HTML-страницы и обеспечить при этом сохранение информации об авторских правах на публикацию.
решение
В новостной HTML-странице располагается некоторая подпрограмма-функция test_copyright() языка JavaScript, ненсущая инфорацию об авторских правах. Выполнение этой подпрогаммы возлагается на сценарий CGI. CGI-программа считывает файл новостей с диска, сравнивает информацию об авторских правах с образом copyright и генерирует на лету свой JavaScript-скрипт test2(), который непосредственно и выполняет вывод в окно браузера.
Загрузка CGI-ресурса выполняется с помощью атрибута SRC элемента SCRIPT:
<SCRIPT language="JavaScript" src="../cgi-bin/jscgi_prog.cgi"></SCRIPT>.
Расссмотрим сначала код HTML-страницы:
<html>
<head>
<style type="text/css">P.just{text-align: justify;}</style>
<script>
var name_copyright='Copyright 2002 The Web Production';
function test_copyright(){return name_copyright;}
</script>
<SCRIPT language="JavaScript" src="../cgi-bin/cgi_programming/jscgi_prog.cgi"></SCRIPT>
</head>
<body bgcolor='#00007b' text="white" leftmargin='0'>
<center><h2>News Bulletin</h2></center><hr>
<table border="0" cellspacing="0" cellpadding="20" width="800">
<tr>
<td align="left" valign="top" width="400">Examples examples
examples examples examples examples ......</td>
<td bgcolor='#0000aa' align="left" valign="top" width="400">
<p><font color='#ffffff' size='4'>
<script>test2();</script>
</font></p></td>
</tr>
</table>
</body>
</html>
Как видим, в теле HTML-страницы располагается подпрограмма-функция test_copyright(). Возвращаемый параметр этой функции содержит copyright. Образ этого copyright находится в одной из переменных сценария CGI:
#!/usr/bin/perl
print "Content-Type: text/html\n\n";
$path = "c:/apache/htdocs";
$FILE_NEWS=$path.'/txt/bulletin.txt';
open(LIST,"<$FILE_NEWS");
@lines=<LIST>;
close(LIST);
$txt=join("",@lines);
$txt=~s/\n//g;
$param='Copyright 2002 The Web Production'; #patteren
print "function test2(){".
"var m1= test_copyright();".
"if(m1==\"$param\"){document.write(\"$txt\");".
"document.write(\"<br><br>$param<br>\")}else{err();}}";
exit;
Функция test2() языка JavaScript, размещается в теле CGI-скрипта, а вызывается на выполнение при загрузке HTML-страницы. В процессе выполнения сценария CGI, последний анализирует информацию, возвращаемую подпрограммой-функцией test_copyright() и сравнивает ее с помощью скрипта test2() с образом copyright в переменной $param. В зависимости от совпадения или несовпадения образа с copyright, новостная информация публикуется или возвращается ошибка JavaScript. Ошибка JavaScript, равносильная запрету на публикацию, возникает в двух случаях: 1) если вы измените или удалите copyright из тела HTML-страницы, 2) если вы попытаетесь удалить подпрограмму-функцию.
Отметим, что как и прежде, реализуется идеология разделения CGI-программы и дизайна (Рис.2).
Рис. 2.
Из кода HTML видно, что атрибуты текста новостей могут быть изменены независимо от сценария CGI и независимо от скрипта языка JavaScript. Разумеется, мы здесь представили упрощенный вариант скриптов, преследуя цель наглядности излагаемого материала. Понятно, что техника CGI-JavaScript программирования, позволяет создать весьма изощеренные средства защиты и ограничения доступа.
CGI-Flash программирование
Появление технологии Flash компании Macromedia в значительной степени преобразило мир Web. Flash, без особой шумихи (что было свойственно распространению в вебе языка Java), быстро занял свою нишу и эволюционировал от внешних plug-in к встроенным в браузеры IE и NN и является в настоящее время полноценной частью инструментов веб-дизайна и веб-программирования. Применение Macromedia Flash избавляет от проблемы совместимости между браузерами, Flash одинаково работает как в IE, так и в NN. В Macromedia Flash 5 применяется специальный событийно-управляемый язык, который поддерживает условные переходы, циклы, массивы, функции и классы, которые можно наследовать. Процедуры ввода-вывода данных, как мы увидим ниже, могут быть реализованы с помошью всего лишь одного оператора этого языка.
задача
Создать в статической HTML-странице on-line котировщик валюты EUR/USD рынка Forex и обеспечить запрос на обновление информации через каждые N секунд без перезагрузки HTML-страницы. В качестве ресурса котировок валют использовать ресурс партнерского сервера (www.my_partner.com).
решение
Создать файл Flash, обеспечивающий прием информации от сценария CGI. CGI-сценарий должен создавать сокет и устанавливать соединение для приема информации от удаленного сервера. Этот сервер содержит ресурс котировок валют рынка Forex (ресурс обновляется каждые 5 секунд). Из полученного ресурса (HTML-страницы) извлечь численные значения спроса (bid) и предложения (ask) EUR/USD. Полученные значения передать в поток вывода. Использовать элемент OBJECT для включения Flash в статическую HTML-страницу:
<HTML>
<HEAD>
<TITLE>TheWebProduction</TITLE>
</HEAD>
<BODY bgcolor="#FFFFFF">
<h1>CGI-Flash programming:</h1>
<hr>
<TABLE border='0' WIDTH='640' align='left' valign='top' cellpadding="0"
cellspacing="0">
<TR>
<TD align='left' valign='top'>
Any text text text text text text text text text text text text
text text text text text text text text text text text text
text text text text text text text text text text text text
text text text text text text text text text .......
</TD>
<TD align='left' valign='top'>
<table border='1' WIDTH='372' HEIGHT='54' align='left' valign='top' cellpadding="0"
cellspacing="0">
<tr>
<td align='left' valign='top'>
<OBJECT classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=5,0,0,0"
WIDTH=370 HEIGHT=52>
<PARAM NAME=movie VALUE="finan_group4_2.swf"><PARAM NAME=quality VALUE=autolow>
<PARAM NAME=salign VALUE=LT><PARAM NAME=bgcolor VALUE=#FFFFFF>
<EMBED src="finan.swf" quality=autolow salign=LT bgcolor=#FFFFFF WIDTH=370 HEIGHT=52
TYPE="application/x-shockwave-flash"
PLUGINSPAGE="http://www.macromedia.com/shockwave/download/index.cgi?
P1_Prod_Version=ShockwaveFlash">
</EMBED>
</OBJECT>
</td></tr>
</table>
</TD></TR>
<TR>
<TD align='center' valign='top' colspan=2>
<b>Copyriht © 2002 TheWebProduction</b>
</TD></TR>
</TABLE>
</BODY>
</HTML>
Как мы видим (рис.6), файл Flash finan.swf выводит в HTML-страницу информационную вставку и идеология разделения CGI-программы и дизайна не нарушена.
Рис. 4.
Программа CGI показана ниже:
#!/usr/bin/perl
use IO::Socket;
print "Pragma: no-cache\nCache-control: no-cache\nContent-type: text/plain\n";
$port="80";
my $host="www.my_forex.com";
my $file;
$valuta="EURUSD";
$file = "/rate/simulation.html";
socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp'));
$iaddr = inet_aton($host);
$paddr = sockaddr_in($port, $iaddr);
connect(SOCK, $paddr);
send (SOCK, "GET $file HTTP/1.0\nHOST:$host\n\n", 0);
@data=<SOCK>;
close(SOCK);
$answer = join("",@data);
($etc,$iter1)=split(/$valuta/,$answer);
$met='</SPAN>';
@iter2=split(/$met/,$iter1);
@iter3=split(/>/,$iter2[0]);
@iter4=split(/>/,$iter2[1]);
($sec, $min, $hour, $day, $month, $year, $wday, $yday, $isdst) = gmtime(time);
if($sec < 10){$sec="0".$sec;}
if($min < 10){$min="0".$min;}
if($hour < 10){$hour="0".$hour;}
$my_time=$hour.":".$min.":".$sec;
print "\nmy_bid=".$iter3[3].'&my_ask='.$iter4[3].'&my_time='.$my_time;
exit;
Функция socket() создает сокет, тип которого определен как потоковый — SOCK_STREAM. После конвертации имени сокета (более удобного представления для сервера), создается соединение с помощью функции connect(). Предварительно с помощью переменных $host и $file задаются имя сервера и имя ресурса, соответственно. Для передачи данных через сокет используется функция send(), а для приема данных через сокет — известная процедура: @data=<SOCK>. После закрытия сокета идет разбор принятых данных ресурса simulation.html:
<html>
<head>
<title>Rates</title>
</HEAD>
<SPAN>My Forex Group</SPAN>
<BODY leftmargin="2" topmargin="0">
<SPAN id="tRates">
<TABLE width='265' height='84' border='0'cellspacing='0' cellpadding='0'>
<TR><td valign='top' background='/images/home/rateb.gif'>
<table>
<TR><TD>Rate</TD><TD>Bid</TD>
<TD>Ask</TD><TD>Time</TD>
</TR>
<TR><TD>EURUSD</TD>
<TD bgcolor=#CCD2DA><SPAN>0.9807</SPAN></TD>
<TD bgcolor=#CCD2DA><SPAN>0.9812</SPAN></TD>
<TD nowrap bgcolor=#CCD2DA><span>07/31/02 11:03:04</SPAN></TD>
</TR>
<TR><TD>USDJPY</TD>
<TD><SPAN>119.65</SPAN></TD>
<TD><SPAN>119.70</SPAN></TD>
<TD nowrap><span>07/31/02 11:03:04</SPAN></TD>
</TR>
<TR><TD>GBPUSD</TD>
<TD bgcolor=#CCD2DA><SPAN>1.5628</SPAN></TD>
<TD bgcolor=#CCD2DA><SPAN>1.5633</SPAN></TD>
<TD nowrap bgcolor=#CCD2DA><span>07/31/02 11:03:54</SPAN></TD>
</TR><TR><TD>USDCHF</TD>
<TD><SPAN>1.4815</SPAN></TD>
<TD><SPAN>1.4820</SPAN></TD>
<TD nowrap><span>07/31/02 11:03:52</SPAN></TD></TR></Table></TD>
</TR></TABLE>
</SPAN>
</Body>
</html>
Для разбора HTML-кода используется образ EURUSD, который задается переменной $valuta. Данные относящиеся к спросу (bid) помещаются в переменную $iter3[3], а данные относящиеся к предложению (ask) в переменную $iter4[3]. В конце программы фиксируется местное время и заносится в переменную $my_time. И наконец создается стандартный вывод, эмулирующий метод POST протокола HTTP (необходимо для Flash).
Теперь обратимся к Flash. Прежде всего создадим форму с тремя полями ввода - рис.5.
Рис. 5.
Создадим два слоя и растянем их на 20 кадров. При скорости кадров 8 fps, полное время цикла из 20 кадров составит 2.5 секунды, т.е. этот промежуток времени будет равен половине времени обновления ресурса курса валют. Затем в оконе Text Options (рис.6) зададим имя текстовой переменной Variable равой my_bid.
Рис. 6.
Аналогичную процедуру выполним для создания имен двух других текстовых переменных: my_ask и my-time. Для верхнего слоя (рис.5) откроем окно Frame Actions и займемся программированием:
loadVariablesNum ("http://www.mycgi.com/cgi-bin/java_applet/finan.cgi", 0);
Процедура loadVariablesNum загрузит CGI-ресурс, а полученные данные от CGI-скрипта разместит в текстовых переменных: my_bid, my_ask и my-time, соответственно.
Смирнов Михаил
Сетевые решения. Статья была опубликована в номере 02 за 2003 год в рубрике программирование