Следующие главы были сделаны доступными в OnLine, как небольшие заметки, статьи и "белые книги".
От переводчика: данные заметки были доступны только как статьи в Интернете на странице доктора Боба, с моей стороны было сделано преобразование в формат Word 97, как английского варианта, так и перевод его на русский язык.
Рассматриваются форматы кодирования файлов Интернет, такие как uuencode/decode, xxencode/decode, Base64 encode/decode, сосредоточенные в едином компоненте TBUUCode (часть пакета DrBob42 для Delphi и C++Builder). Также рассмотрены основы HTML (HyperText Markup Language) и CGI (Common Gateway Interface).
В данной главе показывается, как опубликовать вашу базу данных в Интернете путем (1) генерации статических страниц из таблиц базы данных, (2) написания CGI/WinCGI приложений для выполнения запросов к базе данных без использования Delphi Web Modules.
В данной главе приводятся детальные примеры использования WinInet.DLL (только для Win32 разработчиков) с помощью протоколов HTTP и FTP, примеры DrBobFTP и другие магические инструменты Веб-мастера.
Показывает, чем активные формы (ActiveForms) отличаются от обычных ActiveX. Как сделать ActiveForms, затем как использовать их, как распространять и как преобразовать существующие формы в активные формы. Также подробно рассмотрены вопросы безопасности и уменьшение размеров файлов (с помощью использования пакетов), как распространять BDE и использование активных форм для создания n-tier приложений.
Краткое введение в CGI, WinCGI и ISAPI/NSAPI, с помощью Web Modules, WebDispatcher и WebAction компонент. Генерация HTML с помощью PageProducer, DataSetTableProducer и QueryTablePoducer, включая методы сохранения текущего состояния с помощью куки (cookie) и "невидимых" полей.
Форматы файлов Интернет можно разделить на несколько групп. Во первых форматы передачи файлов по FTP, для чего очень давно была разработана схема uuencode/decode, замененная затем на xxencode/decode. В дальнейшем произошел отказ в пользу Base64 и MIME, которая сейчас используется большинством почтовых программ. Второй тип Интернет форматов это HTML, который со всеми его версиями (часто специфичными для конкретного браузера) и улучшениями сам в себе. Третий тип Интернет форматов это больше интерфейс или протокол связи: CGI, который может быть или стандартный CGI (консольный, или Windows CGI или WinCGI.).
Дельфи имеет сильный набор средств для написания новых компонентов и для иллюстрации мы напишем кодирование с помощью uuencode/uudecode, xxencode/xxdecode и Base64. Мы напишем достаточно мощный компонент, который реализует данные алгоритмы. Новый компонент реализует uuencode и uudecode алгоритмы, которые могут быть использованы для передачи файлов через Интернет (ранее использовались для передачи файлов между Unix системами).
Для более утонченного способа передачи файлов смотри главу об WinInet и FTP компонентах. Эти алгоритмы кодирования файлов д в основном используются для передачи файлов в почте и группах новостей
Необходимость кодирования файлов при передаче является то, что в файле могут находиться любые двоичные данные, для этого файл преобразовывается в "читаемую" или "печатаемую" форму в набор из 64 символов: [`!"#$%&'()*+,-./0123456789:;<=?@ABC…XYZ[\]^_], чтобы кодированный файл прошел через различные сети и почтовые шлюзы. Эти 64 печатных символа представлены в следующей таблице.
Набор символов UEncode | |||||||
---|---|---|---|---|---|---|---|
0 ` | 8 ( | 16 0 | 24 8 | 32 @ | 40 H | 48 P | 56 X |
1 ! | 9 ) | 17 1 | 25 9 | 33 A | 41 I | 49 Q | 57 Y |
2 " | 10 * | 18 2 | 26 : | 34 B | 42 J | 50 R | 58 Z |
3 # | 11 + | 19 3 | 27 ; | 35 C | 43 K | 51 S | 59 [ |
4 $ | 12 , | 20 4 | 28 < | 36 D | 44 L | 52 T | 60 \ |
5 % | 13 – | 21 5 | 29 = | 37 E | 45 M | 53 U | 61 ] |
6 & | 14 . | 22 6 | 30 > | 38 F | 46 N | 54 V | 62 ^ |
7 ' | 15 / | 23 7 | 31 ? | 39 G | 47 O | 55 W | 63 _ |
Алгоритм выдает файл состоящий из строки заголовка, за ней несколько кодированных строк и в конце завершающая строка.
Любые строки до строки заголовка или после завершающей строки игнорируются (так как они не содержат специальных ключевых слов "begin" или "end", которые однозначно определяют заголовок и завершающую строку).
Строка заголовка начинается с ключевого слова "begin", за который следует режим файла (четыре восьмеричных цифры) и имя файла, разделенные пробелом.
Завершающая строка начинается с ключевого слова "end"
Кодированные строки располагаются между заголовком и завершающей строкой, и могут содержать максимум 61 символ, первый символ указывает размер строки и максимум 60 символов сама строка.
Первый символ строки содержит длину строки из набора символов UUEncode, для получения подлинной длины строки из кода символов вычитается 32 ($20).
Строки данных могут содержать максимум 60 символов, это означает, что первый символ строки (длина) может быть 'M' (60 символ набора символов UUEncode).
Действительные данные группируются по четыре байта.
Три символа из входного фала (3 * 8 = 24 бита) кодируются в четыре символа, так что каждый из них содержит только 6 бит, то есть значения от 0 до 63.
Результат затем используется как индекс в таблицу набора символов UUEncode.
Так как каждый кодированный символ представляет из себя простой символ таблицы ASCII начинающийся с позиции 33 и до позиции 64 + 32 = 96, то мы можем просто прибавить ASCII значение символа пробела, что бы получить требуемый UUкодированный символ.
Алгоритм преобразовывает три двоичных символа (Triplet) в четыре (Kwartet) UUкодированных символа и может быть реализован в Паскале следующим образом.
procedure Triplet2Kwartet(const Triplet: TTriplet; var Kwartet: TKwartet);
var
i: Integer;
begin
Kwartet[0] := (Triplet[0] SHR 2);
Kwartet[1] := ((Triplet[0] SHL 4) AND $30) +
((Triplet[1] SHR 4) AND $0F);
Kwartet[2] := ((Triplet[1] SHL 2) AND $3C) +
((Triplet[2] SHR 6) AND $03);
Kwartet[3] := (Triplet[2] AND $3F);
for i:=0 to 3 do
if Kwartet[i] = 0 then
Kwartet[i] := $40 + Ord(SP)
else Inc(Kwartet[i], Ord(SP))
end {Triplet2Kwartet};
Данная процедура состоит из из двух частей: в первой части 24 бита (3 * 8) из триплета преобразовываются в 24 бита (4 * 6) квартета. Во второй части алгоритма, мы добавляем ASCII код символа пробела к каждому квартету. ASCII код символа пробела закодирован как Ord(SP), где SP определен как символ пробела или #32. Заметим, что для случая когда квартет равен 0, то мы не добавляем значение #32, поскольку многие почтовые программы имеют проблемы с этим символом, просто в этом случае добавляем код со значением 64 ($40), в результате получаем вместо пробела код обратного апострофа, который нейтрален к алгоритму декодирования, одинаково работающий как для пробела так и для апострофа.
Говоря о декодировании, реализация его в Паскале преобразования квартетов обратно в триплеты следующая:
procedure Kwartet2Triplet(const Kwartet: TKwartet; var Triplet: TTriplet);
var
i: Integer;
begin
Triplet[0] := ((Kwartet[0] - Ord(SP)) SHL 2) +
(((Kwartet[1] - Ord(SP)) AND $30) SHR 4);
Triplet[1] := (((Kwartet[1] - Ord(SP)) AND $0F) SHL 4) +
(((Kwartet[2] - Ord(SP)) AND $3C) SHR 2);
Triplet[2] := (((Kwartet[2] - Ord(SP)) AND $03) SHL 6) +
((Kwartet[3] - Ord(SP)) AND $3F)
end {Kwartet2Triplet};
Если размер триплета в файле менее 3 байт (4 байта в квартете), то производится добавление структуры нулями при кодировании и декодировании.
UUкодирование было наиболее популярным форматом 64 битного кодирования. Ограничение состояло в том, что набор символов не мог транслироваться между наборами ASCII и EBCDIC (IBM мейнфреймы). XXencode очень похож на UUEncode, просто используется другой набор символов, что более удобно между различными типами систем, например как указано выше между EBCDIC и ASCII.
Набор символов XXEncode | |||||||
---|---|---|---|---|---|---|---|
0 + | 8 6 | 16 E | 24 M | 32 U | 40 c | 48 k | 56 s |
1 – | 9 7 | 17 F | 25 N | 33 V | 41 d | 49 l | 57 t |
2 0 | 10 8 | 18 G | 26 O | 34 W | 42 e | 50 m | 58 u |
3 1 | 11 9 | 19 H | 27 P | 35 X | 43 f | 51 n | 59 v |
4 2 | 12 A | 20 I | 28 Q | 36 Y | 44 g | 52 o | 60 w |
5 3 | 13 B | 21 J | 29 R | 37 Z | 45 h | 53 p | 61 x |
6 4 | 14 C | 22 K | 30 S | 38 a | 46 i | 54 q | 62 y |
7 5 | 15 D | 23 L | 31 T | 39 b | 47 j | 55 r | 63 z |
Заметим что если для UUEncodeиспользуется подмножество набора символов ASCII (32..96), то для XXEncode это не так.
Для преобразования процедур Triplet2Kwartet и Kwartet2Triplet для поддержки мы вводим дополнительный массив из 64 символов.
Нам также необходимо модифицировать процедуры Triplet2Kwartet и Kwartet2Triplet следующим образом.
const
XX: Array[0..63] of Char =
'+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
procedure Triplet2Kwartet(const Triplet: TTriplet; var Kwartet: TKwartet);
var
i: Integer;
begin
Kwartet[0] := (Triplet[0] SHR 2);
Kwartet[1] := ((Triplet[0] SHL 4) AND $30) +
((Triplet[1] SHR 4) AND $0F);
Kwartet[2] := ((Triplet[1] SHL 2) AND $3C) +
((Triplet[2] SHR 6) AND $03);
Kwartet[3] := (Triplet[2] AND $3F);
for i:=0 to 3 do
if Kwartet[i] = 0 then Kwartet[i] := $40 + Ord(SP)
else Inc(Kwartet[i],Ord(SP));
if XXCode then
for i:=0 to 3 do Kwartet[i] := Ord(XX[(Kwartet[i] - Ord(SP)) mod $40])
end {Triplet2Kwartet};
Последние несколько строк новые для процедуры Triplet2Kwartet и мы используем набор символов XXencode для возврата правильно закодированных символов. Помните, что UUEncode возвращает индекс кодированного символа, после чего мы к нему добавляем код #32, так что если XXencode используется после преобразования в UUEncode, то мы должны вычесть 32 и использовать результат как индекс в таблицу символов XXencode.
То же самое относится и к процедуре Kwartet2Triplet, где мы должны преобразовать XXencode символы перед использованием алгоритма UUdecode (заметим, что мы теперь не передаем Kwartet как const).
procedure Kwartet2Triplet(Kwartet: TKwartet; var Triplet: TTriplet);
var
i: Integer;
begin
if XXCode then
begin
for i:=0 to 3 do
begin
case Chr(Kwartet[i]) of
'+': Kwartet[i] := 0 + Ord(SP);
'-': Kwartet[i] := 1 + Ord(SP);
'0'..'9': Kwartet[i] := 2 + Kwartet[i]
- Ord('0') + Ord(SP);
'A'..'Z': Kwartet[i] := 12 + Kwartet[i]
- Ord('A') + Ord(SP);
'a'..'z': Kwartet[i] := 38 + Kwartet[i]
- Ord('a') + Ord(SP)
end
end
end;
Triplet[0] := ((Kwartet[0] - Ord(SP)) SHL 2) +
(((Kwartet[1] - Ord(SP)) AND $30) SHR 4);
Triplet[1] := (((Kwartet[1] - Ord(SP)) AND $0F) SHL 4) +
(((Kwartet[2] - Ord(SP)) AND $3C) SHR 2);
Triplet[2] := (((Kwartet[2] - Ord(SP)) AND $03) SHL 6) +
((Kwartet[3] - Ord(SP)) AND $3F)
end {Kwartet2Triplet};
Заметим, что в новой версии этих процедур используется глобальная переменная XXCode логического типа для определения типа кодирования.
Алгоритм кодирования Base64 отличается от алгоритмов UUencode и XXencode тем, что в нем не используется первый символ как индикатор длины. Общее то что используется алгоритм преобразования триплетов в квартеты с помощью 64 байтной таблицы преобразования.
Набор символов Base64 | |||||||
---|---|---|---|---|---|---|---|
0 A | 8 I | 16 Q | 24 Y | 32 g | 40 o | 48 w | 56 4 |
1 B | 9 J | 17 R | 25 Z | 33 h | 41 p | 49 x | 57 5 |
2 C | 10 K | 18 S | 26 a | 34 I | 42 q | 50 y | 58 6 |
3 D | 11 L | 19 T | 27 b | 35 j | 43 r | 51 z | 59 7 |
4 E | 12 M | 20 U | 28 c | 36 k | 44 s | 52 0 | 60 8 |
5 F | 13 N | 21 V | 29 d | 37 l | 45 t | 53 1 | 61 9 |
6 G | 14 O | 22 W | 30 e | 38 m | 46 u | 54 2 | 62 + |
7 H | 15 P | 23 X | 31 f | 39 n | 47 v | 55 3 | 63 / |
Подобно набору символов XXencode, набор символов Base64 не является подмножеством набора символов ASCII.
Это означает, что мы должны добавить массив преобразования в набор символов Base64 и также преобразовать процедуры Triplet2Kwartet и Kwartet2Triplet для поддержки данного алгоритма:
const
B64: Array[0..63] of Char =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
procedure Triplet2Kwartet(Const Triplet: TTriplet; var Kwartet: TKwartet);
var
i: Integer;
begin
Kwartet[0] := (Triplet[0] SHR 2);
Kwartet[1] := ((Triplet[0] SHL 4) AND $30) +
((Triplet[1] SHR 4) AND $0F);
Kwartet[2] := ((Triplet[1] SHL 2) AND $3C) +
((Triplet[2] SHR 6) AND $03);
Kwartet[3] := (Triplet[2] AND $3F);
for i:=0 to 3 do
if Kwartet[i] = 0 then Kwartet[i] := $40 + Ord(SP)
else Inc(Kwartet[i],Ord(SP));
if Base64 then
for i:=0 to 3 do
Kwartet[i] := Ord(B64[(Kwartet[i] - Ord(SP)) mod $40])
else
if XXCode then
for i:=0 to 3 do
Kwartet[i] := Ord(XX[(Kwartet[i] - Ord(SP)) mod $40])
end {Triplet2Kwartet};
procedure Kwartet2Triplet(Kwartet: TKwartet; var Triplet: TTriplet);
var
i: Integer;
begin
if Base64 then
begin
for i:=0 to 3 do
begin
case Chr(Kwartet[i]) of
'A'..'Z': Kwartet[i] := 0 + Kwartet[i]
- Ord('A') + Ord(SP);
'a'..'z': Kwartet[i] := 26+ Kwartet[i]
- Ord('a') + Ord(SP);
'0'..'9': Kwartet[i] := 52+ Kwartet[i]
- Ord('0') + Ord(SP);
'+': Kwartet[i] := 62+ Ord(SP);
'/': Kwartet[i] := 63+ Ord(SP);
end
end
end
else
if XXCode then
begin
for i:=0 to 3 do
begin
case Chr(Kwartet[i]) of
'+': Kwartet[i] := 0 + Ord(SP);
'-': Kwartet[i] := 1 + Ord(SP);
'0'..'9': Kwartet[i] := 2 + Kwartet[i]
- Ord('0') + Ord(SP);
'A'..'Z': Kwartet[i] := 12 + Kwartet[i]
- Ord('A') + Ord(SP);
'a'..'z': Kwartet[i] := 38 + Kwartet[i]
- Ord('a') + Ord(SP)
end
end
end;
Triplet[0] := ((Kwartet[0] - Ord(SP)) SHL 2) +
(((Kwartet[1] - Ord(SP)) AND $30) SHR 4);
Triplet[1] := (((Kwartet[1] - Ord(SP)) AND $0F) SHL 4) +
(((Kwartet[2] - Ord(SP)) AND $3C) SHR 2);
Triplet[2] := (((Kwartet[2] - Ord(SP)) AND $03) SHL 6) +
((Kwartet[3] - Ord(SP)) AND $3F)
end {Kwartet2Triplet};
Заметим, что в новой версии появилась новая глобальная переменная, которая используется для определения формата кодирования.
MIME означает Multipurpose Internet Mail Extensions (Расширение форматов Интернет почты), в котором международным стандартом является кодирование Base64. Данное расширение было разработано для многоязычной поддержки и преобразования символов между системами (такими как IBM мейнфреймы, системы на базе UNIX, Macintosh и IBM PC).
MIME алгоритм кодирования базируется на RFC1341 как MIME Base64. Подобно UUencode, назначение MIME кодировать двоичные файлы так, что бы они смогли пройти через различные почтовые системы, и MIME использует для этого алгоритм кодирования Base64, плюс набор специальных ключевых слов и опций, которые используются для более детализированной информации о содержимом MIME.
Определение интерфейса компонента TUUCode, базируется на ранее приведенных и объясненных процедур Triplet2Kwartet и Kwartet2Triplet, заметим, что ниже приведенный код использует условное компилирование в зависимости от версий Delphi и C++Builder.
unit UUCode;
interface
uses
{$IFDEF WIN32}
Windows,
{$ELSE}
WinTypes, WinProcs,
{$ENDIF}
SysUtils, Messages, Classes, Graphics, Controls, Forms;
{$IFNDEF WIN32}
type
ShortString = String;
{$ENDIF}
type
EUUCode = class(Exception);
TAlgorithm = (filecopy, uuencode, uudecode, xxencode, xxdecode, Base64encode, Base64decode);
TUnixCRLF = (CRLF, LF);
TProgressEvent = procedure(Percent:Word) of Object;
TBUUCode = class(TComponent)
public
{ Public class declarations (override) }
constructor Create(AOwner: TComponent); override;
private
{ Private field declarations }
FAbout: ShortString;
FActive: Boolean;
FAlgorithm: TAlgorithm;
FFileMode: Word;
FHeaders: Boolean;
FInputFileName: TFileName;
FOutputFileName: TFileName;
FOnProgress: TProgressEvent;
FUnixCRLF: TUnixCRLF;
{ Dummy method to get read-only About property }
procedure Dummy(Ignore: ShortString);
protected
{ Protected Activate method }
procedure Activate(GoActive: Boolean);
public
{ Public UUCode interface declaration }
procedure UUCode;
published
{ Published design declarations }
property About: ShortString read FAbout write Dummy;
property Active: Boolean read FActive write Activate;
property Algorithm: TAlgorithm read Falgorithm write FAlgorithm;
property FileMode: Word read FFileMode write FFileMode;
property Headers: Boolean read FHeaders write FHeaders;
property InputFile: TFileName read FInputFileName write FInputFileName;
property OutputFile: TFileName read FOutputFileName write FOutputFileName;
property UnixCRLF: TUnixCRLF read FUnixCRLF write FUnixCRLF;
published
{ Published Event property }
property OnProgress: TProgressEvent read FOnProgress write FOnProgress;
end {TUUCode};
TUUCode компонент имеет восемь опубликованных свойств (мы здесь опустим описание обработчиков событий):
Свойство About содержит информацию о правах и версии.
Свойство Active может использоваться для вызова преобразования UUCode во время разработки (design time), подобно свойству Active у TTables и Tquery компонент.
Свойство Algorithm содержит информацию об алгоритме кодирования для метода UUCode. Реализованы следующие алгоритмы:
· filecopy – простое копирование файла InputFile в файл OutputFile
· uuencode – копирование файла с помощью алгоритма uuencode из файла InputFile и генерация файла OutputFile
· uudecode – копирование файла с помощью алгоритма uudecode из файла InputFile (и генерация файла OutputFile, если не используется Headers)
· xxencode – копирование файла с помощью алгоритма xxencode из файла InputFile и генерация файла OutputFile
· xxdecode – копирование файла с помощью алгоритма xxdecode из файла InputFile (и генерация файла OutputFile, если не используется Headers)
· Base64encode – копирование файла с помощью алгоритма Base64 encode InputFile и генерация файла OutputFile
· Base64decode – копирование файла с помощью алгоритма Base64 decode InputFile (и генерация файла OutputFile, если не используется Headers)
Свойство FileMode содержит шестнадцатеричное значение режима файла (обычно 0644 или 0755). Заметим, что режим задается с помощью десятичных цифр.
Свойство Headers может быть использовано для указания должны или нет использоваться заголовки begin-end в алгоритме кодирования или ожидаются в алгоритме декодирования. Значение по умолчанию True.
Свойство InputFile содержит имя входного файла для кодирования/декодирования.
Свойство OutputFile содержит имя выходного файла, в который будет записан результат кодирования. Заметим, что свойство OutputFile игнорируется при декодировании, если входной файл имеет заголовки, которые определяют имя файла для декодирования.
Свойство UnixCRLF используется для указания разделителей строк специфичных для Unix систем, только Line Feed (перевод строки) или DOS/Windows, где используется пара Carriage Return/Line Feed (возврат каретки/ перевод строки). По умолчанию CRLF, но как минимум вы имеете возможность кодировать и декодировать файлы для Unix систем.
Компонент TUUCode имеет три метода; один public конструктор, один protected метод и один public метод:
Конструктор Create используется для создания компонента и инициализации свойств ао умолчанию (default) для Active, FileMode, Headers и About.
Метод Activate используется для вызова метода UUCode во время разработки, когда вы изменяете состояние свойства в True. При необходимости вы можете вызвать этот метод напрямую, так как это проще вызова метода UUCode.
Метод UUCode это метод, в котором в действительности производится кодирование/декодирование входного файла (InputFile), базируясь на состоянии других свойств компонента TUUCode.
Компонент TUUCode имеет только одно такое свойство:
Событие OnProgress может использоваться как callback функция, позволяющая компоненту TUUCode выдавать текущий процент обработки входного файла. Использовать эту информацию вы можете с компонентами 16-битным TGauge или 32-битным TprogressBar, для примера показывая прогресс выполнения кодирования/декодирования от 0 до 100%.
Кодирование/декодирование больших документов может занимать значительное время даже при использовании быстрой машины и быстрых дисков. Поэтому приятно иметь такую возможность показывать процесс выполнения. Для реализации вам нужно создать обработчик события.
Обработчик состоит из двух частей, сигнализатора и обработчика события. Сигнализатор должен быть уверен, что компонент в состоянии принять сообщение определенного типа и сгенерировать событие. Обработчик события с другой стороны начинает работать только при поступлении события.
Сигнализатор типично виртуальный или динамический метод самого класса (подобно методу Click) или сообщению Windows, такому как оповещение (notification) или callback сообщения. Обработчик события типично присваивается свойству, такому как OnClick, OnChange или OnProgress. Если обработчик события опубликован, то конечный пользователь компонента может написать любой код для обработки события.
Обработчики события методы объекта. Это означает, что они должны быть методами класса, а не обычной процедурой или функцией (первый параметр должен быть Self). Для наиболее употребимых обработчиков предназначен следующий тип:
TNotifyEvent = procedure(sender: TObject) of object;
Тип TNotifyEvent для обработчиков, в которые передается только один параметр sender. Эти события просто оповещают компонент о возникновении специфического события у объекта sender. Например, OnClick, типа TNotifyEvent, указывает органу, что произошло событие click у конкретного органа. Если бы параметр Sender отсутствовал, то мы бы знали только, то, что произошло определенное событие, но не знали бы у какого органа. Обычно нам требуется знать, у какого конкретно органа произошло данное событие, что бы мы могли работать с этим органом или его данными..
Как было указано ранее, Обработчики событий присваиваются свойствам типа событие (event), и они появляются как отдельная закладка в инспекторе объектов (Object Inspector), для различения их от обычных свойств. Основой для помещения этого свойства на закладку события является то, что они должны быть типа "procedure/function of Object". Фраза "of Object" обязательна, иначе мы получим сообщение об ошибке "cannot publish property".
Компоненту TUUCode требуется событие типа TProgressEvent. Данному событию реально не требуется параметр sender (это всегда можно добавить позже), но ему необходим процент выполнения операции, для цели опишем следующий прототип:
TProgressEvent = procedure(percent: Word) of object;
Сигнализаторы событий требуются для указания обработчику события, что возникло указанное событие, что бы обработчик события смог бы выполнить свои действия. Сигнализаторы обычно виртуальные или динамические методы класса (подобно методу Click) или сообщения Windows, такие как callback ил notification сообщения.
В случае с компонентом TUUCode, сигнализатор интегрирован непосредственно в метод UUCode. После кодирования каждой строки, вызывается обработчик события назначенный OnProgress, реализация этого следующая:
if Assigned(FOnProgress) then FOnProgress(trunc((100.0 * Size) / OutputBufSize))
Где Size это текущий размер или позиция в выходном буфере, и OutputBufSize это размер выходного файла. Size увеличивается от нуля до OutputBufSize, что означает, что обработчик события FOnProgress вызывается с аргументом от 0 до 100.
При регистрации компонента TUUCode, полезно добавить редактор свойства FileName (InputFile), что обеспечит дополнительный комфорт для конечного пользователя. Редактор этого свойства реализован в модуле UUReg, который регистрирует компонент TUUCode в палитре компонентов Дельфи.
unit UUReg;
interface
{$IFDEF WIN32}
{$R UUCODE.D32}
{$ELSE}
{$R UUCODE.D16}
{$ENDIF}
uses
DsgnIntf;
type
TFileNameProperty = class(TStringProperty)
public
function GetAttributes: TPropertyAttributes; override;
procedure Edit; override;
end;
procedure Register;
implementation
uses
UUCode, Classes, Dialogs, Forms, SysUtils;
function TFileNameProperty.GetAttributes: TPropertyAttributes;
begin
Result := [paDialog]
end {GetAttributes};
procedure TFileNameProperty.Edit;
begin
with TOpenDialog.Create(Application) do
try
Title := GetName; { name of property as OpenDialog caption }
Filename := GetValue;
Filter := 'All Files (*.*)|*.*';
HelpContext := 0;
Options := Options +
[ofShowHelp, ofPathMustExist, ofFileMustExist];
if Execute then SetValue(Filename);
finally
Free
end
end {Edit};
procedure Register;
begin
{ component }
RegisterComponents('DrBob42', [TUUCode]);
{ property editor }
RegisterPropertyEditor(TypeInfo(TFilename), nil,
'InputFile', TFilenameProperty);
end {Register};
end.
Если вы желаете использовать компонент TUUCode в составе, какого либо пакета, то вы должны поместить компонент UUCode в пакет времени выполнения (runtime package), и модуль UUReg в пакет разработки (design-time), который требует пакет времени выполнения. В действительности, для использования пакетов вы можете использовать UUCode Wizard из следующей главы в пакет времени разработки и сделать его доступным в IDE Delphi для всех пользователей!
Для показа прогресса 16-битный пример использует TGauge компонент, в то же время 32-битная версия использует Windows 95 Progress Control.
рис. 1.1. 16-битная версия примера UUCode
рис. 1.2. 32-битная версия примера UUCode
Во время исполнения программы могут возникнуть два исключения. Если входной файл пуст и во время кодирования, если выходной файл пуст. Для 16 битной версии может возникнуть третье исключение, если входной или выходной файл больше 65000 байт (16-битная версия данного компонента может обрабатывать входные и выходные файлы до 64 килобайт). На практике это означает, не может быть более 48 килобайт. 32-битная версия не имеет такого ограничения).
В этой главе мы рассмотрели uuencode/uudecode, xxencode/xxdecode, и Base64 алгоритмы кодирования/декодирования. Мы также разработали простой VCL компонент, который поддерживает эти алгоритмы в дополнение к простому копированию. Свойства, методы и события делают данный компонент пригодным для построения Интернет приложений нуждающихся в подобном преобразовании.
Компонент TBUUCode сейчас часть пакета "DrBob42 component package for Delphi and C++Builder".
Аббревиатура HTML означает HyperText Mark-up Language (язык разметки гипертекстовых документов), который является базовым для построения статических страниц. HTML страница является простым текстовым ASCII файлом с HTML-тегами между "<" и ">" (часто парами). Браузеры, такие как Netscape Navigator и Internet Explorer просто интерпретируют HTML коды на данных страницах для показа заголовков, жирного и наклонного текста, изображений и также фреймов и таблиц. Следующая таблица приводит несколько основных HTML тегов, которые будут рассмотрены далее в этой главе.
HTML tag | text effect |
---|---|
… | Ограничивает HTML страницу |
Секция заголовков | |
Заголовок документа | |
… | Секция содержимого страницы |
… |
Заголовок (возможные уровни 1..6) |
… | Жирный текст |
… | Наклонный текст |
Разрыв строки | |
Горизонтальная черта | |
Абзац | |
…. | Ссылка на другую страницу или URL |
HTML страница всегда начинается тегом и заканчивается тегом .
Содержимое страницы размещается между тегами
и . Множественные разрывы строк и пробелы/табуляции игнорируются и заменяются на один пробел, это причина, по которой требуется специальный тег разрыва строки. Простая HTML страница с одним заголовком и ссылкой выглядит так.
Hello, world!
Dr.Bob's Delphi Clinic
Примечание: тег
, который мы можем использовать для помещения адресной информации и ссылки на домашнюю страницу или e-mail адрес. Эта информация будет отображена наклонным шрифтом. Тег часть основы HTML; данная форма используется для указания гипер-ссылки, в данном случае на другую страницу (мою домашнюю страницу) по адресу http://www.drbob42.com. Для данной простой HTML страницы, браузер (такой как Netscape Navigator) покажет одну страницу с заголовком и с ссылкой.Для освоения HTML я могу рекомендовать хорошую книгу "Netscape & HTML Explorer".
Аббревиатура CGI означает Common Gateway Interface, и является связевым протоколом между формой в Web браузере (клиент) и приложением запущенным на Web сервере (сервер). Приложение обычно называется CGI скрипт, но мы можем использовать Дельфи для написания CGI приложений без скриптов.
Имеется два типа CGI: стандартное или консольное CGI приложение и позже появилась версия для Windows называемая WinCGI.
Стандартное или консольное CGI приложение взаимодействует с формой на клиенте с помощью переменных среды (управляющая информация), стандартным входом (данные формы) и стандартным выводом (возвращаемая динамическая HTML страница).
Приложение WinCGI взаимодействует с формой на клиенте с помощью Windows.INI файла вместо переменных среды. Windows.INI файл содержит управляющую информацию, иногда данные формы и имена входного, данных и выходного файлов.
В данной главе я расскажу, как написать простое Дельфи CGI приложение, без использования Web Modules или других Client/Server модулей.
Во первых аббревиатура CGI означает Common Gateway Interface, и это только имя для передачи информации от клиента серверу. На клиентской стороне это реализуется с помощью формы, содержащей только теги. На серверной стороне
На сервере запускается CGI приложение, которое иногда называется CGI скрипт (для примера на Unix машинах, где Perl используется для исполнения CGI скриптов).
В данной главе я сконцентрирую внимание на написание CGI приложения для Windows NT web сервера, и использовании 32-битной Дельфи (например Delphi 2.x или 3.x) для данной задачи, данный код может быть также без проблем откомпилирован в C++Builder.
Стандартное CGI приложение получает данные через стандартный вход и должно выдать ответ через стандартный вывод. (например сгенерированную HTML страницу). Это означает необходимость написания консольного приложения. Если даже нет входных данных мы все равно можем использовать CGI приложение для генерации динамических HTML страниц (например для выдачи данных их таблицы).
Для начала посмотрим на стандартное "hello world" CGI приложение. Единственное, что оно должно сделать, это вернуть HTML страницу со строкой "hello, world". Перед тем как мы начнем делать это – обратим внимание на следующее: CGI приложение должно сообщить миру какой (MIME) формат оно выдает. В нашем случае это "text/html", которое мы должны указать как: content-type: text/html , и затем одну пустую строку.
Вот код нашего первого "Hello, world!" CGI приложения:
program CGI1;
{$APPTYPE CONSOLE}
begin
writeln('content-type: text/html');
writeln;
writeln('
writeln('
writeln('Hello, world!');
writeln('
writeln('
end.
Если вы откомпилируете данную программу в Дельфи 2 или 3 и затем запустите ее из web браузера подключенного к web серверу, где оно записано в исполнимом виде в исполняемом каталоге таком как cgi-bin, то вы увидите текст "Hello, world!" на странице.
Теперь, мы знаем как создавать CGI приложение, которое может генерировать динамическую HTML страницу (или в действительности почти статическую). Но как насчет ввода? Здесь более чем одно действие: мы должны проверять переменную DOS 'CONTENT LENGTH' что бы знать как много символов мы можем прочитать со стандартного ввода (если мы попытаемся читать больше чем есть, то мы повиснем навсегда). Конечно, это широко известный факт. Я написал компонент TBDosEnvironment чтобы вы могли иметь доступ до переменных среды DOS:
unit DrBobDOS;
interface
uses
SysUtils, WinTypes, WinProcs, Classes;
type
TBDosEnvironment = class(TComponent)
public
{ Public class declarations (override) }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
private
{ Private field declarations }
FDosEnvList: TStringList;
procedure DoNothing(const Value: TStringList);
protected
{ Protected method declarations }
Dummy: Word;
function GetDosEnvCount: Word;
public
{ Public interface declarations }
function GetDosEnvStr(const Name: String): String;
{ This function is a modified version of the GetEnvVar function thatappears in the WinDos unit that comes with Delphi. This function's interface uses Pascal strings instead of null-terminated strings.
}
published
{ Published design declarations }
property DosEnvCount: Word read GetDosEnvCount write Dummy;
property DosEnvList: TStringList read FDosEnvList write DoNothing;
end;
implementation
constructor TBDosEnvironment.Create(AOwner: TComponent);
var
P: PChar;
begin
inherited Create(AOwner);
FDosEnvList := TStringList.Create;
{$IFDEF WIN32}
P := GetEnvironmentStrings;
{$ELSE}
P := GetDosEnvironment;
{$ENDIF}
while P^ <> #0 do
begin
FDosEnvList.Add(StrPas(P));
Inc(P, StrLen(P)+1) { Fast Jump to Next Var }
end;
end {Create};
destructor TBDosEnvironment.Destroy;
begin
FDosEnvList.Free;
FDosEnvList := nil;
inherited Destroy
end {Destroy};
procedure TBDosEnvironment.DoNothing(const Value: StringList);
begin
end {DoNothing};
function TBDosEnvironment.GetDosEnvCount: Word;
begin
if Assigned(FDosEnvList) then
Result := FDosEnvList.Count
else
Result := 0;
end {GetDosEnvCount};
function TBDosEnvironment.GetDosEnvStr(const Name: String): String;
var
i: Integer;
Tmp: String;
begin
i := 0;
Result := '';
if Assigned(FDosEnvList) then while i < FDosEnvList.Count do
begin
Tmp := FDosEnvList[i];
Inc(i);
if Pos(Name,Tmp) = 1 then
begin
Delete(Tmp,1,Length(Name));
if Tmp[1] = '=' then
begin
Delete(Tmp,1,1);
Result := Tmp;
i := FDosEnvList.Count { end while-loop }
end
end
end
end {GetDosEnvStr};
end.
Здесь список переменных среды (предоставленный Deepak Shenoy), которые доступны для CGI программ. Даже ISAPI программы могут использовать эти переменные:
Environment Variable | Purpose/Meaning/Value |
---|---|
GATEWAY_INTERFACE | Версия CGI для которой скомпилирован web сервер |
SERVER_NAME | IP адрес сервера или имя. |
SERVER_PORT | Порт на сервере, которые принимает HTTP запросы. |
SERVER_PROTOCOL | Имя и версия протокола, используемая для обработки запросов. |
SERVER_SOFTWARE | Имя (и обычно версия) программного обеспечения сервера. |
AUTH_TYPE | Схема проверки прав используемая сервером (NULL, BASIC) |
CONTENT_FILE | Файл используемый для передачи данных CGI программе (только Windows HTTPd/WinCGI). |
CONTENT_LENGTH | Количество байтов переданное на стандартный вход (STDIN) как содержимое POST запроса. |
CONTENT_TYPE | Тип данных переданных на сервер. |
OUTPUT_FILE | Имя файла для результата (только Windows HTTPd/WinCGI). |
PATH_INFO | Дополнительный, относительный путь переданный на сервер после имени скрипта, но до данных запроса. |
PATH_TRANSLATED | Та же самая информация, но преобразованная из относительного пути в абсолютный. |
QUERY_STRING | Данные переданные как часть URL, все после символа ? в URL. |
REMOTE_ADDR | Адрес IP или имя сервера конечного пользователя. |
REMOTE_USER | Имя пользователя, если используется схема проверки прав. |
REQUEST_LINE | Полный HTTP запрос представляемый сервером (зависит от сервера). |
REQUEST_METHOD | Указывает метод передачи данных, как часть URL (GET) или через стандартный ввод STDIN (POST). |
SCRIPT_NAME | Имя запущенного скрипта. |
Немного еще дополнительной, но важной информации. Немного об переменных среды, которые особо важны для обработки запроса, и небольшое описание по обработке стандартных CGI приложений:
REQUEST_METHOD – указывает, как посланы данные, как POST или как GET метод.
QUERY_STRING – если используется GET
CONTENT_LENGTH – если мы используем POST, то мы должны прочитать "CONTENT_LENGTH" символов со стандартного ввода (которые оканчиваются "Query", подобно QUERY_STRING при использовании метода GET).
Во всех случаях стандартное CGI приложение должно писать свой вывод на стандартный выход, если мы используем консольное приложение.
Теперь с помощью компонента TBDosEnvironment мы создадим приложение, которое примет все три переменных среды, описанных выше и получит необходимые данные. После этого мы напишем код генерирующий вывод.
Правда просто? Для другого очень маленького (39 Кб) стандартного CGI приложения, проверьте Search Engine на моем web сайте. Краткий исходный код будет опубликован в одной из статей в The Delphi Magazine, но я могу сказать, что базовый протокол CGI связи не более сложный, чем представленный здесь.
Сейчас мы попробуем прочитать запрос в стандартном CGI приложении с помощью 32-битной версии Дельфи (Delphi 2.x или 3.x).
Обычно это двух ступенчатый процесс. Первый шаг создание HTML и специальный CGI Form-тегов, второй шаг получение данных внутри CGI приложения на сервере.
HTML CGI форма определяется с помощью тегов
. Открывающий тег также содержит имя метода (GET or POST) и действие, которое является URLом CGI приложения на web сервере. Например:
…
Данная HTML CGI форма посылает свои данные методом POST на мой web сервер, и выполняет программу debug.exe (из каталога cgi-bin). В данный момент мы пока не знакомы с концепцией различий между методами POST и GET (Я всегда использую метод POST). Мы заметим, что здесь пока нет ничего что бы посылать на сервер методом POST, это позже. Мы должны указать поля ввода внутри CGI формы. Для этого мы поместим некоторое количество наиболее стандартных Windows органов управления, все они предопределены, подобно editbox, memo, listbox, drop-down combobox, radiobuttons, checkboxes и конечно клавиши "action" (reset или submit).
Простой editbox это поля ввода типа "text", которое обязано иметь имя и необязательно размер и ширину в пикселях, и может иметь значение:
Результатом этой фразы будет нарисован editbox в котором можно ввести до восьми символов, и которое будет послано нашему CGI приложению как "login=xxxxxxxx", где xxxxxxxx данные введенные на форме в окошке подобному этому
Стандартное CGI приложение обязано проверить переменную среды REQUEST-METHOD для определения метода передачи данных. В случае POST, мы должны проверить CONTENT-LENGTH для определения количества символов, которые необходимо прочесть со стандартного ввода. Стандартный ввод содержит данные (такие как "login-xxxxxxxx") для нашего CGI приложения.
Вместо написания сложного стартового кода для каждого CGI приложения, я написал модуль DrBobCGI для выполнения всех необходимых стартовых процедур и извлечения входных данных и доступных затем через вызов единственной функции, называемой "Value". Так для выше приведенного примера мы можем вызвать "Value('login')" для получения строки 'xxxxxxxx'.
unit DrBobCGI;
{$I-}
interface
var
ContentLength: Integer = 0;
function Value(const Field: ShortString): ShortString;
{ use this function to get the CGI inputquery values }
implementation
uses
SysUtils, Windows;
var
Data: String = '';
function Value(const Field: ShortString): ShortString;
var
i: Integer;
begin
Result := '';
i := Pos(Field+'=',Data);
if i > 0 then
begin
Inc(i,Length(Field)+1);
while Data[i] <> '&' do
begin
Result := Result + Data[i];
Inc(i)
end
end
end {Value};
var
P: PChar;
i: Integer;
Str: ShortString;
type
TRequestMethod = (Unknown,Get,Post);
var
RequestMethod: TRequestMethod = Unknown;
initialization
P := GetEnvironmentStrings;
while P^ <> #0 do
begin
Str := StrPas(P);
if Pos('REQUEST_METHOD=',Str) > 0 then
begin
Delete(Str,1,Pos('=',Str));
if Str = 'POST' then RequestMethod := Post
else
if Str = 'GET' then RequestMethod := Get
end;
if Pos('CONTENT_LENGTH=',Str) = 1 then
begin
Delete(Str,1,Pos('=',Str));
ContentLength := StrToInt(Str)
end;
if Pos('QUERY_STRING=',Str) > 0 then
begin
Delete(Str,1,Pos('=',Str));
SetLength(Data,Length(Str)+1);
Data := Str
end;
Inc(P, StrLen(P)+1)
end;
if RequestMethod = Post then
begin
SetLength(Data,ContentLength+1);
for i:=1 to ContentLength do read(Data[i]);
Data[ContentLength+1] := '&';
{ if IOResult <> 0 then { skip }
end;
i := 0;
while i < Length(Data) do
begin
Inc(i);
if Data[i] = '+' then Data[i] := ' ';
if (Data[i] = '%') then { special code }
begin
Str := '$00';
Str[2] := Data[i+1];
Str[3] := Data[i+2];
Delete(Data,i+1,2);
Data[i] := Chr(StrToInt(Str))
end
end;
if i > 0 then Data[i+1] := '&'
else Data := '&'
finalization
Data := ''
end.
Я написал кучу CGI приложений за последний год и все они используют модуль DrBobCGI. Теперь реальное пример: стандартное CGI приложение – гостевая книга (guestbook), в которой запрашивается ваше имя и небольшой комментарий, написанное с помощью всего нескольких строк на Дельфи.
Вначале CGI форма:
Dr.Bob's Guestbook
METHOD=POST>
Name:
Comments:
Теперь консольное приложение:
program CGI;
{$I-}
{$APPTYPE CONSOLE}
uses
DrBobCGI;
var
guest: Text;
Str: String;
begin
Assign(guest,'book.htm'); // assuming that's the guestbook
Append(guest);
if IOResult <> 0 then // open new guestbook
begin
Rewrite(guest);
writeln(guest,'');
writeln(guest,'')
end;
writeln(guest,'Date: ',DateTimeToStr(Now),'
');
writeln(guest,'Name: ',Value('name'),'
');
writeln(guest,'Comments: ',Value('comments'),'
');
reset(guest);
while not eof(guest) do // now output guestbook itself
begin
readln(guest,Str);
writeln(Str)
end;
close(guest);
writeln('');
writeln('')
У меня на форме две "submit" клавиши, одна на переход на предыдущую страницу, другая переход на следующую страницу. Как определить какая из них была нажата, чтобы я мог выполнить соответствующее действие.
Вы должны назначить уникальное значение для каждой кнопки "type=submit", ниже приведен соответствующий код:
Edit the information and press the SAVE button
To Delete information, press the DELETE button
Вы должны получить "Action=SAVE" или "Action=DELETE" после нажатия одной из этих кнопок.