This document describes the Windows CGI interface. The Windows httpd server package includes a reference implementation in Visual Basic which contains a re-usable module that creates the CGI environment within a VB program.
Try out the Windows CGI Demo.
Windows has no native command interpreter. Therefore, any back-end must be an executable program. A goal to keep the interface simple and minimize back-end programming requirements. Therefore, a file-based interface has been chosen (as opposed to DDE or OLE). Request content is placed into a content file, and results must be written to an output file.
It is expected that many of the back-end applications will be developed using Microsoft Visual Basic (VB). VB supports generation of a .EXE file, and supports a wide range of features for accessing data in the Windows environment, such as OLE, DDE, Sockets, and ODBC. The latter permits accessing data in a variety of databases, relational and non-relational. The VB application can be developed without the need for any windows (forms), consisting purely of VB code modules. This makes it possible to meet the recommendation that the back-end execute invisibly, or at least as an icon.
It is hoped
that this interface will be standardized among Windows based
HTTP servers.
Command Line
The server executes back-end requests by doing a WinExec() with
a command line in the following form:
back-end-exe cgi-data-file content-file output-file url-args
back-end-exe
cgi-data-file
content-file
This file is created even if there was no content supplied with the request (in which case it will be a zero-length file).
output-file
url-args
Launch Method
The server issues the WinExec() such that the
process being launched has its main window minimized
(iconized) and
it does not become the "active" window. This is done by
including the parameter SW_SHOWMINNOACTIVE in the WinExec() call.
The launched process itself should not cause the appearance of a window nor a change in the Z-order of the windows on the desktop.
The server supports a back-end/script debugging mode. If that mode is enabled, the back-end is launched such that its window shows and is made active. This will assist in debugging back-end applications.
The CGI data file contains the following sections:
Request Protocol
Request Method
Executable Path
Logical Path
Physical Path
Query String
Content Type
Content Length
Content File
Server Software
Server NameThe network host name or alias of the server, as needed for self-referencing URLs.
Server PortTne network port number on which the server is listening. This is also needed for self-referencing URLs.
Server Admin
CGI Version
Remote Host
Remote Address
Authentication Method
Authenticated Username
Accept: type/subtype {parameters}If the parameters (e.g., "q=0.100") are present, they are passed as the value of the item. If there are no parameters, the value is "Yes".
Note: The accept types may easily be enumerated by the back-end with a call to GetPrivateProfileString() with NULL for the key name. This returns all of the keys in the section as a null-delimited string with a double-null terminator.
The [System] Section
This section contains items that are specific to the Windows
implementation of CGI. The following keys are used:
Output File
Content File
Note: The extra headers may easily be enumerated by the back-end with a call to GetPrivateProfileString() with NULL for the key name. This returns all of the keys in the section as a null-delimited string with a double-null terminator.
The [Form Literal] Section
If the request is an HTTP POST from a Mosaic form (with content type of
"application/x-www-form-urlencoded"), the server will decode the form
data and put it into the [Form Literal] section.
Raw form input is of the form "key=value&key=value&...", with the value parts in url-encoded format. The server splits the key=value pairs at the '&', then splits the key and value at the '=',url-decodes the value string and puts the result into key=(decoded)value form in the [Form Literal] section.
If the form contains any SELECT MULTIPLE
elements, there
will be multiple occurrences of the same key. In this case, the server
generates a normal "key=value" pair for the first occurrence, and it
appends a sequence number to subsequent occurrences. It is up to the
CGI back-end to know about this possibility and to properly recognize
the tagged keys.
The [Form External] Section
If the decoded value string is more than 254 characters long,
or if the decoded value string contains any control characters,
the server puts the decoded value into an external tempfile and
lists the field into the [Form External] section as:
key=pathname lengthwhere pathname is the path and name of the tempfile containing the decoded value string, and length is the length in bytes of the decoded value string.
Note: Be sure to open this file in binary mode unless you are certain that the form data is text!
The [Form Huge] Section
If the raw value string is more than 65,535 bytes long, the server
does no decoding, but it does get the keyword and mark the location
and size of the value in the Content File.
The server lists the huge field in the [Form Huge]
section as:
key=offset lengthwhere offset is the offset from the beginning of the Content File at which the raw value string for this key is located, and length is the length in bytes of the raw value string. You can use the offset to perform a "Seek" to the start of the raw value string, and use the length to know when you have read the entire raw string into your decoder. Note: Be sure to open this file in binary mode unless you are certain that the form data is text!
Example of Form Decoding
In the following sample, the form contained a small field, a SELECT
MULTIPLE with 2 small selections, a field with 300
characters in it, one with line breaks (a text area), and a 230KB field.
[Form Literal] smallfield=123 Main St. #122 multiple=first selection multiple_1=second selection [Form External] field300chars=C:\TEMP\HS19AF6C.000 300 fieldwithlinebreaks=C:\TEMP\HS19AF6C.001 43 [Form Huge] field230K=C:\TEMP\HS19AF6C.002 276920
The data stream consists of two parts: the header and the body. The header consists of one or more lines of text, and is separated from the body by a blank line. The body contains MIME-conforming data whose content type must be reflected in the header.
The server does not interpret or modify the body in any way. It is essential that the client receive exactly the data that was generated by the back end.
Content-Type
Status
If the back-end response does not contain a Status:
header, the server assumes that the back-end operation succeeded
normally, and generates a "200 OK" status.
Location
The server looks at the results in the Output file, and if the first line starts with "HTTP/1.0", it assumes that the results contain a complete HTTP response, and sends the results to the client without packaging.
--- BEGIN --- Content-type: text/html <== MIME type of body Status: 200 OK <== HTTP status (optional) <== Header-body separator <HTML> <== Body starts here <HEAD> <TITLE>Sample Document</TITLE> </HEAD> <BODY> <H1>Sample Document</H1> [... etc.] </BODY> <HTML> --- END ---
--- BEGIN --- Location: ftp://ftp.netcom.com/pub/www/object.dat <== URL of object <== Blank line --- END ---
--- BEGIN --- HTTP/1.0 200 OK <== Start of HTTP Header Date: Tuesday, 31-May-94 19:04:30 GMT Server: WinHTTPD/1.4 MIME-version: 1.0 Content-type: text/html Last-modified: Sunday, 15-May-94 02:12:32 GMT Content-length: 4109 <== Header-body separator <HTML> <HEAD> <TITLE>A document</TITLE> [... etc.] --- END ---
GetPrivateProfileString()
differs from that normally seen in
VB programs. The difference is compatible, and allows enumeration by passing
NULL for the key name. A '\' at the end of a line indicates continuation.
The code must actually be all on one line.
Declare Function GetPrivateProfileString Lib "Kernel" \ (ByVal lpSection As String, \ ByVal lpKeyName As Any, \ <== This permits NULL key name ByVal lpDefault As String, \ ByVal lpReturnedString As String, \ ByVal nSize As Integer, \ ByVal lpFileName As String) As Integer Type Tuple ' Used for Accept: and "extra" headers Key As String ' and for holding POST form key=value pairs value As String End Type Global CGI_ProfileFile as String ' Pathname of CGI Data File Global CGIAcceptTypes(MAX_ACCTYPE) as Tuple ' Accept: types array Global CGINumAcceptTypes as Integer ' Number of Accept: types in array Const ENUM_BUF_SIZE 8192 ' Size of key enumeration buffer '--------------------------------------------------------------------------- ' ' GetProfile() - Get a value or enumerate keys in CGI_Data file ' ' Get a value given the section and key, or enumerate keys given the ' section name and "" for the key. If enumerating, the list of keys for ' the given section is returned as a null-separated string, with a ' double null at the end. ' ' VB handles this with flair! I couldn't believe my eyes when I tried this. '--------------------------------------------------------------------------- Private Function GetProfile (sSection As String, sKey As String) As String Dim retLen As Integer Dim buf As String * ENUM_BUF_SIZE If sKey <> "" Then retLen = GetPrivateProfileString(sSection, \ sKey, "", buf, ENUM_BUF_SIZE, CGI_ProfileFile) Else retLen = GetPrivateProfileString(sSection, \ 0&, "", buf, ENUM_BUF_SIZE, CGI_ProfileFile) End If If retLen = 0 Then GetProfile = "" Else GetProfile = Left$(buf, retLen) End If End Function '--------------------------------------------------------------------------- ' ' GetAcceptTypes() - Create the array of accept type structs ' ' Enumerate the keys in the [Accept] section of the profile file, ' then get the value for each of the keys. '--------------------------------------------------------------------------- Private Sub GetAcceptTypes () Dim sList As String Dim i As Integer, j As Integer, l As Integer, n As Integer sList = GetProfile("Accept", "") ' Get key list l = Len(sList) ' Length incl. trailing null i = 1 ' Start at 1st character n = 0 ' Index in array Do While ((i < l) And (n < MAX_ACCTYPE))' Safety stop here j = InStr(i, sList, Chr$(0)) ' J -> next null CGI_AcceptTypes(n).Key = Mid$(sList, i, j - i) ' Get Key, then value CGI_AcceptTypes(n).value = GetProfile("Accept", CGI_AcceptTypes(n).Key) i = j + 1 ' Bump pointer n = n + 1 ' Bump array index Loop CGI_NumAcceptTypes = n ' Fill in global count End Sub