INTERNET DEVELOPMENT


An ISAPI Web-Server Extension SMTP Gateway

Keith Stevens

Perl scripts are quick to write but slow to execute. A little well placed C++ code can really speed Web responses.


Introduction

This article describes a CGI (Common Gateway Interface) project that began as a Perl script and developed into a C++ ISAPI (Internet Server API) web-server extension. CGI provides a way to do custom processing at the web server of user inputs from a web browser. When you click the submit button of an on-line form, the form data is sent to the web server, which spawns a CGI process. The data is passed from the web server to the CGI process either through a pipe or as a command-line argument.

Perl (Practical Extraction and Report Language), an interpreted script language, is commonly the tool of choice for CGI because of its text processing capabilities and its support for "quick and dirty" development and maintenance. Given the requirement of e-mailing the contents of an online resume to multiple job recruiters, the original Perl script performed the following functions:

1. Read the data from standard input:

&ReadParse(*values);

2. Parse the data to discover which job recruiters to e-mail:

if( $values{"JBostic"} =~ /./ ){
$RECIPIENT = $RECIPIENT . ",JBostic\@sbphillips.com";
}

3. Spawn postmail (a command-line SMTP e-mail client) with a pipe:

open( OUT,"|Postmail.exe -Hemail.acsinc.net -t") ||
    print "COULDNT OPEN THE MAILER: ";

4. Pipe the data to postmail (which sends the data as email to the email server):

print OUT "To: $RECIPIENT \n";
print OUT "From: gateway\@acsinc.net\n";
print OUT "Subject: SB Phillips form data\n";

5. Echo a message back to the web browser by writing to standard output:

print "<h1>Thank you, ",
    $values{"Firstname"}," ",
    $values{"Lastname"},
    "</h1>";
print "Your form has been submitted
    to:\n";

When the web server software was "upgraded," the spawn and pipe functions in Perl scripts no longer worked. Since the new web server is an ISAPI server, I decided to port the broken Perl script to a C++ ISAPI server extension. Then, discovering that the spawn and pipe functions did not work from the ISAPI extension either, I decided to send the data directly to the e-mail server using SMTP, Simple Mail Transfer Protocol. (See the sidebar on SMTP.)

ISAPI

ISAPI web server extensions possess advantages over standard CGI programs. The significant overhead of spawning a new process is eliminated because the ISAPI extension (a Dynamic Linked Library) becomes part of the web server. The result is faster and more efficient than standard CGI solutions. Data from a form is parsed using a parse mapping mechanism, converted, and passed as arguments to a function designed to process the form data. A single ISAPI extension DLL may contain multiple functions, each designed to process a different form or provide other functionality such as password authentication. There are also ISAPI web-server filters, a type of web-server extension that can be set to receive notification when selected events occur, including reading raw data from the client.

Microsoft Visual C++ has extensive online documentation covering the ISAPI, the Microsoft Foundation Class Library (MFC), and an Application Wizard that generates the files for an ISAPI application. MFC is a very useful C++ "application framework" that shortens development time by providing a wealth of extendable classes for a variety of applications. The following MFC classes were used (see Listings 1 and 2) :

CHttpServer
CHttpServerContext
CSocket
CSocketFile
CArchive
CString

CHttpServer wraps the ISAPI functionality and CHttpServerContext provides the means for communicating with the client or server. CHttpServer has two member functions, GetExtensionVersion and HttpExtensionProc. The other member functions are user defined for custom handling of client requests.

When the server loads the extension, it calls the GetExtensionVersion member function which returns the version number of the ISAPI specification for the extension. For each client request, the HttpExtensionProc member function is called. HttpExtensionProc reads client data and decides what action to take.

You can override this member function to customize the implementation. While only one CHttpServer object may exist per module, a new CHttpServerContext object is created and runs in its own thread for each call to the server. This arrangement facilitates the handling of multiple simultaneous calls from different clients to the server object. CSocket encapsulates the Windows Sockets API and works with classes CSocketFile and CArchive to manage communications. This saves an enormous amount of coding.

CString handles dynamic string buffering and concatenation.

Parse Maps

A single parse map is created for each CHttpServer object. The parse map definition is delimited by macros BEGIN_PARSE_MAP and END_PARSE_MAP. The other three macros — ON_PARSE_COMMAND, ON_PARSE_COMMAND_PARAMS, and DEFAULT_PARSE_COMMAND — are used to associate commands with member functions and their arguments. The command, which is also the name of the member function that processes the form, is contained in an HTML form tag as part of the ACTION value. For example, to execute a command named DOIT that is part of an ISAPI extension named EXT2.DLL, you would embed the following tag in the HTML document:

     
<FORM ACTION="/SCRIPTS/EXT2.DLL?DOIT">

The ON_PARSE_COMMAND macro is used to map the types of the form data to the argument types for the member functions. ON_PARSE_COMMAND expects as arguments the member function name and the CHttpServer derived class name, followed by a list of types that the member function expects. There must be at least one of the following possible types:

Symbol            Type or Comment
ITS_EMPTY         if no arguments
ITS_PSTR          pointer to a string
ITS_I2            short
ITS_I4            long
ITS_R4            float
ITS_R8            double

The ON_PARSE_COMMAND_PARAMS macro must immediately follow its associated ON_PARSE_COMMAND macro. It takes a single string argument used to associate each field name in the form with a data type in the ON_PARSE_COMMAND macro.

Conclusion

Debugging this type of program can be a little sticky. One trick I use is to simply begin testing early while the code is still very small. I get it working and then continue testing as each layer of functionality is added. Make sure that web server logging is enabled, as it is frequently useful to check the log when something goes wrong. I also write messages to the client browser at various points in my code during development. There are also a few tools and a lot of useful information available on the web for ISAPI programming. One favorite is the ISAPI Developers Site at:

http://rampages.onramp.net/~steveg/isapi.html

Although at first I hated not being able to use Perl, I'm very pleased with the performance of the ISAPI C++ version. I was able to remove a note at the bottom of the old form that said "Please wait, processing the form may take a minute." Now it takes a second or two and uses very few resources. I removed several of the form fields to make the example code more readable. But you can see the actual project results at:

http://www.sbphillips.com/resume.htm
o

Keith Stevens is currently a LAN/WAN administrator and webmaster for ACSinc.NET ACS/Advanced Computer Systems in Greenville, S.C. He has over ten years' experience as a professional C/C++ programmer. He may be reached at keith@acsinc.net or at www.acsinc.net.