This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


May 1996

Microsoft Systems Journal Homepage

Writing Interactive Web Apps is a Piece of Cake with the New ISAPI Classes in MFC 4.1

Mike Blaszczak

Mike Blaszczak is a software design engineer in Microsoft's Developer Studio Business Unit working on MFC. He is currently updating his book, The Revolutionary Guide to Win32 Programming with the Microsoft Foundation Classes (Wrox Press). He can be reached at mikeblas@msn.com or 76360.157@compuserve.com.

Click to open or copy the MFCTALK project files.

Click to open or copy the OLDBROW project files.

Many Web pages today provide interactive forms users can fill out to access databases, get information dynamically, request mail, order products, or just run another program on the server. Let's explore ways to make your Web pages contain something more interesting than static pictures. I'll show you how the new WindowsÒ ISAPI architecture and MFC 4.1 make writing interactive web apps a piece of cake.

The common language of the Web is HTML. Web browsers all support some form of HTML, while the underlying communication software uses TCP/IP-based sockets to accept requests and return responses using Hypertext Transfer Protocol (HTTP). So whether you want to write an interactive sports page that lets users request the latest hockey scores by typing in the names of their favorite teams, or you simply want to run another program without any user interface, all you have to work with is HTML. All clients do is request HTML documents and all servers do is ship them back. To create an interactive Web page, you have to be creative.

Several methods already exist for writing these Web extensions. Traditionally, developers have used a protocol calledCommon Gateway Interface (CGI). In CGI, theserver uses environment variables to communicate with the extension. The extension dynamically generates HTML documents, which the server pumps back to the client. Most UNIX Web servers support some form of CGI. Some CGI extensions are written in C, but most are written in a language called Perl. Since Perl can access environment variables easily, talks naturally with standard input and output, and supports simple control structures, it's easier to use than C, but it's slower. The main advantage of Perl scripts is that they run on any platform that has a Perl interpreter.

A Better Way: ISAPI

One problem with CGI is that the server must spawn a separate copy of the extension program for every user who uses it. Spawning an application, tracking it while it runs, and terminating it cleanly incurs considerable expense. This expense adds up quickly on busy machines that serve dozens or even hundreds of connections. If you're running a Web server on Windows NTÔ, there is a much better way. Instead of launching a new process for every user, you can use threads, which are much more lightweight. By writing your extension as a dynamic-link library (DLL) instead of an executable program (EXE), the server can call entry points within your library from threads the server owns. Since the server uses multiple threads to service I/O requests from different clients, no extra overhead is incurred by running your extension code inside the same process as the server. This approach is faster than the old spawn-a-process technique thought up by UNIX hackers.

This architecture-using a DLL to implement Web server extensions-was jointly proposed by Progress Software Corporation and Microsoft. To make it rhyme with technologies like MAPI and TAPI, the architecture was named ISAPI, which stands for Internet Server API. Microsoft adopted ISAPI in its Windows NT Server-based Internet Information Server (the software formerly known as Gibraltar). Internet Information Server provides Web, FTP, and Gopher publishers, all implemented as secure, high-performance Win32Ò services.

Release 4.1 of the Microsoft Foundation Classes, which will ship with Visual C++ 4.1 (you'll need to order the Visual C++ subscription to get it), includes a handful of new classes that make writing ISAPI extensions as easy as overriding a few virtual functions. The new development environment even has a custom AppWizard that produces ISAPI extension projects that use the new classes. Using ISAPI and MFC, you can write two types of server extensions: filters and server applications.

Filters

Filters aren't as visually glamorous as server applications, but they're surprisingly powerful. As the name suggests, a filter sits between the client and server apps and modifies data as it goes by (see Figure 1). An ISAPI filter is a DLL that provides two special entries: GetFilterVersion and HttpFilterProc.

Figure 1 Where a Filter Sits

To install a new filter, you must bring the server down to doctor some registry entries, then restart the server. As it starts up, the server loads your filter DLL and calls GetFilterVersion with a pointer to an HTTP_FILTER_VERSION structure.

 BOOL GetFilterVersion( HTTP_FILTER_VERSION * pVer ); 
// entry point in your DLL

// defined in HTTPFILT.H
struct HTTP_FILTER_VERSION {
    DWORD  dwServerFilterVersion;
    DWORD  dwFilterVersion;
    CHAR      lpszFilterDesc[SF_MAX_FILTER_DESC_LEN];
    DWORD  dwFlags;
};

It's your job to fill this structure with information about your filter. dwFilterVersion tells the server what level of the ISAPI specification you use. dwServerFilterVersion is the corresponding number for the server (which the server fills in before calling your GetFilterVersion function). By comparing dwServerFilterVersion and dwFilterVersion, servers and filters of different vintages can coexist peacefully. You can identify your filter by copying a string into the lpszFilterDesc. The server never shows this string to the client, but only to various server-side administration tools. The most important item in HTTP_FILTER_VERSION is dwFlags. By setting the appropriate flags, you tell the server exactly what events you are interested in. Figure 2 shows the various flags/events.

You can view the work a filter does along two lines: filtering data and processing requests. When the server sends or receives data from the client, your filter gets a chance to process the data. Normally, the server fulfills user requests by retrieving the appropriate Web page or executing a script and then sending the HTML back to the client. If you specify SF_NOTIFY_SEND_RAW_DATA, the server will call your filter with a buffer containing the return data-before the data is actually sent, but after the server has completely retrieved it and processed it. Likewise, if you specify SF_NOTIFY_READ_RAW_DATA, then as soon as the server receives new information from a client, it passes the information to your filter before doing any processing of its own. Your filter can translate, massage, or completely mutilate the data to your heart's content, before the server ever sees it.

How does the server "notify" your DLL with new data? By calling the other entry point, HttpFilterProc.

 DWORD HttpFilterProc(
   HTTP_FILTER_CONTEXT * pfc,
   DWORD NotificationType,
   VOID * pvNotification);

The filter proc is similar to an ordinary window proc. The server calls HttpFilterProc whenever something interesting happens. pfc is a pointer to an HTTP_FILTER_CONTEXT structure (see Figure 3) that contains information about the connection, such as whether it has been authenticated or not. The context provides a generic VOID pointer, pFilterContext, that you can use to hold your own connection-specific information. It also holds several useful server-supplied callback functions, some of which I'll describe shortly. NotificationType is a code such as SF_NOTIFY_SEND_RAW_DATA that identifies the event, and pvNotification is a pointer to event-specific information, like LPARAM for a window procedure (see Figure 2).

Data filters are immensely powerful because you can alter the data any way you like. You might compress it, expand it, encrypt it, decrypt it, or map it all to lowercase. The main drawback is that raw data notifications are rather expensive, since the server must notify you frequently with great wads of data.

The READ/SEND_RAW_DATA notifications include only the actual content data sent to and from the server, not the HTTP header information that wraps it. The HTTP headers describe the format, age, last modification date, size, and language of the content data, among other things. There are times when you may want to examine this information. For example, you might want to look at the "Referer:" field in client requests so you can see how users are finding out about your server, or you might want to change something in the reply header before it's sent to the client. Either way, to see header information, you must specify SF_NOTIFY_PREPROC_HEADERS in your version flags. Another common use for SF_NOTIFY_PREPROC_HEADERS is to implement "cookies." Cookies are HTML extensions that allow clients to hold persistent data for servers, such as context information between two parts of a search or merchandise order. Cookies aren't part of the HTML standard, but most browsers can handle them.

Another useful filter event is SF_NOTIFY_URL_MAP. Clients download pages from your Web server by requesting a specific universal resource locator (URL). The URL identifies the type of resource (which is always "http:" for web servers) and its location: server name, directory path, and filename. For example, if you work at Microsoft, you might find a file loaded with hockey statistics for a team in New England on a server named MOOSEBOY using this URL:

 http://mooseboy/hockey/whalers/team_stats.html

When the server receives this request, it notifies any filters registered with the SF_NOTIFY_URL_MAP flag. Your filter can alter the URL if it likes; this is a great way to make your site respond as if it implements virtual directories. If you use SF_NOTIFY_URL_MAP to change the logical look of your Web site, you might also want to use SF_NOTIFY_PREPROC_HEADERS to fiddle with the headers sent back from your server to the client, since the HTTP header may itself contain URLs and other information about the page returned, and many browsers rely on this information to make decisions about how to process the page once it's returned to the client.

One of the most interesting notifications is SF_NOTIFY_AUTHENTICATION. The server sends this notification when a client browser tries to authenticate its connection. If you want to perform your own authentication-for example, by looking up the user's account in a database to make sure he has prepaid for this session-you can do so by handling SF_NOTIFY_AUTHENTICATION.

SF_NOTIFY_END_OF_NET_SESSION isn't very interesting, but it's important. The server uses it to notify your DLL when a connection is about to end. This is your chance to clean up any per-connection state information you may have allocated. If you don't find it amusing to write your own clean-up code in response to SF_NOTIFY_END_OF_NET_SESSION, you can use the HTTP_FILTER_CONTEXT::AllocMem callback to allocate all your per-connection storage. The server will automatically free it when the connection terminates.

Two other flags, SF_NOTIFY_SECURE_PORT and SF_NOTIFY_NONSECURE_PORT, tell the server you are interested in connections involving either secured or non-secured ports. If you're writing a filter that performs authentication, you'll want to talk with non-secured ports only. If you're writing a filter that performs encryption, you might want it to run only on a secured port that's already been authenticated. If your filter simply maps resource strings or watches raw data go by, it's up to you what ports you watch. You can watch both non-secured and secured ports from the same filter by using both flags. SF_NOTIFY_SECURE_PORT and SF_NOTIFY_NONSECURE_PORT are different from the other flags in that they don't correspond to any particular events; there is no "SF_NOTIFY_SECURE_PORT" event.

If you are able to initialize your filter properly, you should return a non-zero value from GetFilterVersion. If you have registered your filter correctly, the server will load it whenever the server starts up, and begins notifying you of whatever events you requested in your version flags. If several filters are registered for the same notifications, the server is allowed to decide which it will notify first. Microsoft's server uses the same order as the registry entries listing the DLLs.

If you're using MFC to write a filter, you don't have to write GetFilterVersion and HttpFilterProc. Instead, you derive your own filter class from CHttpFilter and override the virtual function CHttpFilter::GetFilterVersion and whichever specific event handlers you are interested in (for example, CHttpFilter::OnReadRawData to translate incoming data or CHttpFilter::OnUrlMap to mess with the URL mapping). You then create a single static instance of your class, just like you create a single instance of your CWinApp-derived class, usually called the App. Internally, MFC provides its own ::GetFilterVersion and ::HttpFilterProc, which get linked with your code. The MFC versions route calls to the appropriate CHttpFilter virtual functions, just like MFC routes window messages to your handler functions like OnGetFocus or OnCreate. The major difference is that with CHttpFilter, you don't need message maps, you just implement the function. There is a virtual function CHttpFilter::HttpFilterProc you can override, but normally you shouldn't have to since the default implementation routes notifications to the appropriate event handler. MFC even converts the generic VOID* pvNotification argument into the appropriate type-safe event-specific structure pointer. Figure 4 shows the definition of CHttpFilter and Figure 2 shows which member functions correspond to which filter events.

OLDBROW

To put the discussion in the context of a real live working program, I wrote a simple filter, OLDBROW (see Figure 5), that you can use to exclude users from your Web server if they're not running a particular browser. Not very friendly, but it's the simplest example I could think of. COldBrowFilter overrides just two virtual functions: GetFilterVersion and OnUrlMap. GetFilterVersion is easy; it calls the base class implementation to set dwFilterVersion and then sets SF_NOTIFY_URL_MAP to tell the server I'm interested in URL mapping events. It also copies the resource string "OldBrow Filter" into lpszFilterDesc.

OnUrlMap is a little more complex, but not much. Most of the filter notification handlers receive a CHttpFilterContext* and a pointer to an event-specific structure like HTTP_FILTER_URL_MAP, which is specific to URL mappings. CHttpFilterContext is an MFC wrapper for HTTP_FILTER_CONTEXT that lets you invoke the HTTP_FILTER_CONTEXT callback functions using normal C++ notation. In OLDBROW, I use GetServerVariable to get the value of the HTTP_USER_AGENT variable. This is just the value of the "User-Agent" field in the HTTP request header. Browsers identify themselves to servers by sending their name and version information in the User-Agent header field of any request they make. For OLDBROW, if the User Agent is "Microsoft Internet Explorer" (as opposed to "Microsoft Internet Explorer 2.0"), I change whatever URL the browser requested to a hardwired value "c:\...\oldbrow.htm" which is an HTML page that basically says, "Sorry, Charlie" (see Figure 6). Otherwise the request goes through unaltered. Either way, OnUrlMap returns SF_STATUS_REQ_NEXT_NOTIFICATION to tell the server to pass the event along to the next filter, if any (see Figure 5).

Figure 6 Sorry, Charlie

While OLDBROW doesn't use it, WriteClient is easily the most interesting CHttpFilterContext member function. It writes data directly back to the client. You can use this function to send anything you'd like back to the client, be it HTML data or raw data in some other format you've announced to the client. You can use it in conjunction with AddResponseHeaders to formulate complete replies to any request. You should call AddResponseHeaders first, then WriteClient to actually send the data. If you're not using C++ and MFC, these functions are still available as callback members of HTTP_FILTER_CONTEXT.

As I mentioned at the outset, the major benefit of ISAPI is that it lets servers execute a Web extension from within multiple threads within its process space instead of launching a new process for every connection. One consequence of this is that your code must be thread-safe! The server may call your DLL's HttpFilterProc function (or virtual CHttpFilter::HttpFilterProc override) concurrently from many different threads on behalf of many ongoing client connections. In practice, all this means is that you must be careful not to use globals so your code will be reentrant. In particular, if you need to maintain per-connection information, you can't just store a pointer to the CHttpFilterContext object MFC sends you. Fortunately, HTTP_FILTER_CONTEXT contains a VOID* member, pFilterContext, that's designed for just this purpose. You can use it to store a pointer to your own connection-specific data. You can create the structure the first time you get a notification from a particular connection, then delete it in OnEndOfNetSession. (If you use CHttpFilterContext::AllocMem, the system will delete it for you.)

Aside from thread considerations, the whole trick to programming a filter is efficiency. You don't want to do anything that isn't absolutely necessary because time is of the essence. Wasting time in a filter is bad! It steals cycles from the server, so the server can't work as quickly, so users get bored staring at the hourglass. Don't forget that whatever inefficiencies you introduce will be multiplied as you service many clients concurrently.

Server Applications

I told you that there are two basic kinds of ISAPI server extensions: filters and server apps. We just finished filters; now it's time to turn our attention to server applications. ISAPI server applications are a great way to implement interactive Web apps. You can use server applications (also called extensions) to process forms, generate log files, or respond to user queries. Server applications are a lot like filters. They are just DLLs with two special entry points, GetExtensionVersion and HttpExtensionProc.

 BOOL    GetExtensionVersion( HSE_VERSION_INFO  *pVer );
DWORD HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB);

You can even implement a filter and an extension in the same DLL! (But at most only one of each.) Figure 7 shows the structures these functions use. As with HTTP_FILTER_CONTEXT, EXTENSION_CONTROL_BLOCK contains a number of useful callbacks like WriteClient and GetServerVariable. MFC wraps it all up the way you would expect. MFC provides its own DLL entry points; instead of writing GetExtensionVersion and HttpExtensionProc, you derive your own class from CHttpServer, override the appropriate virtual functions, and instantiate exactly one static instance. MFC also provides CHttpServerContext to wrap EXTENSION_CONTROL_BLOCK. (MFC uses more consistent naming conventions.) Figure 8 shows the definitions for CHttpServer and CHttpServerContext.

There are a lot of details about server applications I'm going to skip in order to focus on the really interesting thing, which is how clients interact with your server extension. As I mentioned at the outset, the key to having an interactive Web app is that you need some way to pass arguments to the server app, invoke its functions, and get a result back. To see how argument-passing works in practice with ISAPI, let's consider the MOOSEBOY server I mentioned earlier, the one that reports the latest hockey scores. A typical MOOSEBOY client begins by requesting a URL to get the score for the Hartford-Pittsburgh game:

 http://mooseboy/hockey/hockey_stats.dll?GameScore&HFD&PIT

You may be wondering what all that funny stuff at the end of the URL is? Don't get excited, it's just a convention for passing argument values. When the server processes this request, it understands that the portion of the URL preceding the ? identifies an executable file (DLL). The rest says: call the GameScore function with "HFD" and "PIT" as arguments.

If the user's connection allows executable privileges for the directory and file in question (hockey\hockey_stats.DLL), the server loads hockey_stats.dll and looks for an entry point named GetExtensionVersion. GetExtensionVersion accepts a pointer to an HSE_VERSION_INFO structure. The filter fills the structure and, assuming it initializes OK, is ready for action.

Unlike filters, which are loaded when the server first starts up, an extension isn't loaded until a client requests it. Once loaded, however, the server will keep the extension in memory as long as possible-which on a well-endowed server darn-near means forever. In particular, this means GetExtensionVersion is called only once.

HttpExtensionProc, on the other hand, is called each and every time a client asks (through the server) the extension to do some work. If six people looking for hockey scores connect to MOOSEBOY concurrently, hockey_stats.dll loads only once, but the server calls HttpExtensionProc six times, once for each connection. Like filters, your server application must be reentrant and thread-safe.

Once the DLL is loaded, the server calls your HttpExtensionProc, passing it the command string ("GameScore?HFD&PIT") inside the EXTENSION_CONTROL_BLOCK structure. If you're using C, it's up to you to parse the command and arguments and determine what function(s) within the DLL to invoke. In this case, you would invoke the GameScore function with "HFD" and "PIT" as arguments.

If you're using MFC, things are even easier. The default implementation for CHttpServer::HttpExtensionProc parses the command string for you and then looks for an appropriate entry in your parse command map. A parse command map is like the ordinary message maps we all know and love. Instead of writing a lot of ugly code to parse command strings and dispatch to functions in your extension DLL, all you do is write a handler for each operation you support, and then add an entry for it in the parse command map.

My hockey server has a parse map that looks something like this:

 BEGIN_PARSE_MAP(CHockeyServer, CHttpServer)
   ON_PARSE_COMMAND(GameScore, CHockeyServer, 
                    ITS_PSTR, ITS_PSTR, ITS_PSTR)
END_PARSE_MAP(CHockeyServer)

This map tells MFC I have a command "GameScore" that has three string arguments. GameScore is also the name of the member function-I'll have to run off and implement it to examine the parameters it receives and generate a reply. Usually, information is returned as HTML, but by tweaking the HTTP headers I could return an image or sound file instead. If the parameters the client sends to my GameScore function are bogus, I can return an error message like "Say what?" formatted as HTML. On the other hand, if the user requested a game actually played on a given date, I might return a JPEG image of the winning goal along with a link to a URL for the box scores.

The parse map entry for GameScore describes three string parameters (the three ITS_PSTR symbols), but the URL showed only two parameters: HFD and PIT. You can imagine that a puckhead like me might forget to specify the date if the game was today. In this case, the server should do the right thing. In other words, the Date parameter should be optional, with today's date as the default value. The parse map above makes all three strings mandatory, but I can alter it slightly to name the parameters and provide defaults.

 BEGIN_PARSE_MAP(CHockeyServer, CHttpServer)
   ON_PARSE_COMMAND(GameScore, CHockeyServer, 
                    ITS_PSTR, ITS_PSTR, ITS_PSTR)
   ON_PARSE_COMMAND_PARAMS("HomeTeam AwayTeam
                            Date='~'")
END_PARSE_MAP(CHockeyServer)

Now HomeTeam and AwayTeam are still required, but Date has a default value of tilde (~). When my GameScore function gets a date of "~", it substitutes the current date. I chose a tilde just because it's a nifty out-of-bounds character, and it has a funny name. With this new map, all of the following URLs would call my GameScore function with HomeTeam="HFD", AwayTeam="PIT", and Date="~".

 http://mooseboy/hockey/
       hockey_stats.dll?GameScore&HFD&PIT
http://mooseboy/hockey/
    hockey_stats.dll?GameScore&AwayTeam=PIT&HomeTeam=HFD

The following URLs request information about the Vancouver-Anaheim game on my birthday.

 http://mooseboy/hockey/
       hockey_stats.dll?GameScore&VAN&ANA&1/25/96
http://mooseboy/hockey/
       hockey_stats.dll?GameScore&Date=
       1/25/96&AwayTeam=ANA&HomeTeam=VAN

Since the parameters are named, their order doesn't matter. If I don't supply names, I must follow the order in which they appear in the parse map entry. Of course, my C++ member function CHockeyServer::GameScore always gets the parameters in the declared order.

 void CHockeyServer::GameScore(CHttpServerContext*pCtxt,  
LPCSTR pszHomeTeam,
LPCSTR pszAwayTeam,
LPCSTR psxDate);

The types of the parameters must match the types defined in ON_PARSE_COMMAND. MFC uses manifest constants like ITS_PSTR to identify the parameter types. Since the URLs aren't data-intensive, only a handful of types are supported (see Figure 9).

If the URL syntax seems cumbersome, it is. No one ever intended it to be used by humans. You don't just walk up to your browser and pound out a long URL with a bunch of variables and values at the end. In most cases, you use a nice user-friendly form. It's pretty easy to design an HTML form with dropdown lists to select teams and an edit box to enter the date. The form would have a button called "Send" or maybe something colorful like "Drop the puck!" to send the request to the server. When the user clicks Send, the browser constructs the nasty URL according to instructions contained in the form's HTML source code. Since the source code identifies the first dropdown as "HomeTeam" and the second dropdown as "AwayTeam", the browser generates a string with those parameters coded in the URL. Once the browser has done this, it ships the URL off to the server, which loads hockey_server.dll (if it isn't loaded already) and calls the HttpExtensionProc entry (supplied by MFC), which parses the arguments and grovels through your parse maps to find the right function to call-namely, GameScore. GameScore generates whatever kind of reply it wants-the score formatted as an HTML file, a video or audio clip, or whatever, and sends it back to the client. Amazing!

You should be able to see how general this is. The same basic mechanism lets clients invoke any function in your DLL, passing any number of string or numeric arguments.

A Dip in the Stream

The obvious goal of most extensions is to return information in the form of HTML text to the client, even if all it says is "Congratulations, your name is now on our eternal mailing list, we'll send you overpriced offers for things you don't want, starting now and until you die." But how exactly do you send the reply back?

CHttpServerContext has a member function named WriteClient that writes data back to the client. The problem with WriteClient is that it sends data immediately, which in most cases is not good because you need to assemble your response piecemeal and each call to WriteClient causes a network hit since the server does no buffering. To help relieve your network load, MFC provides a simple class, CHtmlStream, that does buffered output to the client. CHttpServerContext contains an initialized CHtmlStream object prepared exclusively for the call in progress. Your handler function (GameScore) can use CHttpServerContext's streaming operators to build the response incrementally before sending it off.

 void CHockeyServer::GameScore(CHttpServerContext*   
                              pCtxt, LPTSTR pstrHome,
                              LPTSTR pstrAway, 
                              LPTSTR pstrDate)
{
      StartContent(pCtxt);
      WriteTitle(pCtxt);
      *pCtxt << "Game on: " << pstrDate;
      *pCtxt << "\r\n";

      // get scores from database
      // into nHomeScore and nAwayScore
      *pCtxt << pstrHome << " score is " << nHomeScore;
      *pCtxt << " and " << pstrAway << " score is ";
      *pCtxt << nAwayScore;
      EndContent(pCtxt);
}

When GameScore returns, it gives control back to MFC. If there were no errors, MFC sends out the HTML header to indicate success and then dumps, all at once, the contents of the CHtmlStream owned by pCtxt. Note that GameScore calls StartContent, WriteTitle, and EndContent. These are virtual CHttpServer functions you can override. StartContent writes (to the stream) a simple HTTP header, then opens an HTML page; EndContent closes that page. WriteTitle grabs the title by calling yet another virtual CHttpServer function, GetTitle, and wraps it in the appropriate HTML tags. Most browsers show the title in the window caption (and in history and favorite site lists), so choose a title that's brief but unique and descriptive. (You can skip WriteTitle if your handler doesn't return HTML.) These functions work nicely if you're sending HTML, but if you want to return data in a different format-a JPEG picture or an AU sound file-you'll have to override StartContent to emit the appropriate HTTP header. Another alternative is to not call StartContent at all, but write the headers manually from your handler function.

This simplified hockey example only touches on the power and flexibility afforded by dynamically generated HTML. The possibilities are limitless. Anything you can implement in C++ can be plopped on a web site for everyone in the world to use. For now, the real bottleneck is HTML and HTTP.

MFCTALK

Just so you don't think I'm waving my hands with the hockey example, I wrote a sample Internet server application, MFCTALK, that implements a "chat room" (see Figures 10 and 11). Web clients can execute the server DLL with no parameters to get a form that tells them how to join the conversation, and also shows the last twenty comments in the stack. When the user presses a button, the form invokes a function that adds their comment and returns the current pool of comments from other users. To make MFCTALK more interesting, I implemented a function that generates a random comment from a pool of dead celebrities.

Figure 11 MFCTALK in action

MFCTALK implements several features worth noting. First, it overrides StartContent to emit an extra header line on every page the program produces. The content generated by MFCTALK is volatile: as soon as another user adds a comment, the previously generated pages become obsolete. This is something of a problem, since most Web browsers try to cache pages as a way of minimizing data transfer. That approach doesn't wear well with MFCTALK, so StartContent emits a header line

 Pragma: no-cache

that tells compliant browsers the page in question should not be cached. Since the page is never cached, the browser downloads it again each time the user requests it.

Second, MFCTALK provides a name to identify each participant in the chat. The user name comes from the IP address of the requester. I call CHttpServerContext::GetServerVariable("REMOTE_HOST") to get this name-the system asks the Windows Sockets API to retrieve the machine name for the IP address. This adds a nice touch to my program, but it's probably not a great idea for serious applications. The user could have arrived at the server through some route that masked their IP address-for example, they bounced through a couple of extra proxies or firewall servers, or they didn't use TCP/IP to log in. (In general, you shouldn't depend on TCP/IP or Winsock being available in your extension.) More importantly, the translation of an IP address to a machine name is an expensive operation. If you need to do this translation, you should do it on another machine away from your server, and do it offline. The way MFCTALK is written, it will certainly bring a powerful machine to its knees if a larger number of users connect.

Implementing Your ISAPI App

All of the classes I've talked about here are part of MFC 4.1 and declared in afxisapi.h, which you must include in your application source. You can use the Internet Extension Wizard to kick out a new project that implements minimal server and/or filter overrides. This new project type allows you to quickly and easily create ISAPI projects with MFC without the tedium of getting the project hooked up yourself. The first screen of the wizard (see Figure 12) asks for the essential information about your project-it allows you to specify whether or not you want a filter object or a server extension object, or both. By putting both a filter and a server in your DLL, you can save a few bytes of overhead on your server machine. It's particularly convenient if the extension uses filter functionality, or vice versa.

Figure 12 ISAPI Extension Wizard Sample One

If you request a filter, the wizard displays a second page (see Figure 13) that allows you to hook up overrides for the various notification functions automatically. This step is as straightforward as can be-just pick the notifications you'd like to receive and the wizard hooks up the appropriate flags in GetServerVersion and adds the corresponding overrides to the generated header file and implementation file!

Figure 13 ISAPI Extension Wizard Sample Two

The beauty of the MFC classes, though, lies in their flexibility. You can write a slim filter or server application module that doesn't use the MFC classes because the ISAPI classes live in a separate, statically-linkable library. On the other hand, if you're going to be pounding on databases or using OLE objects, you may want to use the support provided in the other MFC classes. You can bake the MFC ISAPI library into a module with the rest of the MFC code and write the coolest extension on the block in less time than it takes to download the Microsoft home page.

If C++ isn't your bailiwick, you can always use the ISAPI SDK directly. ISAPI is a public standard and is available in lots of places. Even if you're not using the Microsoft server, you can still use ISAPI and the MFC classes that support it, as long as your server platform is targeted by Visual C++.

End of Session

Even if you're not a Web-surfer type dude, someone in your organization can undoubtedly benefit from the information shared by a Web server extension. Forms provide a great way to collect information, and using a server extension to tuck the information away inside a database is a great way to store it. You can use other extensions to get that same information back out to clients who request it through the Web. By embracing Windows NT for your server platform, you'll have a scalable place for your server to grow. And by using MFC, you'll be able to grow quickly. Before you know it, the Web can become the most convenient way for your organization to share data-both internally and externally. As services offered by Web servers become more and more mature (as the result of enhancements to HTTP, for example), you'll find that applications you write for the Internet, or for your own organization's intranet, will be more robust and powerful.

What's New in Microsoft(r) Visual C++(r)4.1

Microsoft Visual C++ version 4.1 is a subscription update to Visual C++ version 4.0. The focus of Visual C++ 4.0 was delivering features that supported faster development through improved code reuse. In Visual C++ 4.1, these same features-custom AppWizards, OLE controls, and new MFC encapsulation-are used to deliver new Internet-oriented functionality that you can incorporate into new and existing applications rapidly.

The release includes new MFC and Wizard support for Microsoft's Internet Server API (ISAPI). Five new MFC classes are included for creating interactive Web applications using Microsoft Internet Information Server (see Figure A). These classes let you create dynamic Web-based applications by adding functionality to Internet servers and Web pages.

The ISAPI Extension Wizard makes it easy to create Internet server extensions and filters. Built using the custom AppWizard technology, the ISAPI Extension Wizard is available via the New Project Workspace dialog box.

Microsoft has teamed up with Template Graphics Software to support VRML with a custom AppWizard, an MFC extension, and an OLE control. Using these components you can create Internet applications capable of real-time, three-dimensional walk-throughs on the Web.

In addition, developers can access their favorite Web sites from within Developer Studio. Using the Web Favorites command on the Help menu, you can go to one of the preloaded Microsoft sites for Visual C++ developer support or to one of your own custom sites.

Several of the leading OLE control vendors have partnered with the Visual C++ team to deliver fully functional, fully redistributable controls as part of the Visual C++ 4.1 release. These OLE controls offer a wide range of reusable functionality that helps simplify application development (see Figure B).

The project management and build systems have been tuned to manage large projects better. Enhancements include improved efficiency in project load time, settings management, debugger start time, and overall responsiveness.

Visual C++ 4.1 also includes the Game Software Development Kit. This SDK provides the tools necessary to develop high-performance games for Windows 95. Visual C++ 4.1 also includes new support in the compiler backend and debugger to take advantage of the recently announced Intel MMX architecture.

Finally, Visual C++ 4.1 includes several new Tech Notes and many new MFC samples covering a wide range of topics, from real-time database access over the Web (WWWQUOTE) to creating an OLE document object (BINDSCRB) (see Figure C).

From the May 1996 issue of Microsoft Systems Journal.