Введение

Следующие главы были сделаны доступными в OnLine, как небольшие заметки, статьи и "белые книги".

От переводчика: данные заметки были доступны только как статьи в Интернете на странице доктора Боба, с моей стороны было сделано преобразование в формат Word 97, как английского варианта, так и перевод его на русский язык.

Форматы кодирования файлов Интернет

Рассматриваются форматы кодирования файлов Интернет, такие как uuencode/decode, xxencode/decode, Base64 encode/decode, сосредоточенные в едином компоненте TBUUCode (часть пакета DrBob42 для Delphi и C++Builder). Также рассмотрены основы HTML (HyperText Markup Language) и CGI (Common Gateway Interface).

HTML и CGI/WinCGI "трудный путь"

В данной главе показывается, как опубликовать вашу базу данных в Интернете путем (1) генерации статических страниц из таблиц базы данных, (2) написания CGI/WinCGI приложений для выполнения запросов к базе данных без использования Delphi Web Modules.

Microsoft WinInet

В данной главе приводятся детальные примеры использования WinInet.DLL (только для Win32 разработчиков) с помощью протоколов HTTP и FTP, примеры DrBobFTP и другие магические инструменты Веб-мастера.

Delphi ActiveForms (intranet)

Показывает, чем активные формы (ActiveForms) отличаются от обычных ActiveX. Как сделать ActiveForms, затем как использовать их, как распространять и как преобразовать существующие формы в активные формы. Также подробно рассмотрены вопросы безопасности и уменьшение размеров файлов (с помощью использования пакетов), как распространять BDE и использование активных форм для создания n-tier приложений.

Delphi Web Modules (internet)

Краткое введение в CGI, WinCGI и ISAPI/NSAPI, с помощью Web Modules, WebDispatcher и WebAction компонент. Генерация HTML с помощью PageProducer, DataSetTableProducer и QueryTablePoducer, включая методы сохранения текущего состояния с помощью куки (cookie) и "невидимых" полей.

1. Форматы кодирования файлов Интернет

Форматы файлов Интернет можно разделить на несколько групп. Во первых форматы передачи файлов по FTP, для чего очень давно была разработана схема uuencode/decode, замененная затем на xxencode/decode. В дальнейшем произошел отказ в пользу Base64 и MIME, которая сейчас используется большинством почтовых программ. Второй тип Интернет форматов это HTML, который со всеми его версиями (часто специфичными для конкретного браузера) и улучшениями сам в себе. Третий тип Интернет форматов это больше интерфейс или протокол связи: CGI, который может быть или стандартный CGI (консольный, или Windows CGI или WinCGI.).

1.1. Передача файлов через Интернет

Дельфи имеет сильный набор средств для написания новых компонентов и для иллюстрации мы напишем кодирование с помощью uuencode/uudecode, xxencode/xxdecode и Base64. Мы напишем достаточно мощный компонент, который реализует данные алгоритмы. Новый компонент реализует uuencode и uudecode алгоритмы, которые могут быть использованы для передачи файлов через Интернет (ранее использовались для передачи файлов между Unix системами).

Для более утонченного способа передачи файлов смотри главу об WinInet и FTP компонентах. Эти алгоритмы кодирования файлов д в основном используются для передачи файлов в почте и группах новостей

1.1.1. UUEncode и UUDecode

Необходимость кодирования файлов при передаче является то, что в файле могут находиться любые двоичные данные, для этого файл преобразовывается в "читаемую" или "печатаемую" форму в набор из 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 байта в квартете), то производится добавление структуры нулями при кодировании и декодировании.

1.1.2. XXEncode и XXDecode

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 логического типа для определения типа кодирования.

1.1.3. Base64

Алгоритм кодирования 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};

Заметим, что в новой версии появилась новая глобальная переменная, которая используется для определения формата кодирования.

1.1.4. MIME

MIME означает Multipurpose Internet Mail Extensions (Расширение форматов Интернет почты), в котором международным стандартом является кодирование Base64. Данное расширение было разработано для многоязычной поддержки и преобразования символов между системами (такими как IBM мейнфреймы, системы на базе UNIX, Macintosh и IBM PC).

MIME алгоритм кодирования базируется на RFC1341 как MIME Base64. Подобно UUencode, назначение MIME кодировать двоичные файлы так, что бы они смогли пройти через различные почтовые системы, и MIME использует для этого алгоритм кодирования Base64, плюс набор специальных ключевых слов и опций, которые используются для более детализированной информации о содержимом MIME.

1.1.5. TBUUCode компонент

Определение интерфейса компонента 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};


1.1.6. Свойства

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 систем.

1.1.7. Методы

Компонент TUUCode имеет три метода; один public конструктор, один protected метод и один public метод:

Конструктор Create используется для создания компонента и инициализации свойств ао умолчанию (default) для Active, FileMode, Headers и About.

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

Метод UUCode это метод, в котором в действительности производится кодирование/декодирование входного файла (InputFile), базируясь на состоянии других свойств компонента TUUCode.

1.1.8. События

Компонент TUUCode имеет только одно такое свойство:

Событие OnProgress может использоваться как callback функция, позволяющая компоненту TUUCode выдавать текущий процент обработки входного файла. Использовать эту информацию вы можете с компонентами 16-битным TGauge или 32-битным TprogressBar, для примера показывая прогресс выполнения кодирования/декодирования от 0 до 100%.

Кодирование/декодирование больших документов может занимать значительное время даже при использовании быстрой машины и быстрых дисков. Поэтому приятно иметь такую возможность показывать процесс выполнения. Для реализации вам нужно создать обработчик события.

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

Сигнализатор типично виртуальный или динамический метод самого класса (подобно методу Click) или сообщению Windows, такому как оповещение (notification) или callback сообщения. Обработчик события типично присваивается свойству, такому как OnClick, OnChange или OnProgress. Если обработчик события опубликован, то конечный пользователь компонента может написать любой код для обработки события.

1.1.9. Обработчики событий

Обработчики события методы объекта. Это означает, что они должны быть методами класса, а не обычной процедурой или функцией (первый параметр должен быть 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;

1.1.10. Сигнализаторы событий

Сигнализаторы событий требуются для указания обработчику события, что возникло указанное событие, что бы обработчик события смог бы выполнить свои действия. Сигнализаторы обычно виртуальные или динамические методы класса (подобно методу Click) или сообщения Windows, такие как callback ил notification сообщения.

В случае с компонентом TUUCode, сигнализатор интегрирован непосредственно в метод UUCode. После кодирования каждой строки, вызывается обработчик события назначенный OnProgress, реализация этого следующая:

if Assigned(FOnProgress) then FOnProgress(trunc((100.0 * Size) / OutputBufSize))

Где Size это текущий размер или позиция в выходном буфере, и OutputBufSize это размер выходного файла. Size увеличивается от нуля до OutputBufSize, что означает, что обработчик события FOnProgress вызывается с аргументом от 0 до 100.

1.1.11. Регистрация компонента

При регистрации компонента 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 для всех пользователей!

1.1.12. UUCode Example Wizard

Для показа прогресса 16-битный пример использует TGauge компонент, в то же время 32-битная версия использует Windows 95 Progress Control.

рис. 1.1. 16-битная версия примера UUCode

рис. 1.2. 32-битная версия примера UUCode

Во время исполнения программы могут возникнуть два исключения. Если входной файл пуст и во время кодирования, если выходной файл пуст. Для 16 битной версии может возникнуть третье исключение, если входной или выходной файл больше 65000 байт (16-битная версия данного компонента может обрабатывать входные и выходные файлы до 64 килобайт). На практике это означает, не может быть более 48 килобайт. 32-битная версия не имеет такого ограничения).

1.1.13. Заключение

В этой главе мы рассмотрели uuencode/uudecode, xxencode/xxdecode, и Base64 алгоритмы кодирования/декодирования. Мы также разработали простой VCL компонент, который поддерживает эти алгоритмы в дополнение к простому копированию. Свойства, методы и события делают данный компонент пригодным для построения Интернет приложений нуждающихся в подобном преобразовании.

Компонент TBUUCode сейчас часть пакета "DrBob42 component package for Delphi and C++Builder".

1.2. HTML

Аббревиатура 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".

1.3. CGI

Аббревиатура CGI означает Common Gateway Interface, и является связевым протоколом между формой в Web браузере (клиент) и приложением запущенным на Web сервере (сервер). Приложение обычно называется CGI скрипт, но мы можем использовать Дельфи для написания CGI приложений без скриптов.

Имеется два типа CGI: стандартное или консольное CGI приложение и позже появилась версия для Windows называемая WinCGI.

1.3.1. Консольное CGI приложение

Стандартное или консольное CGI приложение взаимодействует с формой на клиенте с помощью переменных среды (управляющая информация), стандартным входом (данные формы) и стандартным выводом (возвращаемая динамическая HTML страница).

1.3.2. WinCGI

Приложение WinCGI взаимодействует с формой на клиенте с помощью Windows.INI файла вместо переменных среды. Windows.INI файл содержит управляющую информацию, иногда данные формы и имена входного, данных и выходного файлов.

1.3.3. Delphi и CGI

В данной главе я расскажу, как написать простое Дельфи 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 страниц (например для выдачи данных их таблицы).

1.3.4. Динамический вывод

Для начала посмотрим на стандартное "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!" на странице.

1.3.5. CGI ввод

Теперь, мы знаем как создавать 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 связи не более сложный, чем представленный здесь.

1.3.6. Input Queries

Сейчас мы попробуем прочитать запрос в стандартном 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: