Delphi Clinic C++Builder Gate Delphi Notes Weblog Delphi for .NET Prism for .NET
Dr.Bob's Delphi Notes Dr.Bob's Delphi Clinics
 Delphi Internet Solutions: CGI
See Also: Dr.Bob's Delphi Internet Solutions on-line book

Internet file formats can be divided into a few groups. First, we have the file transfer (or communication) file formats, for which a long time ago the uuencode/decode schema was invented, followed by xxencode/decode. This later evolved into the base64 encoding and MIME messaging scheme that a lot of mailers use today. A second type of internet file formats is the Hyper Text Markup Language (HTML), with all its versions and (often browser specific) enhancements a true group in itself. The third group of internet file formats is more an interface or protocol of communication again; the Common Gateway Interface (CGI), of which we can identify standard (or console) CGI and Windows CGI or WinCGI.

CGI
CGI stands for Common Gateway Interface, and is the communication protocol between a "Form" on a Web Browser (the client) and an application running on the Web Server (the server). The application is usually called a CGI Script, although we can use Delphi to write CGI applications that are of course no scripts.
There are two kinds of CGI: standard or console CGI and the later Windows version called WinCGI.

console CGI
A standard or console CGI application communicates with the "Form" on the client side by using environment variables (control values), the standard input (the Form data) and standard output (the resulting dynamic HTML page).

WinCGI
A WinCGI application communicates with the "Form" on the client side by using a Windows .INI file instead of environment variables. The Windows .INI file contains the control values, sometimes even the entire Form Data, and the filenames of the input, data and output file that needs to be generated.


Delphi and CGI
In this dynamic and growing on-line chapter I'll explain how to write some simple Delphi CGI applications, without having to use Web Modules or other Client/Server stuff at all.
First of all, CGI stands for Common Gateway Interface, and is just the name for the protocol to send information from the client (i.e. the web browser) to the server (i.e. the web server application). On the client side, this is implemented by a CGI Form, which consists of nothing more but HTML tags. One the server this consists of a CGI application, which is sometimes also called CGI script (for example on Unix machines, where Perl is used to implement CGI scripts).
In this chapter, I'll focus on writing CGI application for Windows NT web server, and we'll be using 32-bits Delphi (i.e. Delphi 2.x or 3.x) for this task, although the code will also compile without problems with C++Builder, of course.
A standard CGI application gets its input (like a search query) from the standard input, and must write its output (for example a HTML page) on the standard output. This means we'll need to write a Delphi CONSOLE application. If there's no input, we can also refer to CGI application as simply generating dynamic HTML pages (for example extracted from a table).

Dynamic Output
So, let's take a look at a "hello world" CGI application first. This one would do nothing except print the string "hello, world" in a HTML page. Before we can do that, however, there's one more thing: the CGI application must tell the world what (MIME) format the output actually is. And in this case it's "text/html", which we must write as follows: content-type: text/html, followed by an empty line.
Which all results in our first "Hello, world!" CGI application that could look as follows:

  program CGI1;
  {$APPTYPE CONSOLE}
  begin
    writeln('content-type: text/html');
    writeln;
    writeln('<html>');
    writeln('<body>');
    writeln('Hello, world!');
    writeln('</body>');
    writeln('</html>')
  end.
If you compile this program with Delphi 2 or 3 and run it from either IntraBob or a web browser connected to a web server where it's stored in an executable directory such as cgi-bin, then you'll get the "Hello, world!" output in a HTML page.
Note, by the way, that IntraBob will also show the "content-type" header, just to tell you that it's been generated (after all, it's a CGI tester/debugger).

CGI Input
Now, we've seen how to create a CGI application that can generate a dynamic (or actually quite static) HTML page. But what about input? There's one more issue here: we need to check the DOS environment variable 'CONTENT LENGTH' to see how many characters from standard input we actually have to read (if we try to read one more, we'll just hang forever). Of course, this is a simplification of the entire complete facts, but it'll do for our second working sample CGI application...
I've written a TBDosEnvironment component that you can use to access DOS environment variables:

  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 that
        appears 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: TStringList);
    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.

Here's a list of the Environment Variables (provided by Deepak Shenoy) that are available to a CGI program. Even ISAPI programs can use these variables:

Environment Variable
Purpose/Meaning/Value
GATEWAY_INTERFACE CGI version that the web server complies with.
SERVER_NAMEServer's IP address or host name.
SERVER_PORTThe Port on the server that received the HTTP request.
SERVER_PROTOCOLName and version of the protocol being used by the server to process requests.
SERVER_SOFTWAREName (and, normally, version) of the server software being run.
AUTH_TYPEAuthentication scheme used by the server (NULL , BASIC etc)
CONTENT_FILEFile used to pass data to a CGI program (Windows HTTPd/WinCGI only).
CONTENT_LENGTHNumber of bytes passed to Standard Input (STDIN) as content from a POST request.
CONTENT_TYPEType of data being sent to the server.
OUTPUT_FILEFilename to be used as the location for expected output (Windows HTTPd/WinCGIonly).
PATH_INFOAdditional relative path information passed to the server after the script name, but before any query data.
PATH_TRANSLATEDSame information as PATH_INFO, but with virtual paths translated into absolute directory information.
QUERY_STRINGData passed as part of the URL, comprised of anything after the ? in the URL.
REMOTE_ADDREnd user's IP address or server name.
REMOTE_USERUser name, if authorization was used.
REQUEST_LINEThe full HTTP request line provided to the server (availability varies by server).
REQUEST_METHODSpecifies whether data for the HTTP request was sent as part of the URL (GET) or directly to STDIN (POST).
SCRIPT_NAMEName of the CGI script being run.

There are some more, but they're not as important as these. A few Environment Variables that are especially important to our CGI application, and an outline for the processing of a standard CGI application is as follows:

REQUEST_METHOD - determine whether we get data by POST or by GET
QUERY_STRING - if we used GET
CONTENT_LENGTH - if we used POST, and now read "CONTENT_LENGTH" characters from the standard input (which ends up with the "Query", like QUERY_STRING does when using the GET protocol).

In all cases, the standard CGI application must write (HTML) output to the standard output file, so we'll use a CONSOLE type application.

Now, with the TBDosEnvironment component you can create a dynamic instance, obtain the above three Environment Variables, and get the input you need. After that, it's up to you to write the code to generate the output you want for that particular input.

Easy, right? For an example of yet another very small (39Kb) standard CGI application, check out the mini website Search Engine on my website. The (short) source code will be presented in an article for The Delphi Magazine, but I can tell you that the basic CGI communication protocol is no more complex than what I've outlined so far...

Input Queries
Today, we'll deal with "reading queries values" in a standard CGI application written in 32-bits Delphi (i.e. Delphi 2.x or 3.x).
Basically, it's a two-step process. The first step involves HTML and the special CGI Form-tags, the second step involves retrieving the data from within the CGI application on the web server.
A HTML CGI Form is defined within <form>...</form> tags. The opening tag also contains the method to send data (GET or POST) and the action, which is the URL of the CGI application that is to be executed on the web server. For example:

  <form method="POST" action="http://www.ebob42.com/cgi-bin/debug.exe">
  ...
  </form>
This denoted a HTML CGI Form that will POST the data to my web server, and then execute the program debug.exe (from cgi-bin directory). For now, we don't have to concern ourselves with differences between POST and GET (I always use POST). We do note, however, that there's nothing to POST, so far. We need to specify so-called input fields within the CGI Form. For this, we can pick a number of very basic standard Windows controls, all predefined, like an editbox, a memo field, a listbox, a drop-down combobox, radiobuttons, checkboxes and finally the "action" buttons (reset or submit).
A simple editbox is an input field of type "text", which also needs a name, optionally a size (and a width in pixels), and can get a value:
  <input type="text" name="login" size="8">
This would result in an editbox where we can type up to 8 characters, which will be send as "login=xxxxxxxx" to our CGI app, where xxxxxxxx denoted the data we filled in on this CGI Form, like this:
The standard CGI application, on its turn, needs to check the DOS Environment variable REQUEST-METHOD to see if it is GET or POST. In case of a POST, we need to check the CONTENT-LENGTH to determine the number of characters that we need to read (from standard input). This standard input is containing the data (such as "login-xxxxxxxx") for our CGI application.
Now, instead of writing some difficult start-up code for each CGI application, I've written a special DrBobCGI unit that will perform all the necessary start-up code of retrieving the CGI input data, and make them available through calls to a single function called "Value". So, in the example above, we would only need to call "Value('login')" to get the string 'xxxxxxxx' as result.
The unit responsible for this is listed below. Supporting CGI and POST as well as GET (so try to figger out what GET does by looking at this code ;-)
  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.
FWIW, I've written over a dozen CGI applications over the past year, and all are now using this DrBobCGI unit, resulting in easy maintenance and tiny and fast executables.
In this article I'll present the cross-platform edition of DrBobCGI (to produce web server applications with Delphi and Kylix without the need for the WebBroker Technology). As one of the examples, I'll show how to (set and) get cookie values using DrBobCGI.

Now, for a real world example: a standard CGI guestbook application (which asks for your name and a ne-lone comment) written in only a few lines of Delphi.
First the CGI Form itself:

  <html>
  <body>
  <H2>Dr.Bob's Guestbook</H2>
  <form method="POST" action="http://www.ebob42.com/cgi-bin/guest.exe">
  Name: <input type=text name="name"><br>
  Comments: <textarea cols="42" lines="4" name="comments"></textarea>
  <p>
  <input type="submit" value="Send Comments to Dr.Bob">
  </form>
  </body>
  </html>
Now the Delphi CONSOLE application:
  program CGI;
  {$I-}
  {$APPTYPE CONSOLE}
  uses
    DrBobCGI;
  var
    guest: Text;
    Str: String;
  begin
    Assign(guest,'book.htm'); // assuming that's the guestbook
    if FileExists('book.htm') then Append(guest)
    else
    begin
      Rewrite(guest); // create it for the first time
      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>')
  end.
Which results in a display like the following:

Dr.Bob's Guestbook
Name:
Comments:  


Question:
I have two submit buttons on a HTML page, which implement a go back one page and go forward one page. I need to determine which button was pressed inside my CGI so that I can perform the appropriate operation.

Dr.Bob Says:
You can just assign a unique value to each "type=submit" button, as you can see by the form below:

  <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.ebob42.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>
You should get the "Action=SAVE" (or Action=DELETE) after you press on that button.


This webpage © 1997-2010 by Bob Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.