Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

CGI Programming & Visual Basic


CGI Programming & Visual Basic

Ofer is technical director of Quality-By-Vision, and author of CGI Programming with Visual Basic 5 (McGraw-Hill, 1997). He can be reached at [email protected].


The oCGI2 library I present here makes Visual Basic (VB) a viable CGI application-development tool. Although Visual Basic is not portable to platforms other than Win32, applications written using oCGI2 are portable across web servers available for Windows 95/NT (OmniHTTPd, IIS, PWS, and the like). In this article, I'll describe the oCGI2 library (available electronically; see "Resource Center," page 3), discuss some of its interesting features, and present sample applications that utilize it.

My original goal in writing the oCGI2 library was to provide Visual Basic applications with access to the STDIN/STDOUT streams. Visual Basic already has access to the other facilities required by CGI applications (the command line and environment variables). oCGI2 also has facilities for handling most CGI-related tasks such as decoding the parameter string.

The server sends the CGI parameter string in three ways:

  • Via the command line (ISINDEX).
  • Via the environment string (GET or PUT).
  • Via the STDIN stream (POST).

oCGI2 processes all of these methods through the ProcessCGI function. The application calls ProcessCGI as a manual constructor, since you may want to modify some parameters between the oCGI2 instantiation and the actual constructor call (for example, you may want to inject a personalized parameter string).

ProcessCGI calls ReadCGI, which returns the parameter string, then passes the string to SetParams, which parses the string and stores each name/value pair into a Visual Basic collection ("hashed" bag) called cParameters.

The ReadCGI function reads the QUERY_METHOD environment string to determine the method in which the server is passing the parameters to the CGI application. In the case of an ISINDEX submission (or when debugging), the application reads the parameter string from the application's command-line parameters. In the case of the PUT and GET methods, the function gets the parameter string from the QUERY_STRING environment variable.

Finally, if the stream is sent using the POST method, the application calls the ReadPost function. ReadPost (see Listing One) uses the GetStdHandle Win32 function to retrieve a valid file handle for the STDIN stream, then uses the ReadFile Win32 function to read the parameter string from STDIN.

The POST method does not impose a limit on the parameter string size. Both of the other techniques are often limited by the hosting operating-system constraints (that is, the maximum size of the environment area or the maximum size of a valid command-line string).

However, to read data from STDIN properly, Visual Basic must preallocate a string to be used for the ReadFile function. The oCGI2 class exports a variable (MaxPost) that can be used to modify the maximum size of the parameter string being passed to the application (the default is 10,000 characters). You must modify MaxPost before calling ProcessCGI if your application will have potentially large parameter strings.

For example, a simple shopping cart application could easily produce an HTML form with 300 items, each of which could have four HTML controls attached to it (some could be hidden controls). If each of these controls has a name of more than eight characters, you're already past the 10,000-character limit! Such an application must modify the MaxPost variable prior to calling the ProcessCGI function.

CGI Parameters

The parameter string has a simple coding scheme and consists of a list of parameter names and their corresponding value ParamName1=Value1&ParamName2=Value2&ParamNameN=ValueN. The oCGI2 library stores these parameters in a Visual Basic collection.

A secondary class, called ParamType, contains each parameter's name and value. A new instance of the class is constructed for each new parameter. The parameter objects are inserted into the collection with the parameter name serving as the hash key for the parameter.

The application can call the ReadParam function to retrieve the value of a parameter, given that parameter's name. ReadParam uses the hash to find the appropriate parameter object. If it does not find it, a VB exception is raised, which is handled in ReadParam by returning an empty string (see Listing Two).

Originally, the parameter class held only the value of the parameter. However, in many instances, an application may not know in advance the name of the parameter it will be using. For example, a shopping bag application could display a list of items. The quantity editbox for each item could have a name and value pair like Quantity210197=1. The numbers following the Quantity qualifier could very well indicate what the database key was for the item being purchased. In such instances, the programmer could find it useful to search through the list of parameters for qualifying names, as in Listing Three. Also, some HTML controls, such as submission buttons and check boxes, only return a name and value pair if they are actually selected or used. In such cases, having the name stored in the ParamType object is necessary to determine whether a control was selected.

ProcessCGI handles cookies in a similar manner. ProcessCGI calls the SetCookies function, which decodes the cookie string (passed to the application via the HTTP_COOKIE environment variable). SetCookies creates new ParamType objects and inserts them into the cCookies collection. Cookies can be retrieved by calling the ReadCookie function, which accesses the cCookies collection and retrieves the appropriate cookie value (if found). As with the cParameters collection, the cCookies collection is exposed and can be accessed directly.

Sending Cookies and Other HTTP Headers

oCGI2 provides a WriteCGI function for transferring information back to the web server, which subsequently transfers it to the browser that requested it. By default, the first WriteCGI call in an application sends the HTML MIME content type using the StartCGI function.

Listing Four is a simple "Hello World" CGI application, and Listing Five is a form-based application that uses ProcessCGI to receive an optional parameter from the originating HTML document and display it.

Launching the application in Listing Five using the URL http://localhost/cgi-bin/Helloworld.exe?Name=Ofer+LaOr yields the HTML page in Figure 1.

In most cases, the CGI program is returning HTML, so WriteCGI by default sends the HTML content type the first time it is called. However, WriteCGI has an optional parameter (fStartCGI) that prevents the HTML content type from being sent at all.

fStartCGI is used internally to send other HTTP headers, including cookies, since they need to be sent to the server before the content type. An application may also use this feature to send alternate content types (sounds, images, or complete files, for instance). For such cases, however, the application is responsible for sending out the appropriate content type.

Cookies are sent using the WriteCookie method, as in Listing Six. Because WriteCookie uses WriteCGI with the optional fStartCGI flag turned off, you must call WriteCookie before you call WriteCGI with fStartCGI turned on for the first time. Otherwise, the cookie string is sent after the content type, and the browser will assume that this string is part of the HTML stream, not a valid cookie.

Cookies are sent by the browser to the server only if the server name (domain name) and script name (path name) are both valid. This prevents sites from ever seeing cookies created by other sites: Sites may only read their own cookies. Cookies can be removed by the application through specifying a cookie expiration date that has already passed. Not specifying an expiration date at all creates a cookie that is active for the current browser session only.

Sites are limited to 20 cookies, each with less than four KB of data. If your application (for example, a shopping cart) requires more than 20 items, this limit presents a problem.

Since the total limit on cookies is 80-KB per site, oCGI2 offers the facility to create virtual cookies, which store information using their own format on top of standard cookies. Virtual cookies, which are standard cookies with name such as _1, _2, and so on, allow you to overcome the four-KB-per-cookie limit and to have more than 20 cookies. The only restriction is that the total size of virtual cookies cannot surpass the total cookie limit imposed by the browser.

For example, two cookies can be compressed into one virtual cookie by creating the following virtual cookie string and committing it as a regular cookie: _1=CookieName1=CookieValue1&CookieName2=CookieValue2. The actual cookie in this case is named _1 (the underscore denotes that this is a special cookie name). The contents of this cookie are CookieName1=CookieValue1&CookieName2=CookieValue2.

The ProcessVirtualCookies function is used to process these virtual cookies. ProcessVirtualCookies retrieves the virtual cookies, creates a cookie string sVirtualCookies, parses the virtual cookie string, removes the virtual cookies from the cookie collection, and places the individual virtual cookies back into the cookie collection. You can then retrieve these virtual cookies using a regular ReadCookie call.

Since virtual cookies are converted into standard cookies by the ProcessVirtualCookie function, ReadCookie can read them without any special coding. The _1 cookie is removed from the normal cookie list so the application does not inadvertently recognize it as a valid cookie. If the _1 cookie runs out of space, the _2 cookie is employed.

All virtual cookies have an identical expiration date, which is passed as an optional parameter to ProcessVirtualCookie. If an application needs cookies with different expiration dates, you can use both standard cookies and virtual cookies.

WriteVirtualCookie and DeleteVirtualCookie add and remove virtual cookies by modifying the virtual cookie string. This string is committed when the first standard WriteCGI call is made and StartCGI is initiated. The StartCGI function checks whether any virtual cookies have been modified. If so, it breaks up the virtual cookie string into individual standard cookies (namely _1, _2, and the like). Finally, if there were more cookies before than there are now (for instance, enough virtual cookies were removed from the virtual cookie string), the surplus cookies are simply removed.

Interfacing with oCGI2

An application may interface with the oCGI2 library using static linking, late binding, or early binding. Static linking means that each application has a local copy of the oCGI2 modules. This allows an application to be independent of everything other than the VB run-time libraries. However, the size of the application grows as a result, and any changes in oCGI2 requires recompiling each application that uses it.

The oCGI2 library provides a type library, a .tlb file that specifies to applications the identity of the library, where its functions reside, and the specification for each function (parameters, dispatch ID number, and so on). Type information is automatically generated by Visual Basic when the oCGI2 library is compiled. If oCGI2 is changed in such a manner that Visual Basic is forced to change this type information (for instance, a parameter in one of the functions is modified, or an existing function removed), Visual Basic will create a new type library.

Early binding means that the application binds to oCGI2's type library at compile time. However, if the type library information has been changed, you have to recompile the application. Late binding is slower, since it requires the application to resolve type information at run time. However, it does not need to be recompiled when the type library changes.

Typically, early binding is recommended since it gives you a speed advantage and prevents applications from breaking when you install newer, incompatible versions of the library.

Registering ActiveX Components

As with any Visual Basic application, the standard VB run-time DLLs (MSVBVM50.DLL, for instance) must already be installed on the target machine in order for the application to operate correctly. Once these are installed, users must register the oCGI2 library DLL before any application can create a new instance of an oCGI2 object. This is typically done by a setup routine or by using the REGSVR32 utility.

In some cases, direct access to the machine is problematic. Installing ActiveX components becomes a problem because no actual setup routine is in use, and there are no remote versions of REGSVR32.

For an application to register its own components (be its own setup program), you can use the technique in Listing Seven. Using this technique, the application accesses the library's registration routine (in this case, created by Visual Basic) and performs the registration process itself rather than depending on a setup routine to do it. The oCGI2.DLL should be in the same directory as the actual application or in the Windows system directory.

The application can register the library by calling the library's DLLRegisterServer function before actually creating an instance of the oCGI2 object. This technique can be used by Visual Basic applications to register any OCX or ActiveX library, assuming that the machine has all of the necessary components (DLLs and maybe even other OCXes) required by the control.

A CGI Application that Generates Images

Listing Eight is a sample CGI application built using the oCGI2 library that sends an HTML page and a corresponding JPEG image. When called with no parameters, the application shows a "Hello World" HTML message and then references itself with the parameter type=jpeg. When it receives this parameter, the application displays the helloworld.jpg file. Figure 2 is the application's output.

This type of application can be useful in displaying different images for different users. Another use is to provide a pay-per-use image facility that only shows the images users have paid for.

Banner servers use this technique to monitor in an intelligent manner the advertising banners that users have actually seen. The application can use cookies and retain the user's information across sessions. (Some browsers prevent this from occurring by simply banning cookies originating in non-HTML sources.) The site can then make sure that a banner that the user has already seen will not be viewed again. Alternatively, the site can simply use a randomize function and display one image from a host of possible banners.

The new HTTP 1.1 specification extends the ability to send non-HTML content by allowing CGI applications to create a mixed content output containing graphics, sounds, and HTML. This allows the application to send the entire content of a web page in one sweep, rather than requiring a request for each individual element as described earlier. (This is one reason why HTTP 1.1 is more efficient than HTTP 1.0.)

Conclusion

Writing CGI applications with Visual Basic is useful when a significant investment in Visual Basic has already been put into place or when VB's features are ideal for the task at hand. Visual Basic's strong capabilities in string processing and database access make it an ideal candidate as a CGI development tool. Additionally, the strong component support for Visual Basic allows an application developer to create applications that can easily integrate with any environment (credit-card systems, host systems, networking environments, and more). This makes VB a great tool for CGI application development.

DDJ

Listing One

Private Function ReadPOST() As String    Static sText As String
    If (sText <> "") Then
        ReadPOST = sText
        Exit Function
    End If
    Dim lWritten As Long, lReserved As Long
    Dim hConsole As Long
    
    sText = Space(MaxPost)
    lReserved = 0
    If (hConsole = 0) Then
        hConsole = GetStdHandle(STD_INPUT_HANDLE)
    End If
    ReadFile hConsole, sText, Len(sText) - 1, lWritten, lReserved
    sText = Trim$(sText)
    sText = Left$(sText, Len(sText) - 1) ' kill \0
    ReadPOST = sText
End Function


</p>


</p>

Back to Article

Listing Two

Public Function ReadParam(sName As String) As StringOn Error GoTo EndReadParam ' parameter that hasn't shown
    ReadParam = ""
    ReadParam = cParameters(sName).Value
EndReadParam:
    Exit Function
End Function


</p>


</p>

Back to Article

Listing Three

For Each param in cgi.cParameters.If param.Name Like "Quantity######" then
        ... do something ...
    End If
Next


</p>


</p>

Back to Article

Listing Four

Option ExplicitSub main()
    Dim cgi As New oCGI2
    
    cgi.WriteCGI "<HTML>"
    cgi.WriteCGI "<H1> HELLO WORLD! </H1>"
    cgi.WriteCGI "</HTML>"
End Sub


</p>


</p>

Back to Article

Listing Five

Option ExplicitSub main()
    Dim cgi As New oCGI2
    cgi.ProcessCGI Command
    
    Dim sHTML As String
    sHTML = "<HTML>"
    sHTML = sHTML + "<H1> HELLO " + cgi.ReadParam("Name")
    sHTML = sHTML + " WORLD! </H1>"
    sHTML = sHTML + "</HTML>"
    
    cgi.WriteCGI sHTML
End Sub


</p>


</p>

Back to Article

Listing Six

Option ExplicitSub main()
    Dim cgi As oCGI2
    Set cgi = New oCGI2
    
    cgi.ProcessCGI Command
    cgi.WriteCookie "UserName", cgi.ReadParam("UserName"), _
        cgi.ReadServerName, cgi.ReadScriptName
        
    cgi.WriteCGI "<HTML>"
    cgi.WriteCGI "The cookie has been set! <BR>"
    cgi.WriteCGI "</HTML>"
End Sub


</p>


</p>

Back to Article

Listing Seven

Declare Function oCGI2Register Lib "oCGI2.dll" Alias _          "DllRegisterServer" () As Long


</p>
Sub Main()
    On Error GoTo NormalErr
    oCGI2Register


</p>
    Dim cgi As New oCGI2
    cgi.ProcessCGI Command


</p>
  ... REST OF APPLICATION HERE ...


</p>
    End
NormalErr:
    Beep
    End
End Sub


</p>


</p>

Back to Article

Listing Eight

Option ExplicitDeclare Function oCGI2Register Lib "oCGI2.dll" Alias _
      "DllRegisterServer" () As Long
Dim cgi As New oCGI2
Sub HelloWorld()
  Select Case (cgi.ReadParam("type"))
    Case "jpeg"
       Open App.Path + "\helloworld.jpg" For Binary Access Read As #1
       PipeMIME "image/jpeg", 1
       Close #1
    Case ""
        cgi.WriteCGI "<HTML><HEAD>"
        cgi.WriteCGI "<TITLE>Hello World </TITLE></HEAD>"
        cgi.WriteCGI "<BODY BGCOLOR=#aabbcc><CENTER>"
        cgi.WriteCGI "<H1>Hello World!</H1>"
        cgi.WriteCGI "<IMG SRC=""" + cgi.ReadScriptName
        cgi.WriteCGI "?type=jpeg"">"
        cgi.WriteCGI "</CENTER></BODY></HTML>"
  End Select
End Sub


</p>
Sub PipeMIME(sMime As String, iFile As Integer)
    cgi.WriteCGI "Content-type: " + sMime, False
    cgi.WriteCGI vbCrLf, False
    cgi.WriteCGI vbCrLf, False
    Dim sOut As String
    On Error GoTo FILEEOF
    Do While Not EOF(1)
        sOut = Input(1, iFile)
        cgi.WriteCGI sOut, False
    Loop
FILEEOF:
    Exit Sub
End Sub


</p>
Sub Main()
    On Error GoTo NormalErr


</p>
    cgi.ProcessCGI Command
    HelloWorld
    End
NormalErr:
    Beep
    oCGI2Register
    End
End Sub


</p>

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.