menu
  Home  ==>  papers  ==>  web  ==>  simple_cgi_web_server   

Simple CGI Web Server - Felix John COLIBRI.

  • abstract : a Web Server which handles CGI extensions, and can be used to test the CGI scripts
  • key words : Web Server, CGI script, server extension, HTTP, StdIn, StdOut, Windows pipes, Windows Environment, UML Class diagram
  • software used : Windows XP, Delphi 6
  • hardware used : Pentium 1.400Mhz, 256 M memory, 140 G hard disc
  • scope : Delphi 1 to 8 for Windows
  • level : Delphi developer
  • plan :


1 - Introduction

We will present here a simple CGI Web Server. The server will operate in graphic mode (not CONSOLE) which allows easier debugging of CGI handling. Is is based on the socket classes presented in the simple_web_server paper.




2 - CGI

2.1 - Principle

CGI (Common Gateway Interface) is a basic HTTP mechanism allowing the web user to send parametrized queries to the web server:
  • we build a special HTML page with checkboxes, radiobuttons, edits, memos, listboxes, and a "submit" button
  • the user loads this page from the Server, fills the controls, and when he clicks "submit" a string containing the control values is sent back to the Server
  • the Server fetches the parameters, and sends this to a CGI executable which was tailored to analyze and build an HTML answer corresponding to the parameters. This answer is forwarded to the Server which sends it back to the user
The server is unaware of the details of the CGI request. It simply transfers it to the CGI executable. Each HTML Form has an associated dedicated CGI executable. So basically CGI is a way to append custom warts to an existing monolitic HTTP Web server.



2.2 - simple example

Let's take this simple information enquiry for Delphi trainings:

HTML Form



This page was built with the following .HTML text:

<HTML>
  <HEAD>
    <TITLE>Felix COLIBRI - Training Information</TITLE>
  </HEAD>
  <BODY>
    <H2><CENTER>Felix COLIBRI - Training Information</CENTER></H2>
 
    <FORM method="POST"
        ACTION="http://127.0.0.1/scripts/training_information.exe"><BR>
      Select the training, enter your e-mail and click the "Send" button:<BR>
 
      <INPUT NAME=database TYPE=checkbox> Delphi Client Server
          Database Programming<BR>
      <INPUT NAME=oo TYPE=checkbox> Delphi Object Oriented Programming<BR>
      <INPUT NAME=uml_dp TYPE=checkbox> Delphi Object Oriented
          Analysis and Design with UML<BR>
      your e-mail : <INPUT TYPE=text NAME=e_mail SIZE=30><BR>
 
      <INPUT TYPE="submit" VALUE="Send">
    </FORM><BR>
 
  </BODY>
</HTML>

This page contains:

  • the usual <HTML> <HEAD> and <BODY> tags:

    <HTML>
      <HEAD>
        <TITLE>Felix COLIBRI - Training Information</TITLE>
      </HEAD>
      <BODY>
        <H2><CENTER>Felix COLIBRI - Training Information</CENTER></H2>
        <!-- here the CGI Form -->
      </BODY>
    </HTML>

  • the CGI Form is specified with the <FORM> tag:

    <FORM method="POST"
            ACTION="http://127.0.0.1/scripts/training_information.exe"><BR>
          Select the training, enter your e-mail and click the "Send" button:<BR>
        </FORM><BR>

  • and within the <FORM> tag we can place checkboxes:

     
    <INPUT NAME=oo TYPE=checkbox> Delphi Object Oriented Programming<BR>
     

    edits:

     
    your e-mail : <INPUT TYPE=text NAME=e_mail SIZE=30>
     

    and a button:

     
    <INPUT TYPE="submit" VALUE="Send">
     



So:
   we build the HTML page containing the <FORM> and place it on our Web Server:

cgi server ready

   later a Client requests the page:

cgi client request

   the Server fetches the page from the disk and sends it over to the Client:

cgi
server sends form

   the Clients fills the <FORM>, clicks "submit", which sends the CGI parameters to the Server:

cgi client sends parameters

   upon receipt of the parameters, the Server
  • locates the correct cgi.exe
  • starts this .EXE as a child process, handing over the other <FORM> parameters
  • the cgi.exe analyzes the parameters and processes the request
cgi server process parameters

Very often, the purpose of the CGI is to send some HTML page back to the Client, even if this page only contains some kind of acknowlegement. This looks like this:

cgi server sends acknowlege



2.3 - The HTTP exchange

To give you an idea of the exchanged information, we used our TCP/IP Sniffer to monitor the communication. Here is the textual dump of the HTTP packets between the Client and the Server:
   the Client requests the page:

 
c -> s |GET /training_information.html HTTP/1.1
c -> s |Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
c -> s |Accept-Language: en
c -> s |Accept-Encoding: gzip, deflate
c -> s |User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
c -> s |Host: www.felix-colibri.com
c -> s |Connection: Keep-Alive
c -> s |

   the Server sends the page back:

 
c <- s |HTTP/1.1 200 OK
c <- s |Server: Microsoft-IIS/5.0
c <- s |X-Powered-By: ASP.NET
c <- s |Cache-Control: max-age=60
c <- s |Expires: Mon, 16 May 2005 06:54:32 GMT
c <- s |Date: Mon, 16 May 2005 06:53:32 GMT
c <- s |Content-Type: text/html
c <- s |Accept-Ranges: bytes
c <- s |Last-Modified: Mon, 16 May 2005 06:51:31 GMT
c <- s |ETag: "433fab0e359c51:1709"
c <- s |Content-Length: 725
c <- s |
c <- s |<HTML>
c <- s | <HEAD>
c <- s | </HEAD>
c <- s | <BODY>
c <- s | <P>
c <- s | <H2><CENTER>Felix COLIBRI - Training Information</CENTER></H2>
c <- s | <FORM method="POST"
c <- s | ACTION="http://www.felix-colibri.com/scripts/training_information.exe"><BR>
c <- s | Select the training, enter your e-mail and click the "Send" button:<BR>
c <- s | <P>
c <- s | <INPUT NAME=database TYPE=checkbox> Delphi Client Server Database Programming<BR>
c <- s | <INPUT NAME=oo TYPE=checkbox> Delphi Object Oriented Programming<BR>
c <- s | <INPUT NAME=uml_dp TYPE=checkbox> Delphi Object Oriented Analysis and Design
c <- s | with UML<BR>
c <- s | your e-mail : <INPUT TYPE=text NAME=e_mail SIZE=30><BR>
c <- s | <P>
c <- s | <INPUT TYPE="submit" VALUE="Send">
c <- s | <P>
c <- s | </FORM><BR>
c <- s | </BODY>
c <- s |</HTML>

   the Client fills the <FORM> and sends it over to the Server:

 
c -> s |POST /scripts/training_information.exe HTTP/1.1
c -> s |Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */*
c -> s |Referer: http://www.felix-colibri.com/training_information.html
c -> s |Accept-Language: en
c -> s |Content-Type: application/x-www-form-urlencoded
c -> s |Accept-Encoding: gzip, deflate
c -> s |User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
c -> s |Host: www.felix-colibri.com
c -> s |Content-Length: 27
c -> s |Connection: Keep-Alive
c -> s |Cache-Control: no-cache
c -> s |
c -> s |oo=on&e_mail=me@my_site.com

   the Server extracts the parameters, starts training_information.exe. This executable takes the parameters apart, and builds an answer which is fed back to the Server. The Server frames this as an HTTP packet which is sent back to the Client:

 
c <- s |HTTP/1.1 200 OK
c <- s |Server: Microsoft-IIS/5.0
c <- s |Date: Mon, 16 May 2005 06:53:51 GMT
c <- s |X-Powered-By: ASP.NET
c <- s |Connection: close
c <- s |Content-type: text/html
c <- s |Content-Length=182
c <- s |
c <- s |<HTML>
c <- s | <HEAD>
c <- s | </HEAD>
c <- s | <BODY>
c <- s | Thank your for contacting us about our
c <- s | object oriented trainings.<BR>
c <- s | The information will be send at me@my_site.com
c <- s | </BODY>
c <- s |</HTML>



2.4 - The CGI executable's job

The CGI executable will receive the CGI parameters, process them and eventually send an answer back to the CGI Server.

The CGI parameters are formatted as an ampersand delimited key=value list, like:

 
oo=on&e_mail=me@my_site.com

This string is sent by the Server to the Standard Input file, and the CGI executable reads them using the usual Pascal Readln procedure.

The parsing of the parameter string is very simple.

After parameter extraction, the CGI executable performs whatever tasks the programmer of this executable choses. In our case, the CGI executable builds some HTML page which acknowleges the receipt of the request

This answer is sent back to the Server using the standard output file, and this is done using the usual Pascal Writeln instruction.



2.5 - The Server's job

A CGI Web Server is any standard HTTP Server with the following additional capabilities:
  • it must detect the reception of the CGI request and the parameters
  • the CGI executable must be located and loaded in memory
  • the CGI parameters must be transfered to the CGI executable
  • the Server then waits until the CGI executable sends the answer
  • the answer is sent back to the Client.


A CGI request is detected, when a Client sends a "POST" request. This "POST" also contains the name of the CGI executable:

 
POST /scripts/training_information.exe HTTP/1.1

The Server extracts the executable name, and transforms this into a Windows path.

The CGI parameters are extracted from the body of the request.



Using the executable name, the Server loads and starts the CGI executable using the CreateProcess Windows API call. It communicates the Client's parameters using the Windows environment and the new process handle:

  • some parameters like the Client's IP, the verb (POST) are communicated using the Environment block
  • the CGI parameter string itself is used by writing the string into the new process's Input file handle. In a similar manner, the result of the CGI is used by reading the new process's Output file handle


And the answer is sent back to the Client using a standard Send socket call.




3 - The Delphi Cgi Server code

3.1 - The CGI Executable

As explained above, the CGI executable reads the parameters. The size of this string is in the Windows Environment block.

So the first steps for a CGI executable are

  • fetch from the environment strings the parameters relevant to the CGI handling
  • read the parameter string


The environment strings are fetched using Windows API calls, like GetEnvironmentVariable:

FUNCTION GetEnvironmentVariable(Namestring): string

Among the keys of interest are:

  • REQUEST_METHOD
  • REMOTE_ADDRESS
  • QUERY_STRING
  • CONTENT_LENGTH
Using the length of the parameters, we can use a loop to read the parameters:

if (UpperCase(m_request_method)= 'POST'and (m_content_length> 0)
  then begin
      SetLength(m_raw_answer_stringm_content_length);

      // -- read from Input (alias StdIn)
      for l_index:= 1 to m_content_length do
        Read(m_raw_answer_string[l_index]);  
    end;



The CGI parameter string is the parsed, extracting the value=key couples which are separated by "&" characters.



An HTML answer page is then built, based on the parameters.

Finally this answer is sent back to the Server using Writeln.



3.2 - CGI handling units

In order to ease the handling of the CGI, we are using different helper units:
  • a first group of units is a copy of our GUI libraries, but without any visual component usage. The CGI executable works in CONSOLE mode, so we had to remove all tMemo (for display) references. Among those units is the c_simple_log used to place debug information in a file which can be retreived from the Server and analyzed
  • the second group is centered around CGI:
    • Windows environment block handling. The simple Windows Environment paper presents this unit
    • u_c_cgi_base_forms reads the environment and the parameter string. Here is the definition:

       c_base_formclass(c_basic_object)
                      m_nameString;

                      m_content_lengthInteger;

                      m_request_methodString;
                      m_remote_address_stringString;
                      m_raw_answer_stringString;

                      m_query_stringString;

                      Constructor create_base_form(p_nameString);
                      procedure get_form_result;
                    end// c_base_form




All CGI handling is then placed in a single unit, with a single call in the INTERFACE:

unit u_cgi_training_handler;
  Interface

    procedure do_handle_cgi(p_write_segment,
        p_log_namep_site_urlString);

    const // -- default to get string to system
          k_output'';
    var // -- will be filled by the caller in debug mode
        g_output_nameStringk_output;
        g_debug_requestString'';

  Implementation

    procedure do_handle_cgi(p_write_segment,
        p_log_namep_site_urlString);

      // -- here : analyze_form_values, evaluate_request and send_answer

      var l_c_key_value_listtStringList;
          l_site_urlString;
          l_c_cgi_html_training_page_builderc_cgi_html_training_page_builder;

      begin // do_handle_cgi
        open_log(f_cgi_exe_pathp_write_segmentp_log_name);

        // -- get the request without % etc
        write_log('== analyze_form_values');
        l_c_key_value_list:= tStringList.Create;

        analyze_form_values(l_c_key_value_list);

        write_log('== load_cgi_text');
        l_c_cgi_html_training_page_builder:= c_cgi_html_training_page_builder
            .create_cgi_html_training_page_builder('request'l_site_url);

        // -- build the target file list
        evaluate_request(l_c_key_value_listl_c_cgi_html_training_page_builder);

        write_log('== send_answer');
        send_answer(g_output_namel_c_cgi_html_training_page_builder);

        l_c_key_value_list.Free;
        l_c_cgi_html_training_page_builder.Free;

        write_log('== end ok');
      end// do_handle_cgi

    end



During debugging, we use a GUI .DPR which calls do_handle_cgi, testing all the possible CGI parameter values:

debugging the CGI executable

Here is the call used to start the CGI processing:

const k_datas_segment'log\';
      k_training_write_segment'training_info\';
      k_training_log_name'log_training_information.txt';

procedure build_debug_request;
  begin
    with Form1 do
    begin
      // -- |oo=on&e_mail=aha|
      g_debug_request:= '';

      if database.Checked
        then g_debug_request:= g_debug_request'database=on&';
      if oo.Checked
        then g_debug_request:= g_debug_request'oo=on&';
      if uml_dp.Checked
        then g_debug_request:= g_debug_request'uml_dp=on&';
      if e_mail.Text<> ''
        then g_debug_request:= g_debug_request'e_mail='e_mail.Text;
    end// with Form1
  end// build_debug_request

procedure TForm1.send_Click(SenderTObject);
  begin
    g_output_name:= 'result.txt';
    build_debug_request;

    do_handle_cgi(k_datas_segmentk_training_write_segment,
        k_training_log_name'');
  end// send_Click



And for the true CGI executable, we create an application console which uses the SAME handling call:

(*$r+*)
(*$APPTYPE CONSOLE *)

program training_information;
  uses u_cgi_training_handler;

    const k_datas_segment'log\';
          k_training_write_segment'training_info\';
          k_training_log_name'log_training_information.txt';
          k_form_training_root_log_name'log_training_information_';
          k_site_url'http://www.felix-colibri.com/';

    begin
      do_handle_cgi(k_datas_segmentk_training_write_segment,
          k_training_log_namek_site_url);
    end.

The resulting .EXE is then placed on the Server's disk.



3.3 - The CGI Server

The CGI Server loads and executes the CGI Executable using the Windows CreateProcess function:

function CreateProcess(lpApplicationNamePChar
  lpCommandLinePChar;
  lpProcessAttributeslpThreadAttributesPSecurityAttributes;
  bInheritHandlesBOOL
  dwCreationFlagsDWORD
  lpEnvironmentPointer;
  lpCurrentDirectoryPChar
  const lpStartupInfoTStartupInfo;
  var lpProcessInformationTProcessInformation): BOOLstdcall;

where:

  • lpCommandLine is the console exe name and any command line parameters
  • bInheritHandles forces the new process to inherit the handles that can be specified in the lpStartupInfo parameter
  • lpStartupInfo is a record containing initial parameters for the new process, including the hStdIn, hStdOut and hStdErr handles (if bInheritHandles is True)
  • lpProcessInformation is the record filled by the CreateProcess call and containing the new hProcess handle. This handle will be used to check the process status, and eventually to kill it


So basically the Server launches a child process, using the CreateProcess parameters to communicate with this child process:
  • the StdIn and StdOut handles are in lpStartupInfo
  • lpEnvironment is the brand new environment used by the child process
The environment block is simply a null terminated buffer containing key=value#0 couples. We have built this block in the u_windows_environment unit.

For the StdIn and StdOut files:

  • when the CGI Server is itself a CONSOLE application, the handles are simply inherited by the CGI Executable from the Server handles
  • when the CGI Server is a GUI application, StdIn and StdOut (Readln and Writeln) are invalid. We must use Windows anonymous pipes. This has been explained in great detail in the StdIn_StdOut paper.


The CGI Server uses a Server Socket. As usual, a Server Socket spins off individual Server Client Sockets for each incoming Client request, and this Server Client Socket does the grunt work of CGI handling. In our case the Server Client Socket was derived from a c_http_server_client_socket which already handles the non-CGI requests (requests without <FORM>). The c_cgi_http_server_client_socket is defined by:

 c_cgi_http_server_client_socket=
     class(c_http_server_client_socket)
       m_cgi_exe_pathString;
       m_exe_nameString;
       m_post_contentString;

       m_on_cgi_http_server_client_socket_closed:
           t_po_cgi_http_server_client_socket_event;

       Constructor create_server_client_socket(p_nameString;
           p_c_server_socket_refc_server_socket); Override;

       procedure handle_received_dataOverride;
       procedure send_and_receive_cgi;
     end// c_cgi_http_server_client_socket

The packet received from the Client is analyzed here:

procedure c_cgi_http_server_client_socket.handle_received_data;
  var l_end_of_header_positionInteger;
      l_read_indexInteger;
  begin
    // -- get the data and handle GET if any
    Inherited handle_received_data;

    if m_page_name''
      then
        with m_c_reception_buffer do
        begin
          l_read_index:= f_string_position(0, 'post');
          if l_read_index< 0
            then l_read_index:= f_string_position(0, 'POST');

          if m_read_index< 0
            then m_http_answer_error:= e_no_GET
            else begin
                // -- |HREF="http://www.felix-colibri.com/scripts/coliget.exe?a=1?"interbase">
                // -- => receives
                // --   |POST /scripts/coliget.exe HTTP/1.1 RET
                // --   | other header stuff
                // --   |interbase

                // -- skip POST
                m_read_index:= l_read_index+ 4;
                // -- skip blanks
                skip_blanks(m_read_index);
                // -- extract the cgi name
                m_exe_name:= f_extract_non_blank(m_read_index);

                l_end_of_header_position:= f_next_2_new_line_position(m_read_index);
                if l_end_of_header_position> 0
                  then begin
                      m_post_content:= 
                         f_extract_string_start_end(l_end_of_header_position+ 4, 
                           m_write_index- 1);

                      send_and_receive_cgi;

                      // -- cleanup buffer
                      m_c_reception_buffer.clear_buffer;

                      do_close_win_socket;
                      if Assigned(m_on_cgi_http_server_client_socket_closed)
                        then m_on_cgi_http_server_client_socket_closed(Self);
                    end;
              end// handle POST
        end// with p_c_byte_buffer
  end// handle_server_client_socket_received_data

and the CGI Executable is launched with this procedure:

procedure c_cgi_http_server_client_socket.send_and_receive_cgi;
  var l_cgi_pathl_cgi_exe_file_nameString;

  function f_found_cgi_exeboolean;
    begin
      // -- here: path manipulations
    end// f_found_cgi_exe

  procedure launch_cgi;

    procedure send_answer_to_client(p_contentString);
      begin
        l_header:= k_answer_http_200_okk_answer_server;
        f_do_send_string(l_headerp_content);
      end// send_answer_to_client

    begin // send_and_receive_cgi
      with c_stdin_stdout_process.create_stdin_stdout_process('runner'do
      begin
        m_exe_name:= l_cgi_pathl_cgi_exe_file_name;

        // -- initialize the process parameters
        m_window_style:= wsNormal;
        m_do_call_process_messages:= True;
        m_c_environment_list:= tStringList.Create;
        with m_c_environment_list do
        begin
          Add('CONTENT_LENGTH='IntToStr(Length(m_post_content)));
          Add('REQUEST_METHOD=POST');
          Add('REMOTE_ADDR=127.0.0.1');
        end;
        if f_create_stdin_stdout_pipes
          then begin
              if f_create_process
                then begin
                    write_string(m_post_content);

                    read_string;
                    // -- debug
                    display(f_display_string(m_received_string));

                    // -- send this back to the client
                    send_answer_to_client(m_received_string);
                  end;
            end;

        Free;
      end// with c_stdin_stdout_process
    end// launch_cgi

  begin // send_and_receive_cgi
    if f_found_cgi_exe
      then launch_cgi
      else send_page_not_found(m_exe_name);
  end// send_and_receive_cgi



The CGI Server itself is encapsulated in a c_cgi_server CLASS:

  c_cgi_web_serverclass(c_basic_object)
                       m_c_server_socketc_server_socket;

                       m_site_pathm_cgi_exe_pathString;
                       m_on_cgi_server_acceptt_po_web_server_event;

                       Constructor create_cgi_web_server(p_name,
                           p_site_pathp_cgi_exe_pathString);

                       procedure start_web_server(p_portInteger);
                       procedure handle_accept(p_c_server_socketc_server_socket;
                           p_c_server_client_socketc_server_client_socket);
                       procedure handle_closed_server_win_socket(
                           p_c_base_socketc_base_socket);

                       procedure close_web_server;

                       Destructor DestroyOverride;
                     end// c_cgi_web_server

and the crucial handling is the Accept notification, which allows to link the creation of the new c_cgi_http_server_socket with the cgi-specific handling methods:

procedure c_cgi_web_server.handle_accept(p_c_server_socketc_server_socket;
    p_c_server_client_socketc_server_client_socket);
  begin
    (p_c_server_client_socket as c_http_server_client_socket).m_site_path:=
        m_site_path;
    (p_c_server_client_socket as c_cgi_http_server_client_socket).m_cgi_exe_path:=
        m_cgi_exe_path;

    p_c_server_client_socket.m_on_after_do_close_socket:=
        handle_closed_server_win_socket;

    if Assigned(m_on_cgi_server_accept)
      then m_on_cgi_server_accept(Self,
          p_c_server_client_socket as c_cgi_http_server_client_socket)
  end// handle_accept



Here is the UML class diagram of our server:

cgi web
server UML diagram



The main tForm or our Server is used

  • to start the Server Socket
  • to display (for debugging) each Client
  • to stop the Server.
Here is a snapshot of the Form (just after a Client request):

cgi web server application



3.4 - The Web Browser

We have created a simple CGI Web Browser which sends the CGI parameter string to the CGI Server and receives the answer. The Browser
  • builds the CGI parameter string using the main tForm controls (checkboxes, edits etc)
  • uses a c_client_socket descendent to sent the HTTP CGI request
  • receives and displays the answer.


Here is a snapshot of the action (after the start of the answer):

cgi web browser application



Please note that:

  • our simple Browser does NOT request the CGI <FORM> to let the user fill it. This would require HTML rendering, which was not included in this paper
  • so basically, if we want to test the complete Browser cycle (send the HTML <FORM> request, send the CGI parameter to the Server, receive and display the answer) we must use a bona fide "rendering browser", like Internet Explorer or Netscape.
  • however those browsers do not display the detail of the packets exchanged: we have to use a TCP IP Sniffer. This is the reason why we used our custom Browser, which is easier to use than a sniffer. But is this enough ? Well, this drives us into the CGI debugging domain.



4 - Improvements



The CGI principle

Our Web pages are stored on the PC of our hosting company. The Web Server itself (IIS in our case) is managed by the hosting company, and we cannot change its behaviour.

This is why Web Server Extensions were invented:

  • the Server is managed
  • the customers of the hosting service only add little warts to generate custom HTML pages based on Client requests
CGI was the first used extension. Other (WinCGI, Isapi, NsApi, Asp) were invented later.

In any case, to add dynamic page building to a Web Site, one has to use one of those extension mechanism. If you choose CGI, the only thing to do is to:

  • write HTML pages with the CGI <FORM>
  • write the matching CGI Executable
So writing a CGI Server and a CGI Browser is not required: IIE and IIS handle those parts for you. The custom Server and Browsers are only of educational and debugging interest.



4.1 - Debugging CGI

From a debugging point of view, CGI is considered "difficult" because:
  • the CGI Executable is a CONSOLE application, which cannot use tForm displays.
  • this executable is launched from an Web Server, which is not always easily available (remote production hosting server, no local debugging Server)
  • the executable must match the HTML <FORM> parameters


To develop the CGI Executable and the matching .HTML <FORM>:
  • we use NotePad to build the page (note really, but this is possible)
  • we build the u_handle_cgi unit (no VCL) which places debugging information into an ASCII Log
  • this unit has a single handle_cgi procedure which is called from a VCL tForm: we can test different CGI parameters, and see how the CGI Executable handles them (loading the log, or the result page). During this test, no TCP/IP packets are involved: we hand over the CGI parameters using a global variable, and load the resulting debug files.


Once we are satisfied with the u_handle_cgi performance, we build a CONSOLE application which calls this same unit and compile it.



Next comes the writing of a CGI Browser, whose main form is similar to the CGI Executable tForm: this Browser can build requests which different control combinations, and uses the TCP/IP protocol to send the CGI parameters to the Server



The CGI Server is started, and can display the packets exchanged with the CGI Browser.



The next step is to substitute a real Browser to our custom browser:

  • we start the CGI Server
  • we start Internet Explorer, fill the address combo-box with the <FORM> page and hit Enter:
       the CGI parameter string is sent to the CGI Server
       the CGI answer is displayed by IE
This steps checks that our CGI Browser correctly coded the CGI parameters



Then

  • we upload our <FORM> .HTML page and the CGI Executable at our hosting server.
  • we start Internet Explorer, and perform a couple of requests
  • we download the cgi log file, to check what our CGI executable received from IIS
And this cgi log is used during the lifetime of our CGI executable to analyze any unexpected exception.



All intermediate steps before uploading the .HTML <FORM> and the CGI Executable are only partial validation steps. Only the use of the real server (IIS) and browsers (IIE, Netscape) will tell whether our Server extensions are without major bugs.



4.2 - CGI today

When Delphi was introduced, CGI was the main Server extension mechanism. Many commercial tools and helpers were offered to ease the creation of CGI executables (like hRef ) with presentations of those tools by Ann LYNWORTH, or CGI Servers to allow local testing.

However the CGI mechanism has been often criticized, mainly because a new .EXE must be loaded to handle each Client. A better way would be to dynamically load a .DLL which could stay in memory and shared among several Clients, and which could be eventually unloaded. This is the IsAPI (NsAPI) route. ASP is yet another possibility for Server side dynamic page generation.

In addition, not many hosting companies allow you to write your own CGI. CGI are .EXE placed on the server. They can perform there whatever the server allows them to do: erase files, allocate memory without freeing it, get stuck in infinite loops etc. So hosting companies prefer scripting server add-ons, like PHP, which are easier to control, especially on shaking OSes like Windows.

Since our hosting service allows CGI Executable, we continue to use them on this site because we find them easier to write than the other Server extension types.




5 - Download the Sources

Here are the source code files:

Those .ZIP files contain:
  • the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
  • any .TXT for parameters
  • all units (.PAS) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • can be used from any folder (the pathes are RELATIVE)
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
  • create or select any folder of your choice
  • unzip the downloaded file
  • using Delphi, compile and execute
To remove the .ZIP simply delete the folder.



As usual:

  • please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will be helpful for other readers
  • we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
  • or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
    Name :
    E-mail :
    Comments * :
     

  • and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.



6 - References

Just a couple of references to other Delphi CGI tools:
  • Bob SWART : offered IntraBob which is a debugging Server for CGI, WinCGI, ISAPI. Since the product is not in source, we never used it
  • RIT Labs offer the Tiny Web Server, which is a full open source code CGI production quality Web Server. The full .EXE is 76 K byte long, despite full threading capabilities. Analyzing the sources, we see that they rewrote everything from scratch (collections as a container class replacing tLists or tStringLists), all necessary SysUtils routines (string utilities, integer conversions etc), and tailored built threading classes. This avoids the huge weight of some libraries (SysUtils, FileCtrl etc), and maybe also the thread safety issues of the early VCL libraries. In any case this beauty compiles in 76 K. The only drawback is the CONSOLE mode, which forbids any GUI extension. You still have ASCII logs, and some people have written GUI extension of the product.
  • Stéphane Grobéty also offers freeware with full source of a Local CGI Server, which uses the Indy socket library for the TCP / IP part



7 - The author

Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP  /  UML, Design Patterns, Unit Testing training sessions.
Created: nov-04. Last updated: jul-15 - 98 articles, 131 .ZIP sources, 1012 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2015. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
      – tcp_ip_sniffer
      – socket_programming
      – socket_architecture
      – simple_web_server
      – simple_cgi_web_server
      – cgi_database_browser
      – whois
      – web_downloader
      – web_spider
      – rss_reader
      – news_message_tree
      – indy_news_reader
      – delphi_web_designer
      – intraweb_architecture
      – ajax_tutorial
      – bayesian_spam_filter
      + asp_net
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog