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

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

  • 1.1.1. UUEncode и UUDecode

  • 1.1.2. XXEncode и XXDecode
  • 1.1.3. Base64

  • 1.1.4. MIME

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

  • 1.1.6. Свойства

  • 1.1.7. Методы

  • 1.1.8. События

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

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

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

  • 1.1.12. UUCode Example Wizard
  • 1.1.13. Заключение
  • 1.2. HTML
  • 1.3. CGI

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

  • 1.3.2. WinCGI

  • 1.3.3. Delphi и CGI

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

  • 1.3.5. CGI ввод

  • 1.3.6. Input Queries

  • Введение

    Следующие главы были сделаны доступными в 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>…</HTML> Ограничивает HTML страницу
    <HEADER>…</HEADER> Секция заголовков
    <TITLE>…</TITLE> Заголовок документа
    <BODY>…</BODY> Секция содержимого страницы
    <H1>…</H1> Заголовок (возможные уровни 1..6)
    <B>…</B> Жирный текст
    <I>…</I> Наклонный текст
    <BR> Разрыв строки
    <HR> Горизонтальная черта
    <P> Абзац
    <A HREF="URL">….</A> Ссылка на другую страницу или URL

    HTML страница всегда начинается тегом <HTML> и заканчивается тегом </HTML>.

    Содержимое страницы размещается между тегами <BODY> и </BODY>. Множественные разрывы строк и пробелы/табуляции игнорируются и заменяются на один пробел, это причина, по которой требуется специальный тег разрыва строки <BR> и <P>. Простая HTML страница с одним заголовком и ссылкой выглядит так.

    <HTML>

    <BODY>

    <H1>Hello, world!</H1>

    <P>

    <HR

    <ADDRESS>

    <A HREF=http://www.drbob42.com>Dr.Bob's Delphi Clinic</A>

    </ADDRESS>

    </BODY>

    </HTML>

    Примечание: тег <ADDRESS>, который мы можем использовать для помещения адресной информации и ссылки на домашнюю страницу или e-mail адрес. Эта информация будет отображена наклонным шрифтом. Тег <A> часть основы 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('<HTML');

       writeln('<BODY');

       writeln('Hello, world!');

       writeln('</BODY');

       writeln('</HTML')

     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 форма определяется с помощью тегов <FORM>…</FORM>. Открывающий тег также содержит имя метода (GET or POST) и действие, которое является URLом CGI приложения на web сервере. Например:

    <FORM ACTION=http://www.drbob42.com/cgi-bin/debug.exe METHOD=POST>

    </FORM

    Данная 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", которое обязано иметь имя и необязательно размер и ширину в пикселях, и может иметь значение:

    <INPUT TYPE=text NAME=login SIZE=8>

    Результатом этой фразы будет нарисован 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 форма:

    <HTML>

    <BODY>

    <H2>Dr.Bob's Guestbook</H2>

    <FORM ACTION=http://www.drbob42.com/cgi-bin/guest.exe

      METHOD=POST>

      Name: <INPUT TYPE=text NAME=name><BR>

      Comments: <TEXTAREA COLS=42 LINES=4 NAME=comments>

      <P>

      <INPUT TYPE=SUBMIT VALUE="Send Comments to Dr.Bob">

      </FORM>

      </BODY>

      </HTML>

    Теперь консольное приложение:

     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,'<HTML>');

         writeln(guest,'<BODY>')

       end;

       writeln(guest,'Date: ',DateTimeToStr(Now),'<BR>');

       writeln(guest,'Name: ',Value('name'),'<BR>');

       writeln(guest,'Comments: ',Value('comments'),'<HR>');

       reset(guest);

       while not eof(guest) do // now output guestbook itself

       begin

         readln(guest,Str);

         writeln(Str)

       end;

       close(guest);

       writeln('</BODY>');

       writeln('</HTML>')


    Вопрос:

    У меня на форме две "submit" клавиши, одна на переход на предыдущую страницу, другая переход на следующую страницу. Как определить какая из них была нажата, чтобы я мог выполнить соответствующее действие.

    Доктор Боб отвечает:

    Вы должны назначить уникальное значение для каждой кнопки "type=submit", ниже приведен соответствующий код:


    <HTML>

    <BODY>

    Edit the information and press the SAVE button<BR>

    To Delete information, press the DELETE button<BR>

    <P>

    <FORM METHOD=POST ACTION=http://www.drbob42.com/cgi-bin/debug.exe>

    <HR>

    <input type=text name=name>

    <P>

    <input type=reset  value="RESET">

    <input type=submit name=action value="SAVE">

    <input type=submit name=action value="DELETE">

    </FORM>

    </BODY>

    </HTML>

    Вы должны получить "Action=SAVE" или "Action=DELETE" после нажатия одной из этих кнопок.








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх