Елена Филиппова дата публикации 16-10-2000 00:00 Простые CGI приложения на Дельфи. Просто рисуем свой счетчик
В продолжение статьи Алексеея Еремеева "Простые CGI-приложения на Delphi"
я попробую рассказать о
генерации изображений "на лету" на примере счетчика посещений.
Материал этот предназначен для начинающих программистов, тем не менее предполагается,
что перед его прочтением вы познакомились со статьей Алексея Еремеева "Простые
cgi-приложения на Delphi" и материалом Евгения Акованцева "К вопросу о построении
гостевой книги" из раздела "Hello, Word!". А так же пытались разобраться с
методами и свойствами класса TWebModule.
Генерация изображения "на лету" |
Смысл этой красивой фразы довольно прост - требуется вывести
на HTML-странице картинку, которой нет. То есть просто нарисовать ее перед
тем, как сервер сгенерит страницу по запросу клиента.
Области применения этой технологии - вывод некой динамической информации
в виде графического изображения. Например, счетчик посещений сайта,
всевозможные статистические графики, рейтинги и наградные(именные) маркеры.
Начнем с самого простого - счетчика посещений.
Практически каждый сайт, посвященный web-программированию, содержит
пример такого счетчика.
Алгоритм его создания довольно прост:
- Есть текущее значение счетчика;
- При каждой загрузке скрипта оно увеличивается на единицу и формируется GIF-изображение ( то есть рисуются цифры);
- Картинка выводится в выходной поток и отправляется клиенту.
Можно было бы не повторяться, но некоторые моменты в самых распространенных
в сети примерах, мягко говоря не совсем корректны.
Тех, кто крайне отрицательно относится к лирическим отступлениям, просьба следующий
раздел не читать, дабы не раздражаться лишний раз.
Где хранить значение счетчика? |
Вопрос, который не имеет никакого отношения к генерации изображений, но
ответить на который надо, пржде чем писать такой скрипт.
Чаще всего предлагается использовать для хранения текущего значения счетчика
таблицу в базе данных. Заводите табличку с соответствующим полем и апдейтите
его при каждой загрузке скрипта.
Дешево и сердито.
Тут даже не важно, есть ли у вас возможность использовать БД или нет,
здесь важен сам подход. Базу данных надо использовать тогда, когда есть
необходимость в выборках по различным условиям, поиске и сортировке.
Что вряд ли требуется для функционирования счетчика посещений,
даже с минимальной статистикой. Не все то, что на первый взгляд "проще" по реализации,
на самом деле лучшее решение.
В полном соответствии с принципом "бритвы Оккама".
В 13-ом веке в Англии жил философ Уильям Оккам (ударение на первый слог).
Занятия его философией и теологией для большей части программистов
покрыты мраком, но имя его известно, по крайней мере для любителей фантастики,
благодаря знаменитому принципу "бритвы Оккама". Принцип этот гласит -
"Отсекай все лишнее" или "Не вводите новых сущностей без особой надобности".
Стоит ли вводить новую сущность (БД) между клиентом и сервером в этом случае?
Ведь чем длиннее цепочка, тем больше шансов у нее порваться :о)
Послушаемся мудрого Оккама, не будем тревожить БД по мелочам
и будем хранить текущее значение счетчика в файле.
Важно ! Для того, чтобы писать в файл на диске на Вашем Web-сервере, необходимо,
чтобы соответствующий каталог имел атрибут Change.
В нашем примере функция ReadCounter возвращает текущее значение
счетчика, читая его из файла. При этом, если файла нет на диске, она его
создает и устанавливает значение счетчика в 1. Такми образом нет необходимости
специально создавать файл на диске с нужным именем и начальным значением.
Функция все сделает сама.
Формат файла может быть текстовый или бинарный. Разницы нет никакой, кроме
возможности (или невозможности) поправлять значение счетчика "ручками".
Я буду использовать бинарный файл. Если Вам захочется переделать код на работу
с текстовым файлом, смотрите Help, функция AssignFile и далее по ссылкам.
Function ReadCounter : String;
Var FHandle ,
Counter : Cardinal;
FileName : String;
Begin
Result:= '0';
// имя файла со значением счетчика
FileName:='counter.dat';
Counter:=0;
Try
// пытаемся открыть или создать этот файл и читать из него
IF NOT FileExists(FileName)
Then FHandle:=FileCreate(FileName)
Else Begin
FHandle:=FileOpen(FileName,fmOpenReadWrite);
FileRead(FHandle,Counter,SizeOF(Counter));
End;
Result:= IntToStr(Counter);
// увеличиваем счетчик
Inc(Counter);
// и пытаемся записать новое значение обратно в файл
FileSeek(FHandle,0,0);
FileWrite(FHandle,Counter,SizeOF(Counter));
FileClose(FHandle);
Except
End;
End;
Функция фозвращает сразу символьное значение счетчика, так удобнее для ее
дальнейшего использования. Тип Cardinal это беззнаковое 32-битное
целое число, его диапазон - < 0 .. 4 294 967 295 > , так что любителям
накручивать счетчики будет где развернуться :о)
Выводить картинку можно в одном из этих форматов, но в каком лучше?
С точки зрения программирования разницы нет никакой, ну кроме того, что
будут использоваться разные объекты. Предпочтение конкретному формату
зависит от Ваших личных вкусов и типа изображения. Дело в том, что формат GIF лучше
подходит для четких изображений (надпись, график и т.п.), а формат JPEG -
для картинок с плавными переходами цветов.
Так что , можете поэкспериментировать и решить, что больше нравится именно Вам.
В нашем примере будем рисовать GIF, для этого нам необходим соответствующий
класс, который поддерживает формат GIF.
В сети есть немало свободно распространяемых графических компонент, я буду
использовать в своем примере RXGIF. Не потому, что он лучше для этого
подходит (скорее всего наоборот, он тяжеловат, так как реализует много возможностей,
которые нам даже и не пригодятся ) , но у меня он уже есть и искать специально что-то
другое только для этого примера нет надобности.
Итак, приступим. Создаем новое приложение:
File-> New -> Web Server Application -> CGI Stand Alone Executable.
В WebModule в Actions добавляем новое действие (Add Item).
Устанавливаем для него Default:=True. В обработчике этого действия (WebActionItem)
и пишем нужный нам код.
Смысл этих действий более подробно описан в материале "К вопросу о создании гостевой книги".
Итак, алгоритм следующий:
- Читаем из файла значение счетчика;
- Создаем объект TBitmap и пишем, то есть рисуем на его канве текст;
- Создаем объект TGIFImage и передаем ему изображение, то есть конвертируем
в формат GIF сформированную картинку. Это действие необходимо, так как сразу рисовать
в формате GIF мы не можем;
- Создаем объект TMemoryStream и "переливаем" в бинарный поток наше изображение;
- Передаем этот поток в Response и отправляем его в броузер клиента.
- Ну и не забываем почистить за собой.
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
Var
Image : TGIFImage;
Stream : TMemoryStream;
BMP : TBitMap;
begin
Image:=TGIFImage.Create;
BMP:=TBitMap.Create;
Stream:=TMemoryStream.Create;
Try
BMP.Width:=100;
BMP.Height:=20;
BMP.Canvas.TextOut(0,0, ReadCounter);
Image.Assign(BMP);
Image.SaveToStream(Stream);
Stream.Position:=0;
Try
Response.ContentType:='image/gif';
Response.ContentStream:=Stream;
Response.SendResponse;
Except
Stream.Free;
End;
Finally
Image.Free;
BMP.Free;
End;
end;
Теперь при каждом обращении к адресу http://localhost/cgi-bin/counter.exe ,
в уголке броузера будет выводится новое значение счетчика. Можно вcтавлять
этот вызов в свои html-страницы.
Примечание:
Кого-то может заинтересовать , отчего объекты Image и BMP я освобождаю в любом случае,
а Stream только в случае неудачного завершения персылки данных клиенту?
Дело в том, что сразу после успешной отработки метода Response.SendResponse объект
Stream окажется ... уже разрушенным.
И попытка освободить его еще раз приведёт к
Access Violation. Можете поэкспериментировать сами.
Цель примера - показать в общем эту технологию, но
оставлять этот код как образец счетчика нельзя, больно он убого выглядит.
Стоило возиться с графикой, чтобы просто "накарябать цифирьку"?..
Усложним нашу задачу. Сделаем более универсальный счетчик с настраиваемыми параметрами.
Прежде чем наводить красоту, нужно решить, как мы будем задавать всякие
разные параметры.
Внимательно прочитайте Help по теме TWebRequest, нас будет интересовать
свойство QueryFields : TStrings. Это список параметров запроса по методу GET,
то есть тех параметров, которые передаются в теле строки URL.
Например, запрос
http://localhost/cgi-bin/counter.exe?par1=10&par2=Test&
передаст на сервер список из двух строк:
'par1=10' и 'par2=Test'
Доступ к списку имен параметров и их значений предоставляют свойства
класса TStrings :
property Names[Index: Integer]: string;
и
property Values[const Name: string]: string;
соответственно.
И если нас интересует входной параметр Par, его значение можно получить
из Request.QueryFields.Values['Par'], если такого параметра в строке
запроса не было, в качестве его значения получим пустую строку .
Данные, передаваемые клиенту методом POST (поля формы), будут доступны через
TWebRequest.ContentFields. Для того, чтобы получать нужные нам значения
независимо от способа передачи параметров, напишем вот такую функцию:
//------------------------------------------------------------
// Получить входящие данные независимо от метода запроса
//
// Порядок поиска : 1) Из входящего потока (GET )
// 2) Из полей формы (POST)
// 3) Из cookies
//------------------------------------------------------------
Function GetRequest( Request : TWebRequest ; Value : String) : String;
Begin
Result:=Request.QueryFields.Values[Value];
Result:=IsEmpty( Result , Request.ContentFields.Values[Value]);
Result:=IsEmpty( Result , Request.CookieFields.Values[Value]);
Result:=Trim(Result);
End;
//------------------------------------------------------------
Function IsEmpty( Value , Empty : String): String;
Begin
IF Trim(Value) = '' Then Value:=Empty;
Result:=Value;
End;
//------------------------------------------------------------
Для счетчика это не так важно, но в дальнейшем такая функция пригодится.
Красота - это страшная сила! |
А теперь можно и красоту наводить.
Что бы такого придумать полезного?
Во-первых, заставим скрипт читать значение счетчика из разных файлов.
Например будем передавать ему в качестве параметра номер счетчика.
Тогда можно будет использовать один и тот же скрипт на разных страницах,
передавая ему разные номера (параметр Data) .
Во-вторых, введем такое понятие, как разрядность счетчика, вместо
"162" будем выводить "000162", так выглядит интереснее. И саму разрядность
будем передавать по параметру (параметр NoCount).
И, наконец, просто раскрасим его, читая из входного потока и цвет (параметр Color).
Для защищенной работы скрипта необходимо предусмотреть систему умолчаний,
на случай если параметры будут не заданы или неверно заданы.
Например вот так:
Text:=GetRequest(Request,'Color');
IF NOT IdentToColor( Text , Color ) Then Color:=clWhite;
No:=GetNumeric(GetRequest(Request , 'NoCount');
IF No = 0 Then No:=10;
Здесь использована функция GetNumeric, которая возвращает
целое число вместо строки символов. Ее реализацию смотрите в коде примера.
Вот так изменится код формирования изображения:
// Устанавливаем параметры шрифта, в том числе полученный цвет
BMP.Canvas.Font.Color:=TColor(Color);
BMP.Canvas.Font.Size:=12;
BMP.Canvas.Font.Name:='Arial';
BMP.Canvas.Font.Style:=BMP.Canvas.Font.Style + [fsBold];
// Рассчитываем размер картинки исходя из выбранного шрифта
// ну и прибавляем по пикселю со всех сторон, для красоты :о)
CharW:=BMP.Canvas.TextWidth('0');
BMP.Width:=No*CharW + 2;
BMP.Height:=BMP.Canvas.TextHeight('0') + 2;
// Готовим фон счетчика и рисуем его!
BMP.Canvas.Brush.Color:=ClBlack;
BMP.Canvas.FillRect(RECT(0,0 , BMP.Width,BMP.Height));
BMP.Canvas.TextOut(1 ,1 , ReadCounter(No , FileName));
Вот так будет выглядеть наш счетчик, если никаких параметов не задавать вообще.
http://localhost/cgi-bin/counter.exe

А вот так с параметрами.
http://localhost/cgi-bin/counter.exe?NoCount=5&Color=clAqua

Красота это страшная сила...
Естественно, что после таких наших нововведений и функция ReadCounter и обработчик события OnAction
изменились. Приводить их здесь полностью нет смысла, скачайте пример и поэкспериментируйте
с кодом. Это будет удобнее и полезнее.
Вы еще не обратили внимание на размер исполняемого кода нашего скрипта?
Ничего не поделаешь.. за удобство надо платить.
Если переписать этот счетчик в стиле Алексея Еремеева, размер исполняемого кода сократится в два раза :о)
Елена Филиппова
Специально для Королевства "Delphi"
К материалу прилагаются файлы:
[TWebModule] [CGI]
Обсуждение материала [ 10-01-2005 18:28 ] 7 сообщений |