Previous Page TOC Next Page See Page



— 7 —
Creating CGI Applications in Visual Basic


Now that you've been properly introduced to both in Chapter 2, "HTTP: How to Speak and in Chapter 6, "The Win/CGI interface, it's time to build your first server-side Visual Basic application. Although this process will build on the Visual Basic experience you already have, if you've never developed an application that runs asynchronously at a random point in time and allows no interactivity, you're in for a treat!

In this chapter we'll discuss the differences between the "typical" Visual Basic application (if there is such a beast) and a CGI application. Then, after introducing some code you'll use throughout the remainder of the book, you'll build a few simple CGI applications, including the application that was used to create the Web page shown in Figure 6.2.

How CGI Apps Differ from "Typical" Visual Basic Apps


As I mentioned in the introduction to this chapter, writing CGI applications is a completely different animal than writing most Visual Basic applications. This section discusses some of the major differences that you must keep in mind when writing an application that your Web server will launch. If you forget these differences, your application will probably leave the Web surfer wondering why the browser displays the Document returned no data message when the Submit button is clicked!

Waiting For Response. . .


One of the most important things to keep in mind is that while your program is executing happily on the server machine, the user is staring at what might as well be a blank screen. The typical Web browser application (also referred to as a user agent), displays a message in its status panel to the effect of Connected to www.xyz.com...Waiting for response.... The cursor is the hourglass cursor, further emphasizing to users that they're on hold. Therefore, CGI applications must do their thing and return control to the Web server as quickly as possible. The program should avoid using any but the quickest methods of accomplishing what needs to be accomplished.

For instance, You should avoid attempting to start a slow OLE automation server. If the application attempts to create an Excel spreadsheet object, stuff data into it, analyze the data, export the spreadsheet to a Word document, and then return the Word document to the user, the client will probably produce a time-out error. Unless, of course, your Web server is running on a machine with an ultra-fast motherboard and hard drives. Unfortunately, there are no "rules of thumb" for what you should and shouldn't do. Only experimentation with the application will reveal how fast or slow it operates.

The Sluggish Net


Another item to keep in mind while we're discussing speed is the speed of the user's connection. If your application returns complicated Web pages with lots of graphics on them, you'll want to connect to the server and try the application using a 14.4Kbps modem. This simulates not only the typical user's connection, but also a fast T-1 connection from across the country. As the Internet becomes more and more crowded, even fast access points slow down as the user traverses the continent, hopping from network to network before finally reaching your server.

This is not a concern for CGI applications only—when designing a Web site, you should attempt to design for the lowest common denominator. If you can afford separate sets of pages for user's with fast and slow connections, the effort is usually very much appreciated.

Do Not Allow for Interactivity


Perhaps the most important item to keep in mind is this: No one is there to click the OK button on a message box that a CGI application might open. This is particularly true on Windows NT Web servers that run as a system service. For debugging on a Windows 3.x or Windows 95 Web server, such interactivity may work. However, unless you want to sit by the machine while the Web server is operating and click OK, make sure you remove all possibility of having a message box appear.

If you're running a Windows NT Web server, such as the Microsoft Internet Information Server, the problem is even more severe. A CGI application that causes a message box also more than likely causes a time-out error on the client or returns no data if the server realizes the app is effectively dead in the water and times out first.

If you use common code modules, comb through them to make sure they contain no user interface code.

Use Tighter, Centralized Error Control


Make sure error trapping is turned on in the Sub Main routine and that, if errors are trapped separately in procedure and function calls, that no message boxes will display! It is best to use a central error trapping mechanism: The only ON ERROR... statement should be in the Sub Main procedure. This provides you with the ability to control exactly what code is executed when an error occurs. It also helps eliminate the possibility of an object requiring interaction being opened. The examples you'll build later use this type of error trapping.



Unless the application requires user-interface controls for some reason, don't use any forms in your application. Use Sub Main in a module instead, for the startup. This decreases the size and load time of your application.


Synchronizing with HTML Forms


When designing an application that accepts input from the user (such as a guest book registration or search engine), you must keep CGI data in the application and input elements on the HTML forms in synch. If you change a form input element's NAME tag, for instance, you must update the CGI code to reflect this change. This becomes more apparent when you start into the examples.

For example, you wouldn't try to access a textbox named Text1 in a typical application if it didn't exist. Likewise, you can't get meaningful data from a CGI field named Text1 if one doesn't exist on the HTML form that serves as the user interface for a CGI application.

Formatting Output for HTML Browsers


Just as you have to be conscious of HTML forms for inputting data, you must also take care with HTML when outputting data to the client. Although HTML is an Internet standard, each Web browser renders an HTML document slightly (or, in some cases, grossly) different. You should limit what you try to accomplish with the output page in order to serve the most number of browsers possible.

http://www.research.digital.com/nsl/formtest/home.html

There is an HTTP header field and a corresponding CGI data field called UserAgent (in the CGI data file it's in the [CGI] system as User Agent). This field is where the Web client that generated the request message specifies its name and version. This could be useful for determining what to output to the client. However, there are so many different values used for this field that trying to make sense of it can be more trouble than it's worth. For a demonstration of this, visit the HTML Form-Testing Home at ://www.research.digital.com/nsl/formtest/home.html and follow the Test Results Sorted by Browser link. This will provide a long list of all the UserAgent values that have been tested by the site.

We hope a future version of HTTP (and Win/CGI) will provide a field for specifying HTML conformance and the graphical capabilities of client applications. This type of field would provide meaningful information to server-side applications when those applications produce HTML output.

Leave No Objects or Files Open


When the CGI program is ready to end, make sure you close all open files and any objects that have been created. The mechanism the server uses to monitor when your program exits has been known to trigger before the program has completely cleaned itself up. As most VB programmers are aware, VB cleans up behind an application if it leaves files or databases open. However, doing so in the CGI environment may cause unexpected results. This is particularly true with the CGI output file. If it is not explicitly closed before the server attempts to open it for transmission to the client, you're in for big trouble! The server may actually read all that's available in the file but miss the last segment of it. This could happen if VB has to close the file after the application exits—the last pieces of data the app wrote to it won't actually make it to the disk until the file is closed.

Use AutoNumber Fields for Access Databases


Because of the tremendous concurrency issues that arise on Web servers, you should always allow the database engine to specify primary keys. This ensures that two concurrently running CGI applications that add records to a table won't pick the same key.

Designing Some Oft-Used Routines


Well, now that all the dire warnings of the previous section are out of the way, it's time to actually write some code! This section will provide a few routines that you'll use in the examples of this chapter, as well as throughout the remainder of the book. This section won't give all the routines used in the book, though. You'll add more as you go along.

For now, we'll examine two code modules: FUNCS.BAS, which houses common routines that might be used in any application, and CGI.BAS, which houses routines specific to Win/CGI and CGI applications. This section will provide the complete code for the routines we'll cover. However, because all the code is on the CD-ROM for your use and modification, don't expect to see full code segments throughout the rest of the book.

However, because all the code is on the CD-ROM for your use and modification, don't expect to see full code segments throughout the rest of the book

The Code from FUNCS.BAS


This module is used for general-purpose routines that could be used in any application. Some of the routines are stupidly simple, others can be pretty neat to dissect and use.

This section will discuss two of the routines that you'll use quite often: tReplaceChars() and ParseString.

tReplaceChars()

This routine is used to replace a given string within another string with a third string. You use this routine mostly when generating database SQL statements to replace single quote characters with two single quote characters.

For example, if you attempted to update a text field in a database using the SQL statement UPDATE MyTable SET LastName = 'O'Neil', you'd get a syntax error due to unmatched quote characters. The correct SQL for this situation is UPDATE MyTable SET LastName = 'O''Neil'. How does tReplaceChars() correct this?

The following is some code that calls tReplaceChars():

tName$ = "O'Neil"
tName$ = tReplaceChars(tName$,"'","''")
lSQL$ = "UPDATE MyTable SET LastName = '" & tName$ & "'"

The final value of tName$ is "O''Neil", and thus the SQL statement contained in lSQL$ is valid.

tReplaceChars() not only replaces single-character strings; it can also replace entire words contained within a string. The possible uses for this routine are endless. So, without further ado, Listing 7.1 contains the code for tReplaceChars().

Listing 7.1. tReplaceChars().

Function tReplaceChars(ptInputStr As String, ptLookFor As String, _
                        ptReplaceWith As String) As String
    lsloc% = InStr(1, ptInputStr, ptLookFor, 1)
    If lsloc% = 0 Or Len(ptLookFor) = 0 Then
        tReplaceChars = ptInputStr
        Exit Function
    End If
    lRLen% = Len(ptReplaceWith)
    lLLen% = Len(ptLookFor)
    lxx$ = ptInputStr
    Do Until lsloc% = 0
        lxx$ = Mid$(lxx$, 1, lsloc% - 1) + ptReplaceWith + _
               Mid$(lxx$, lsloc% + lLLen%, Len(lxx$) - lLLen% - lsloc% + 1)
        lsloc% = InStr(lsloc% + lRLen%, lxx$, ptLookFor, 1)
    Loop
    tReplaceChars = lxx$
End Function

Briefly, here's how tReplaceChars() works. The first line of the function tests to see if ptLookFor (the string to be replaced) exists in ptInputStr (the input string). The Instr() function is called with its final parameter being 1. This causes a case-insensitive search. If the Instr() function returns a value of 0, ptLookFor is not in ptInputStr, and the function returns the input string. (There is nothing to replace so the output string is the same as the input string.) If there is an occurrence of ptLookFor within ptInputStr, the local variable lsloc% contains the start of the first occurrence. The value of ptInputStr is stored in a temporary string (lxx$).

The Do Until lsloc% = 0 loop is where all of the replacement takes place. The first line of the loop takes the portion of the temporary string up to the first occurrence of ptLookFor, appends the ptReplaceWith string, and then appends the portion of the temporary string that appears after the END of the searched-for string. Finally, you look for ptLookFor again and assign its starting position to lsloc%. When there are no more occurrences of ptLookFor, lsloc% will be 0 (zero), and the Do Until will be met, causing the loop to end. The final line assigns the temporary string to the function's output.

Here are some samples:

tReplaceChars("Quick Brown Fox", "Brown", "Red") = "Quick Red Fox"

This call replaces the "Brown" in "Quick Brown Fox" with "Red", as you can see from the results. The following replaces every occurrence of "BB" with "DD" in the string "ABBBCBBC":

tReplaceChars("ABBBCBBC", "BB", "DD") = "ADDBCDDC"

Finally, the following shows how the introduction of a space at the end of the string to be replaced can affect the results:

tReplaceChars("A BBB C BB C", "BB ", "DD ") = "A BBB C DD C"

ParseString

The ParseString procedure takes a delimited string and makes an array out of the delimited elements. For example, if you have a string that looks like: "Jim,John,Jack" and want an array of all the names contained in the string, you would call ParseString(NameArray, "Jim,John,Jack", ",") and, when the procedure returned, NameArray would contain three elements:

NameArray(1) = "Jim"
NameArray(2) = "John"
NameArray(3) = "Jack"

You'll use this procedure extensively to enumerate the fields contained within the CGI data file. Listing 7.2 contains the code for this procedure.

Listing 7.2. ParseString.

Sub ParseString(ptArray() As String, ptString As String, ptDelim As String)
'lPos is an array containing the positions of the delimiters and
'the start and end of the string
Dim lPos() As Integer
ReDim lPos(1)
lPos(0) = 1
lPos(1) = InStr(1, ptString, ptDelim)
lNum% = 1
If lPos(1) <> 0 Then
    Do
        lNum% = lNum% + 1
        ReDim Preserve lPos(lNum%)
        lPos(lNum%) = InStr(lPos(lNum% - 1) + 1, ptString, ptDelim)
    Loop Until lPos(lNum%) = 0
End If
lPos(lNum%) = Len(ptString)
ReDim ptArray(lNum%)
If lNum% > 1 Then
    For ln% = 1 To lNum%
        Select Case ln%
        Case 1      'First substring
            ptArray(ln%) = Mid$(ptString, lPos(ln% - 1), _
                (lPos(ln%) - lPos(ln% - 1)))
        Case lNum%  'Last substring
            ptArray(ln%) = Mid$(ptString, lPos(ln% - 1) + 1, _
                (lPos(ln%) - lPos(ln% - 1)))
        Case Else   'All other substrings
            ptArray(ln%) = Mid$(ptString, lPos(ln% - 1) + 1, _
                (lPos(ln%) - lPos(ln% - 1) - 1))
        End Select
    Next ln%
Else
    ptArray(1) = Mid$(ptString, 1, lPos(lNum%) + 1)
End If
End Sub

This code is pretty straightforward. The first section creates an array that contains the positions of the occurrences of the delimiter (ptDelim) within the string (ptString) and keeps track of how many delimiters were encountered (lNum%). The second section steps through the position array and pulls out each substring from ptString. Each substring is then placed into the array (ptArray) that was passed to ParseString.

The Code from CGI32.BAS


The file CGI32.BAS is used as a common code module by all of the CGI applications you'll develop in this book. It contains the data types, global variables, functions, and procedures that you'll use in creating your CGI applications.

The CGIData Type

The first thing you'll code is a user-defined data type. The CGIData type is used to store all the standard CGI data fields that the server passes to CGI applications.

The CGIData type includes an array element named FormSmallFields. This element is of type CGI_FormFieldType, which is defined in Listing 7.3. This type allows storage of the form or the URL's input field name (key) as well as the value specified on the form or URL (value).

Listing 7.3. The CGI_FormFieldType data type.

Type CGI_FormFieldType
    key As String
    value As String
End Type

Listing 7.4 provides the code for the CGIData user-defined type.

Listing 7.4. The CGIData type.

Type CGIData                'used to access the CGI data file
    ProfileFile As String       'complete path to the CGI data file
    RequestMethod As String
    QueryString As String
    DocumentRoot As String
    LogicalPath As String
    PhysicalPath As String
    ExecutablePath As String
    CGIVersion As String
    RequestProtocol As String
    Referer As String
    ServerSoftware As String
    ServerName As String
    ServerPort As String
    ServerAdmin As String
    UserAgent As String
    RemoteHost As String
    RemoteAddress As String
    ContentFile As String
    ContentLength As Long
    ContentType As String
    GMTOffset As Variant        'time serial
    DebugModeOn As Integer      'False if Debug Mode=No, True otherwise
    OutputFileName As String      'Output File field from [System]
    OutputFileHandle As Integer   'file handle used for output
    FormFieldCount As Integer     'how many data fields?
    FormSmallFields() As CGI_FormFieldType    'holds all data fields
End Type

Most of the fields are defined in the Chapter 6. However, a few fields have been added.

The ProfileFile field is where you'll store the name of the CGI data file. This is passed to the application as the first (and maybe only) command-line parameter.

The OutputFileHandle is the file handle to use when writing information to the output file. The output file path is specified in OutputFileName.

The FormSmallFields array contains most of the fields either from the HTML form that causes the application to launch, or from the URL used in an HTTP GET request message. The field FormFieldCount holds a count of the number of fields contained in FormSmallFields. The array holds fields that were found in the [Form Literal] and [Form External] sections of the CGI data. These are the only sections we'll cover in this chapter. The other sections contain fields that you'll rarely encounter when writing CGI applications.

The global variable guCGIData is of type CGIData and is used to store the CGI data.

The GetKeyValue() Function

The GetKeyValue() function is used to retrieve data from the CGI data file. It can retrieve either a specific field or all of the fields from the specified section. The function uses the value of guCGIData.ProfileFile to determine which file to use as the CGI data file. The function is defined in Listing 7.5.

The string parameter ptSection is the section you'll be getting data from. The values you'll pass with this parameter include "CGI", "Accept", "System", "Extra Headers", and "Form Literal", to match the possible sections contained in the CGI data file. The parameter ptKey specifies the field to be retrieved. If ptKey is passed as an empty string, all the field names present in the section ptSection are returned. This feature is used to retrieve all the items in the [Accept] section and all of the fields in the [Form Literal] and [Form External] sections, for example.

The function works by calling the Windows API function GetPrivateProfileString(). GetPrivateProfileString() reads Windows initialization files and stuffs the value read into a string buffer provided to it. It returns a count of the number of characters placed in that string. The string is terminated with a NULL character (CHR$(0)). GetKeyValue() truncates the string to the length specified by the return value of GetPrivateProfileString() before returning it.

Listing 7.5. GetKeyValue() function.

Public Function GetKeyValue(ptSection As String, _
         ptKey As String, piLength As Long) As String
Dim bucket$, res&
bucket$ = Space$(piLength + 1)
If Len(ptKey) Then
    'return the specfied key
    res& = GetPrivateProfileString(ptSection, ptKey, "", _
           bucket$, piLength, guCGIData.ProfileFile)
Else
    'return all keys contained in ptSection
    res& = GetPrivateProfileString(ptSection, 0&, "", _
           bucket$, piLength, guCGIData.ProfileFile)
End If
bucket$ = Left$(bucket$, res&)
GetKeyValue = bucket$
End Function

GetPrivateProfileString() removes single and double quotes that surround the value being read. So, if the CGI data file contains

[Form External]
LastName='Jones'

GetKeyValue() would return only the string "Jones".

The LoadCGIData() Function and the LoadFields Procedure

The LoadCGIData() function is where the vast majority of the CGI data is retrieved. The function uses the value of guCGIData.ProfileFile as the path to the file that contains the CGI data. Then, the values of the many CGI fields are placed into the other elements of the guCGIData record variable. The function also obtains a file handle to be used for the application's output (guCGIData.OutpuFileHandle) and opens the output file for Write access. The code for LoadCGIData() is given in Listing 7.6.

The optional parameter piLoadFields informs the routine not to retrieve any of the [Form Literal] and [Form External] fields. This is useful in applications where a large number of fields are passed to the application but not all fields are used by the application. By setting the piLoadFields parameter to False when calling LoadCGIData(), you can save the time required to read all the fields. However, you have to call either the GetKeyValue() or GetPrivateProfileString() functions to retrieve the field values individually.

Listing 7.6. The LoadCGIData() function.

Public Function LoadCGIData(Optional piLoadFields As Variant) As Integer
'Input: (Optional) piLoadFields = TRUE or not present: 
'        load [Form Literal] and [Form External]
'                               = FALSE: don't load the sections
'   Also, must have guCGIData.ProfileFile set to the path of the CGI data file
'Ouput: TRUE if successful, FALSE otherwise
LoadCGIData = False 'initial value
With guCGIData
    'if the ProfileFile element has not been defined, error out
    If Len(Trim$(.ProfileFile)) = 0 Then Exit Function
    .OutputFileName = GetKeyValue("System", "Output File", 255)
    .RequestMethod = GetKeyValue("CGI", "Request Method", 255)
    .QueryString = GetKeyValue("CGI", "Query String", 255)
    .DocumentRoot = GetKeyValue("CGI", "Document Root", 255)
    .LogicalPath = GetKeyValue("CGI", "Logical Path", 255)
    .PhysicalPath = GetKeyValue("CGI", "Physical Path", 255)
    .ExecutablePath = GetKeyValue("CGI", "Executable Path", 255)
    .CGIVersion = GetKeyValue("CGI", "CGI Version", 255)
    .RequestProtocol = GetKeyValue("CGI", "Request Profile", 255)
    .Referer = GetKeyValue("CGI", "Referer", 255)
    .ServerSoftware = GetKeyValue("CGI", "Server Software", 255)
    .ServerName = GetKeyValue("CGI", "Server Name", 255)
    .ServerPort = GetKeyValue("CGI", "Server Port", 255)
    .ServerAdmin = GetKeyValue("CGI", "Server Admin", 255)
    .UserAgent = GetKeyValue("CGI", "User Agent", 255)
    .RemoteHost = GetKeyValue("CGI", "Remote Host", 255)
    .RemoteAddress = GetKeyValue("CGI", "Remote Address", 255)
    .ContentFile = GetKeyValue("CGI", "Content File", 255)
    .ContentLength = Val(GetKeyValue("CGI", "Content Length", 255))
    .ContentType = GetKeyValue("CGI", "Content Type", 255)
    buf = GetKeyValue("System", "GMT Offset", 255)
    If buf <> "" Then
        .GMTOffset = CVDate(Val(buf) / 86400#)
    Else
        .GMTOffset = 0
    End If
    .DebugModeOn = (UCase$(GetKeyValue("System", _
                   "DebugModeOn", 255)) = "YES")
    'get an output file handle and open the output file
    .OutputFileHandle = FreeFile
    Open .OutputFileName For Output Access Write As #.OutputFileHandle
End With
'if piLoadFields was not passed, load the fields
If IsMissing(piLoadFields) Then
    Call LoadFields
Else
    'otherwise, check piLoadFields to determine if
    'we should load the fields
    If piLoadFields Then Call LoadFields
End If
LoadCGIData = True
End Function

If piLoadFields is True or is not passed to the function, GetCGIData() calls the LoadFields procedure. This procedure, shown in Listing 7.7, retrieves the fields from [Form Literal] and those referenced in [Form External] into the guCGIData.FormSmallFields array. Recall from Chapter 6 that the format for these sections is as follows:

 [Form Literal]
FormFieldName=Value
[Form External]
FormFieldName=filename filelength

For the [Form Literal] section, LoadField first retrieves all of the FormFieldNames contained in the section. It then calls ParseString() to place these names into an array. Finally, it iterates through this array and assigns the FormFieldNames to the .key element of guCGIData.FormSmallFields and calls GetKeyValue() to get the value from the CGI data file to assign to the .value element.

For the [Form External] section, the procedure retrieves the field names in the same manner as the [Form Literal] section. The .value element, however, is contained in the file specified by the filename element in the value portion of the field's entry in the [Form External] section (see above). So, as the procedure iterates through each key in the section, it opens the file specified (using Binary access just in case the data is not textual) and reads its contents into the .value element.

Finally, the LoadFields procedure sets the value of guCGIData.FormFieldCount.

Listing 7.7. The LoadFields procedure.

Public Sub LoadFields()
    Dim tString$, iCount%
    Dim tmpArray() As String
    guCGIData.FormFieldCount = 0
    'get the Form Literal section
    tString$ = GetKeyValue("Form Literal", "", 4096)
    'tString$ is now "key1" & chr$(0) & "key2" & chr$(0) ... & chr$(0) & chr$(0)
    If Len(tString$) Then
        tString$ = Left$(tString$, Len(tString$) - 1)
        Call ParseString(tmpArray, tString$, Chr$(0))
        iCount% = UBound(tmpArray)
        ReDim guCGIData.FormSmallFields(iCount%)
        For i% = 1 To iCount%
            guCGIData.FormSmallFields(i%).key = tmpArray(i%)
            guCGIData.FormSmallFields(i%).value = GetKeyValue("Form Literal", _
                  tmpArray(i%), 255)
        Next
        guCGIData.FormFieldCount = iCount%
    End If
    'get the Form External section
    tString$ = GetKeyValue("Form External", "", 4096)
    If Len(tString$) Then
        tString$ = Left$(tString$, Len(tString$) - 1)
        Call ParseString(tmpArray, tString$, Chr$(0))
        iCount% = UBound(tmpArray)
        With guCGIData
            ReDim Preserve.FormSmallFields(.FormFieldCount + iCount%)
            For i% = 1 To iCount%
                j% = .FormFieldCount + i%
                .FormSmallFields(j%).key = tmpArray(i%)
                'get the value of the field
                buf$ = GetKeyValue("Form External", tmpArray(i%), 255)
                'the format of byf$ is:
                ' <pathname> SP <length>
                pos% = InStr(buf$, " ")
                pathname$ = Left$(buf$, pos% - 1)
                contentlen& = CLng(Mid$(buf$, pos% + 1))
                filenum% = FreeFile
                Open pathname$ For Binary Access Read As #filenum%
                .FormSmallFields(j%).value = Space$(contentlen&)
                Get #filenum%, ,.FormSmallFields(j%).value
            Next
           .FormFieldCount =.FormFieldCount + iCount%
        End With
    End If
End Sub

The GetFieldValue() Function

The GetFieldValue() function (see Listing 7.8) is used to retrieve the field values from the guCGIData.FormSmallFields array. Its sole parameter is the name of the field you'd like to retrieve. The guCGIData.FormSmallFields array must be filled before GetFieldValue() is called.

As an example, suppose the HTML form that launches the application contains a textbox input called "LastName". You can retrieve the data entered into it by using this line:

GetFieldValue("LastName")

Listing 7.8. The GetFieldValue() function.

Public Function GetFieldValue(tFieldName As String) As String
    GetFieldValue = ""  'default to an empty string
    If Not (FieldPresent(tFieldName)) Then Exit Function
    For i% = 1 To guCGIData.FormFieldCount
        If guCGIData.FormSmallFields(i%).key = tFieldName Then
            GetFieldValue = guCGIData.FormSmallFields(i%).value
            Exit Function
        End If
    Next
End Function

The function defaults to an empty string if the value is not overridden. The function checks to see if the field specified by tFieldName is present in the guCGIData.FormSmallFields array by calling FieldPresent(). If it's not present, the function exits with the empty string as its return value. If the field is present, the function iterates through the array until it locates the field being retrieved. After locating the field, the function assigns its return value to the .value element for that item in the array.

The OutputString Procedure

The final procedure we'll examine before moving on to an actual application is OutputString. This procedure simply writes the string passed to it to the output file opened in LoadCGIData(). Listing 7.9 contains the code. Note that no error handling code is present. So the error handling can be centralized, it is done by the Sub Main routine.

Listing 7.9. The OutputString Procedure

Public Sub OutputString(ptOutputString As String)
    Print #guCGIData.OutputFileHandle, ptOutputString
End Sub

Creating the HTML Form


You're getting closer to actually writing your first CGI applications. However, there's one more step to cover before you can move on. You first must create an HTML document (also known as a Web page). This Web page (shown in Figure 7.1) contains the data input controls in which the user can enter information to be passed to the CGI application. It also has a Submit button and a Reset button. When the user clicks the Submit button, the Web browser sends a request message to the server, which includes the name of the CGI application to launch. The Reset button is a special HTML control that clears out all the input boxes on that form.

Figure 7.1. The simple Web page for this chapter.

This Web page is created using the HTML code in Listing 7.10. Create this Web page on your server so that it is accessible to your Web browser.

While a complete explanation of the HTML contained in Listing 7.10 is beyond the scope of this chapter, a little explanation is in order. Appendix A, "Basic HTML Tags," contains more detailed explanations of the HTML coding used here.

Listing 7.10. The HTML for the simple Web page.

<HTML>
<HEAD><TITLE>Testing Win/CGI Applications</TITLE></HEAD><BODY>
<FORM METHOD=POST ACTION="/cgi-win/test-cgi.dll">
Enter someone's First Name: <INPUT NAME="Name1" TYPE="TEXT" SIZE="30"><p>
Enter someone else's First Name: <INPUT NAME="Name2" TYPE="TEXT" SIZE="30"><p>
Pick something: <SELECT Name ="Fetch" VALUE="water">
<OPTION>water</OPTION><OPTION>soda</OPTION><OPTION>worms</OPTION></SELECT><p>
<INPUT TYPE="SUBMIT">   <INPUT TYPE="RESET">
</FORM>
</BODY>
</HTML>

On the third line of Listing 7.10, you see the definition of an HTML FORM element. This element has several tags that define how the form operates. The first tag is METHOD. This defines the request method that the browser should use when sending the request message to the server after the Submit button is pressed. The choices are GET and POST. This simple Web page uses POST. The second tag (ACTION) specifies the application to be executed when the user clicks the the form's Submit button . This is a URI, which can be either an absolute address or a relative address. Here, you're specifying a relative address because the application to be executed (test-cgi.dll) resides on the same server as the Web page. Replace the text in quotes following the ACTION element with the URI for the application on your server.



The server used in this example is the Microsoft Internet Information Server (IIS). The Microsoft server doesn't directly support the Win/CGI specification. Appendix C, "Win/CGI on the Microsoft Internet Information", discusses the IIS and explains why the form is calling a DLL instead of an executable. For now, it's important only to know that the DLL specified creates the CGI data file and then launches the Visual Basic Win/CGI executable you'll create in the following sections.

The four lines following the <FORM ...> line specify the input controls being used. The first two lines define textboxes that have field names of "Name1" and "Name2" respectively. The third and fourth lines specify a single-select dropdown list box. The items that appear in the list are "water", "soda", and "worms", with "water" being the default value. The field name for the listbox is "Fetch".

The fifth line specifies the two buttons, "Submit" and "Reset". Finally, the sixth line after the <FORM ...> line ends the form definition with the closing </FORM> tag.

Installing the Application on Your Web Server


This book assumes that you have Visual Basic installed on the machine on which your Web server is running. This simplifies installation of the CGI applications you'll develop because all the system files required were loaded when Visual Basic was installed.

The first step is determining whether or not your server is set up to execute applications. And, if so, where those applications should reside. All Web servers should have some sort of directory setup mechanism. For the Microsoft Internet Information Server (IIS), you can find this by double-clicking the WWW service entry in the Internet Service Manager application and selecting the Directories tab. For the WebSite server, the Server Admin application has a tab called Mapping. By selecting the "Windows CGI" radio button in the List Selector frame on this tab, you can get a list of all the directories that can house Win/CGI applications.

While you're in the Web server's setup application, turn on CGI tracing or CGI debug mode, if available. With debugging on, the server does not delete the files created during the CGI process. This is useful for examining what data has been passed to the application and what the output file really looks like.

In WebSite, the debugging option is turned on using the Logging tab's CGI Execution checkbox in the Tracing Options frame. For the Microsoft IIS, this feature is unavailable "out of the box". Appendix C discusses how to turn on debug mode.

After the proper directory is set up, code the application and create an executable. Then, simply copy the executable to the Win/CGI directory specified on the WebSite server admin's Mapping tab or on the IIS' Directories tab. It can now be executed from a Web browser's URL entry box, a Web page, or by using the HTTP application created in Chapter 5, "Retrieving Information From The Web."

Win/CGI's Version of Hello World


The classic example used as the first application in many programming texts is a program called Hello World. This application simply displays the text Hello World on the system's output. For CGI applications, however, the classic application seems to be one that returns all the data contained in the CGI data file. That's what you'll code in this section.

The code you'll create here won't rely on CGI32.BAS for any of its routines. If you don't include CGI32.BAS in the project, however, you will need to declare the Windows API GetPrivateProfileString() function in the Declarations section of the code module. The code does rely on FUNCS.BAS for the ParseString procedure; so be sure to include that in your project or copy the procedure's code into your module.

Listing 7.11 contains the code for a Sub Main procedure. Place this code in a new module (you can either type it in or retrieve it from the CD-ROM included with the book). Select File | Make Exe File from the Visual Basic menu. On the Make EXE dialog box, enter test-cgi.exe as your executable name and make sure the path points to your cgi-win directory identified in the previous section. Once the executable is created, launch your Web browser and enter the URL for the page created in the section "Creating the HTML Form" earlier. Enter some information in the text boxes and click OK. You should see a page similar to Figure 7.2.

Listing 7.11 contains the code for a Sub Main procedure. Place this code in a new module (you can either type it in or retrieve it from the CD-ROM included with the book). Select File | Make Exe File... from the Visual

Listing 7.11. Hello World code.

Public Sub main()
    On Error GoTo FormError
    Dim ProfileFile$, buffer$
    Dim OutputFileHandle As Integer
    Dim KeyArray() As String
    'get the CGI data file/path
    If InStr(Command$, " ") Then
        ProfileFile$ = Left$(Command$, InStr(Command$, " ") - 1)
    Else
        ProfileFile$ = Command$
    End If
    'get the output path
    buffer$ = Space$(256)
    res& = GetPrivateProfileString("System", "Output File", _
           "", buffer$, 255, ProfileFile$)
    buffer$ = Left$(buffer$, res&)
    'get the next available file handle
    OutputFileHandle = FreeFile
    'open the file
    Open buffer$ For Output Access Write As #OutputFileHandle
    'write some header stuff to the file
    Print #OutputFileHandle, "Content-Type: text/html"
    Print #OutputFileHandle, ""
    Print #OutputFileHandle, "<html><body>"
    'define an array with all the section names
    Dim Sections(7) As String
    Sections(0) = "CGI"
    Sections(1) = "Accept"
    Sections(2) = "System"
    Sections(3) = "Extra Headers"
    Sections(4) = "Form Literal"
    Sections(5) = "Form External"
    Sections(6) = "Form Huge"
    Sections(7) = "Form File"
    For j% = 0 To 7
        tSection$ = Sections(j%)
        'get all of the fields in the section
        buffer$ = Space$(4096)
        res& = GetPrivateProfileString(tSection$, 0&, _
               "", buffer$, 4095, ProfileFile$)
        If res& > 1 Then
            'strip off the last chr$(0)
            buffer$ = Left$(buffer$, res& - 1)
            'put the field names into an array
            Call ParseString(KeyArray(), buffer$, Chr$(0))
            Print #OutputFileHandle, "[" & tSection$ & "]<br>"
            'write all of the keys
            For i% = 1 To UBound(KeyArray)
                'print the key name and stay on the same line
                Print #OutputFileHandle, KeyArray(i%) & "=";
                'get the value of the field
                buffer$ = Space$(255)
                res& = GetPrivateProfileString(tSection$, KeyArray(i%), _
                           "", buffer$, 255, ProfileFile$)
                buffer$ = Left$(buffer$, res&)
                'print it to the output file
                Print #OutputFileHandle, buffer$ & "<br>"
            Next i%
        End If
    Next j%
    Print #OutputFileHandle, "</body></html>"
    'close the output file
    Close #OutputFileHandle
    End
FormError:
    If OutputFileHandle <> 0 then
        Seek #OutputFileHandle, 1
        Print #OutputFileHandle, "Content-Type: text/html"
        Print #OutputFileHandle, ""
        Print #OutputFileHandle, "<html><body>"
        Print #OutputFileHandle, "Error: " & Err & "<p>"
        Print #OutputFileHandle, Error$ & "<p></body></html>"
        Close #OutputFileHandle
    End If
    End
End Sub

Figure 7.2. The output of the Hello World application.

The code is fairly simple to understand. The first step is to obtain the path to the CGI data file from the command line. Then, retrieve the name of the output file from the CGI data file by using GetPrivateProfileString(). A new file handle is obtained, and the file is opened for output. The header lines are written to the file. These instruct the server that you're sending HTML formatted text.

An array is created to hold the names of the eight possible sections of the CGI data file. You then start a loop that loops through this array and extracts the information from the section if it exists. By calling GetPrivateProfileString() with a zero of the LONG data type ("0&") as the second parameter, you can retrieve all the available items in the section specified by the first parameter. If the section does not exist or contains no items, an empty string is returned. After calling GetPrivateProfileString() in this manner, you check to see if it returns a value greater than zero (remember that this function returns the length of the string it retrieves from the CGI data file). If it did indeed return a string, that string contains all the items in the current section you're iterating.

The items are separated by a NULL character (CHR$(0)) and end with two NULL characters. The code strips this last NULL before passing the string to ParseString, which fills KeyArray with all the item keys found for this section. Next, the code iterates through KeyArray and, using GetPrivateProfileString(), gets the value specified with each key.

Throughout the code several lines start with "Print #OutputFileHandle,". This is where the code writes information to the output file. In this case, the information is HTML formatted text. However, it could be any data you wish to send back to the client,.

The error trapping is pretty basic in this program. Any error sends the program to the FormError: label. The code then checks to see if you have a file handle. If one is available, you'll write some information about the error to an HTML output file and exit. If the program had not yet obtained a file handle when the error occurred, the code simply ends, usually causing the Web browser to display a message to the user to the effect of Server returned no data.

The CGI StoryTeller


"Why did I create a nice form if all I'm going to do is look at the CGI data file?" You may be asking. Well, in this section, you're going to create an application that actually uses the information entered on the form. The application is still very basic, but it demonstrates the use of many of the functions contained in CGI32.BAS.

Start a new project in Visual Basic. Add CGI32.BAS and FUNCS.BAS as well as a new code module. This new module is where you'll place Sub Main and a procedure called StoryTeller. These two routines are shown in Listing 7.12.

Listing 7.12. Sub Main and StoryTeller code.

Public Sub main()
    On Error GoTo FormError
    If InStr(Command$, " ") Then
        guCGIData.ProfileFile = Left$(Command$, InStr(Command$, " ") - 1)
    Else
        guCGIData.ProfileFile = Command$
    End If
    If LoadCGIData() = 0 Then
        'if we couldn't load all the CGI data,
        'could we at least open the Output File?
        If guCGIData.OutputFileHandle <> 0 Then
            'yes - call ErrorHandler to handle this
            Call ErrorHandler(-1, "Error loading CGI Data File")
        Else
            'no - forget about anything else!
            End
        End If
    End If
    'let's tell a story!
    Call StoryTeller
FormError:
    Call ErrorHandler(Err, Error$)
End Sub
Public Sub StoryTeller()
    OutputString "Content-Type: text/html"
    OutputString ""
    OutputString "<html><head><title>CGI Story Teller!</title></head><body>"
    If Len(GetKeyValue("Name1")) And Len(GetKeyValue(("Name2")) Then
        OutputString "<h1>The Story of " & GetKeyValue("Name1") 
        OutputString " and " & GetKeyValue("Name2") & "</h1>"
        OutputString "<p>"
        OutputString GetFieldValue("Name1") & " and " & GetFieldValue("Name2")
        OutputString " went up a hill to fetch a pail of "
        OutputString GetFieldValue("Fetch") & ".<p>"
        OutputString "<h2>Thank You for Reading!</h2>"
    Else
        OutputString "<h1>Hey! You have to enter both names "
        OutputString "or the story is meaningless!</h1>"
    End If
    OutputString "</body></html>"
    'close the output file
    Close #guCGIData.OutputFileHandle
    End
End Sub

As you can see, the use of routines from CGI32.BAS has greatly simplified the code. In Sub Main, the program starts the in the same way as the previous program: It obtains the path to the CGI data file. Since you're using CGI32.BAS, however, assign the path of the data file to guCGIData.ProfileFile. Recall that LoadCGIData() uses this variable when retrieving the CGI data.

After the profile file is identified, the code calls LoadCGIData() and checks the return value. If the function returns zero (meaning some problem occurred loading the CGI data), it then checks to see whether a file handle has been assigned. If a file handle exists, a call is made to the Errorhandler routine in CGI32.BAS; if one doesn't exist, the program simply exits because it has no valid file to which it can write any output.

If you got past LoadCGIData(), it's time to move on to StoryTeller. This procedure is where all the output is created.

You first inform the server that you're sending HTML formatted text by using the Content-Type header field. This is followed by a blank line. Then you check to make sure the user has entered some text in both name fields on the HTML form. If the user hasn't, you return a nasty response message informing your user of the need to do so. If there are names, you create a small story by combining static HTML with the values entered in the form's fields (by using the GetKeyValue() function).

Finally, the procedure finishes off the HTML file, closes the output file, and ends.

Create the executable file as in the previous section. Use test-cgi.exe as the filename, so you can use the same HTML file as the input form. Rename the previous test-cgi.exe file if you want to keep a copy.

Using your Web browser, open the HTML file created earlier in this chapter. Enter some names, select an item from the list, and click Submit. You should see a page similar to Figure 7.3. Return to the form and click Reset. Then click Submit again to see a nasty response about leaving the names blank.

Figure 7.3. Output of the StoryTeller application.

Summary


We've covered a lot of ground in this chapter. Most of the code samples are pretty basic and don't do a great deal. Still, you now have a good background on which to build real CGI applications.

If you're itching to create a useful CGI application to install on your Web server, Part II of the book presents several real-world applications you can adapt to meet your specific needs.

Previous Page Page Top TOC Next Page See Page