You are on page 1of 9

DevCentral - Developing ISAPI Extensions With MFC: Part I

The original article for this PDF can be found on DevCentral. http://devcentral.iticentral.com

Developing ISAPI Extensions With MFC: Part I


ISAPI Basics

by Kevin Webb

Comment on this article

This is the first part in an introduction to the Internet Server API (ISAPI) used by some web servers for creating interactive web applications (Internet Server Applications, or ISAs). You need to have an understanding of Web/CGI development, MFC and Visual C++ v4.1 or later to fully understand this article.

ISAPI vs. CGI


The Common Gateway Interface (CGI) has long been the standard in web interactivty. CGI scripts allow people to write simple applications in any number of languages. These scripts run on the web server machine and produce output at the user's web browser. Input from the user is passed in through Environment Variables or Standard-In, the program does what ever it needs to do, and sends HTML back through Standard-Out. This simple design, combined with languages like Perl and TCL, made CGIs very easy to develop. There is one big disadvantage to CGIs: Performance. Although there are ways of making CGIs faster (for example, you write them in as compiled executable, instead of a Perl script), speed is still an issue. Each time a CGI is accessed via web, the CGI executable (or an interpreter in the case of a script) must create a new process for each request. For a high traffic site, that places a considerable burden on the server. When Microsoft started to work on their web server (MS Internet Information Server, or IIS) they realized that CGIs were a major problem for large production web servers. Enter ISAPI. Instead of using an executable, ISAPI uses DLLs. These DLLs are loaded into the memory space of the server. This technique greatly increases performance by keeping the code cached in memory instead of having to reload it for each request. The advantages of ISAPI over CGIs:
G G

Speed There is a considerable gain in performance. Features ISAPI allows for creation of server filters (for pre/post processing). And it's fully integrated with MFC.

The disadvantages of ISAPI compared to CGIs:


G G

Standardization Only a few servers currently support ISAPI. Ease of Development Documentation is very sparse, and debugging is somewhat tedious.

The basics of ISAPI


ISAs are based on the MFC CHttpServer class. This class controls all interaction with the server, and contains all the functions for client requests. Only one instance of CHttpServer can exist for each ISA, however an ISA can handle any number of simultaneous request. So

http://devcentral.iticentral.com/articles/MFC/isapi/part1/default.php (1 of 5) [7/9/2001 2:52:03 PM]

DevCentral - Developing ISAPI Extensions With MFC: Part I


CHttpServer creates a CHttpServerContext for each request. CHttpServerContext contains all data specific to the request and all HTML to be returned by the ISA.

ISAPI DLLs are called by the client in the same manner as a CGI using ether GET or POST methods. For example, the following request is made: http://www.mysite.com/myisa.dll?name=bob&id=15248 The "name" and "id" fields and their associated data are passed into the ISA and the data must be placed in data structures before it can be used. To facilitate this, ISAPI uses a request mapping system. Each request that can be made has a parse map. The parse map directs the data to an appropriate data structure by defining the type and order in which the data will be received form the client. For example, with the "name=bob&id=15248" request, the map would show a string and an int, and "bob" and "15248" would be parsed out and placed in their respective data structures. The parse map system also has another function. ISAPI can direct requests to specific member functions within the ISA. The request string can contain a command that the parse map uses to direct the request to the proper member function (or Command Handler in ISAPI lingo) of the ISA.

Because ISAPI uses a command driven approach to handling requests, ISA development may seem a little awkward at first. But once learned, this can a very powerful way of handling requests.

Setting Up The Project


The first step in developing an ISA is building a project workspace. As with other projects built with Visual C++ (VC++), there are wizards that guide you through the initial steps. Select File-New and select the Projects tab. Choose "ISAPI Extension Wizard" as the project type, name it "Hello Web", and press OK. This will bring up a dialog asking what type of ISA you would like to build. The default settings are already configured for an ISA, however MFC will be dynamically linked by default. This is acceptable if your development server has all the MFC DLLs installed (if you don't have Developer Studio installed on the server, they are most likely not there), however if they aren't available then your ISA will not run. In this case the project needs to be statically linked. With that said, everything should be ready, so press "Finish" button. Visual C++ will then inform you about the files it is creating, and that it is generating a class called "CHelloWebExtension". This class is a descendant of CHttpServer (the base class for all ISAs). All the work in this tutorial will be done in the "CHelloWebExtension" class. Now that you have the project built, it's time to deal with a few intricacies of ISAPI development. As mentioned earlier an ISA becomes part of the IIS when it runs. And IIS is in turn running as an NT Service. This fact complicates the debugging processes, because the VC++ debugger can not take control of the of ISA while the server is a service. To solve this problem Microsoft shipped IIS in two forms, as service, and as a stand-alone executable. With the executable, the server can be controlled from the command-line. Although this solves the problem and makes

http://devcentral.iticentral.com/articles/MFC/isapi/part1/default.php (2 of 5) [7/9/2001 2:52:03 PM]

DevCentral - Developing ISAPI Extensions With MFC: Part I


development much easier, setting this arrangement up is a bit tedious. When you are in a debug session, VC++ (and in turn IIS) will run under your account with your permissions. However, IIS needs to do things that most users' permissions don't allow, so you (or your administrator) need do the following:

1. 2. 3. 4. 5. 6.

Open the User Manager for Domains utility (found in Administrative Tools program group). Select the User Rights from the Policies menu. Check the Show Advanced User Rights box. Select Act as part of the operating system from the Right list. Click the Add button to get the Add Users and Groups dialog. Click the Show Users button, and select the account you will be using. Click Add. Do the same for the Generate security audits rights.

For these changes to take effect you must log out and log back in. IIS is a package of three Services: FTP Publishing Service, Gopher Publishing Service, and World Wide Web Publishing Service. Since the debugger will be running IIS from the command line, all three of these services must be stopped. This can be done from the Services Control Applet, or from the Internet Service Manager application. If you will be doing a lot of debugging, it is recommended that you turn the IIS service off using the Services Control Applet and disable the automatic restart feature (this will prevent you from having to turn the service off each time the computer is restarted). Once the service is off, the project workspace must be configured by doing the following:

1. 2. 3. 4. 5. 6.

Select Settings from the Build menu. Click on the Debug tab and select the "General Category". Type the location of the IIS executable (it would be c:\inetsrv\server\inetinfo.exe by default installation) in the "Executable for debug session" field. Type "-e w3svc" in the "Program arguments" field. Click on the Link tab. Type the path and filename to where you want the compiled DLL to be placed into the "Output filename" field. This path should be somewhere inside the website directory tree so it can be accessed by a URL. For example, if your website root directory is c:\www\ and you placed "helloweb.dll" in the root, it's URL would be: http://www.mysite.com/helloweb.dll

If you have not logged off since changing your privileges, do so now. Then log back on. The default source generated by ISAPI Extension Wizard contains everything necessary to compile a working ISA (it won't do anything, but it will compile). Now that you have finished configuring the debugging environment, you are ready to build and run the project. Press F5 to start the ISA and press yes when asked if you want to build the project. A few seconds after the debugger is up, IIS should be running in the background. Now that everything is ready, enter the URL of DLL into you favorite web browser, but add a question mark to the end. The URL should look similar to this: http://www.mysite.com/helloweb.dll? Connecting to the ISA for the first time should take several seconds. However, DLLs are cached after their first execution and that speeds things up considerably. After the DLL loads, the following message should appear:

This default message was produced by the Internet Server DLL Wizard. Edit your CHelloWebExtension::Default() implementation to change it.

You now have a working ISA.

Walking through the base code


There are two main components to an ISA: The Parse Map and the Command Handler functions. When a request from the EXTENSION_CONTROL_BLOCK (the structure used for commuication between the server and the ISA) is received, it is passed to the Command Parse Map. The parse map is defined by a set of macros, like the code snippet below, copied from the Hello Web

http://devcentral.iticentral.com/articles/MFC/isapi/part1/default.php (3 of 5) [7/9/2001 2:52:03 PM]

DevCentral - Developing ISAPI Extensions With MFC: Part I


project (HELLOWEB.CPP):

BEGIN_PARSE_MAP(CHelloWebExtension, CHttpServer) // TODO: insert your ON_PARSE_COMMAND() and // ON_PARSE_COMMAND_PARAMS() here to hook up your commands. // For example: ON_PARSE_COMMAND(Default, CHelloWebExtension, ITS_EMPTY) DEFAULT_PARSE_COMMAND(Default, CHelloWebExtension) END_PARSE_MAP(CHelloWebExtension)

The BEGIN_PARSE_MAP marks the beginning of the parse map. It takes the name of the ISA's CHttpServer and base class as parameters. The ON_PARSE_COMMAND macro maps a specific request format or command to a Command Handler function. It's parameters are the name of the function that the request should be directed to, the function's class, and the format of the request. The DEFAULT_PARSE_COMMAND specifies which function should be called if the request is empty or does not match any other parse map. It's parameters are the name of the function to be called and the function's class. The Command Handler functions are member functions of the main CHttpServer class that get called by the parse map. Below is the "Default" command handler for Hello Web:

void CHelloWebExtension::Default(CHttpServerContext* pCtxt) { StartContent(pCtxt); WriteTitle(pCtxt); *pCtxt << _T("This default message was produced by the Internet"); *pCtxt << _T("Server DLL Wizard. Edit your CHelloWebExtension::Default()"); *pCtxt << _T("implementation to change it.\r\n"); EndContent(pCtxt); }

When the request is empty or contains "Default", this fuction is called. On entry, it's passed the CHttpServerContext for the request (the first parameter of a command handler must be a CHttpServerContext). StartContent() places <HTML><BODY> in pCtxt, and WriteTitle() places the <TITLE> tags. The next three lines write the default message to pCtxt which points to a CHtmlStream. When the ISA is finished, the html stream buffer is sent to the client.

Hello Web
The first program will replace the default message string with "Hello Web!". Go the Default() member function of the CHelloWebExtension class and make the changes below:

void CHelloWebExtension::Default(CHttpServerContext* pCtxt) { StartContent(pCtxt); WriteTitle(pCtxt); *pCtxt << _T("Hello Web!"); EndContent(pCtxt); }

Build and run the DLL. Reload the DLL from the web browser and instead of:

This default message was produced by the InternetServer DLL Wizard. Edit your CHelloWebExtension::Default() implementation to change it.

you should see:

http://devcentral.iticentral.com/articles/MFC/isapi/part1/default.php (4 of 5) [7/9/2001 2:52:03 PM]

DevCentral - Developing ISAPI Extensions With MFC: Part I

Hello Web!

However, if you see "Server Error 500: Specified module not found.", your project is dynamically linked and the necessary DLLs are not available. To correct this, re-link the project statically with MFC (Go to Settings in the build menu and select the General tab and change the selection in the MFC list box to "Use MFC as a Static Library" and re-link the project). Developed Under: Windows NT 4.0 Visual C++ 4.1 IIS 3.0

2001 Interface Technologies, Inc. All Rights Reserved Questions or Comments? devcentral@iticentral.com PRIVACY POLICY

http://devcentral.iticentral.com/articles/MFC/isapi/part1/default.php (5 of 5) [7/9/2001 2:52:03 PM]

DevCentral - Developing ISAPI Extensions With MFC: Part II

The original article for this PDF can be found on DevCentral. http://devcentral.iticentral.com

Developing ISAPI Extensions With MFC: Part II


Understanding Parse Maps

by Kevin Webb

Comment on this article

This is the second part in an introduction to the Internet Server API (ISAPI) used by some web servers for creating interactive web applications (Internet Server Applications, or ISAs). You need to have an understanding of Web/CGI development, MFC and Visual C++ v4.1 or later to fully understand this article. Download the project files
G G

[ Download the Hello Web project ] [ Download the SimpleCalc project ]

Understanding Parse Maps


There are five macros used in parse maps:

BEGIN_PARSE_MAP ON_PARSE_COMMAND ON_PARSE_COMMAND_PARAMS DEFAULT_PARSE_COMMAND END_PARSE_MAP

// // // // // //

Starts the definition of a parse map Parses the client's command Maps data from request to their respective data structures Defines the default command Ends the definition of a parse map

Take another look at the Hello Web parse map:

BEGIN_PARSE_MAP(CHelloWebExtension, CHttpServer) // TODO: insert your ON_PARSE_COMMAND()and // ON_PARSE_COMMAND_PARAMS() here to hook up your commands. // For example: ON_PARSE_COMMAND(Default, CHelloWebExtension, ITS_EMPTY) DEFAULT_PARSE_COMMAND(Default, CHelloWebExtension) END_PARSE_MAP(CHelloWebExtension)

This map defines two commands: an empty request and the "Default" command. An empty request, handled by DEFAULT_PARSE_COMMAND, would look like:

http://devcentral.iticentral.com/articles/MFC/isapi/part2/default.php (1 of 4) [7/9/2001 2:52:05 PM]

DevCentral - Developing ISAPI Extensions With MFC: Part II


http://www.mysite.com/helloweb.dll? However if the "Default" command is placed after the question mark, as shown below: http://www.mysite.com/helloweb.dll?Default It is handled by ON_PARSE_COMMAND. The first parameter defines the name of the command, and is also the name of the function that handles the command. The second parameter is the name of the function's class. The third parameter is for parsing any data associated with the command. With Hello Web no data was sent other than the command, so it is set to ITS_EMPTY, specifying that aside from the command the request should be empty. If data was to be returned with a command it would need to be parsed into the appropriate data types. For example, if the following request was made: http://www.mysite.com/myisapi.dll?Add&name=bob&id=15248 The "name" field would need to be placed in a string and the "id" field in an int. To do this, ISAPI has six data definition flags:

ITS_EMPTY ITS_PSTR ITS_I2 ITS_I4 ITS_R4 ITS_R8

// // // // // //

no data a string a short a long a float a double

For the example above there would be an ITS_PSTR and an ITS_I4. The ON_PARSE_COMMAND would look like this:

ON_PARSE_COMMAND(Add, CMyISAPIExtension, ITS_PSTR ITS_I4)

This would not work, however, because ISAPI parses out everything between the ampersands as a field. So "name=bob" would be placed in the string, and "id=15248" would try to be placed in the int. This problem can be corrected by using the ON_PARSE_COMMAND_PARAMS macro. This is placed just after the ON_PARSE_COMMAND, and creates a map of the field names in the request. The parse map using ON_PARSE_COMMAND_PARAMS would look like this:

ON_PARSE_COMMAND(Add, CMyISAPIExtension, ITS_PSTR ITS_I4) ON_PARSE_COMMAND_PARAMS("name id")

This specifies that the field titled "name" should be associated with the first data type in the parse map, and "id" with the second. When building HTML forms that interact with a parse map, make sure that the action of the form contains the command, and the form method is post. For example, the form for the parse map above would look like this:

<FORM ACTION="myisapi.dll?Add" METHOD=POST> <INPUT NAME="name"> <INPUT NAME="id"> <INPUT TYPE=SUBMIT> </FORM>

Writing: SimpleCalc
Create a new project titled "SimpleCalc", and follow the same steps as in lesson 1 to configure the debugger. SimpleCalc is a simple web driven calculator. It can add, subtract, multiply, and divide. By default, the SimpleCalc ISA will display a form containing two edit fields for number entry and a select box for choosing the appropriate mode. When the form is submitted, it will issue a "Calc" command, the answer will be calculated, and the result will be displayed. The first step of writing SimpleCalc is to set up the parse map. The map needs to handle a default and "Calc" command. With "Calc" the edit fields "num1" and "num2" must be mapped to doubles and the select box "mode" should be mapped to a string. The finished parse map should

http://devcentral.iticentral.com/articles/MFC/isapi/part2/default.php (2 of 4) [7/9/2001 2:52:05 PM]

DevCentral - Developing ISAPI Extensions With MFC: Part II


look similar to this:

BEGIN_PARSE_MAP(CSimpleCalcExtension, CHttpServer) //Handle "Calc" command. ON_PARSE_COMMAND(Calc, CSimpleCalcExtension, ITS_R8 ITS_R8 ITS_PSTR) //Maps "num1" and "num2" to the ITS_R8s, and "mode" to the ITS_PSTR. ON_PARSE_COMMAND_PARAMS("num1 num2 mode") //Display form if request is empty. ON_PARSE_COMMAND(Default, CSimpleCalcExtension, ITS_EMPTY) DEFAULT_PARSE_COMMAND(Default, CSimpleCalcExtension) END_PARSE_MAP(CSimpleCalcExtension)

Next the default command handler needs to be changed so that it displays the calc form. The form contains two edit boxes named "num1" and "num2" and a select box with the basic math operators (+, -, *, /). Here is the Default command handler with the form:

void CSimpleCalcExtension::Default(CHttpServerContext* pCtxt) { //Print the <HTML> <BODY> tags. StartContent(pCtxt); //Print the title. WriteTitle(pCtxt); //The next six lines print the default calc form. //For this form to work correctly the action must contain the "Calc" //command, and the method must be POST. *pCtxt *pCtxt *pCtxt *pCtxt *pCtxt *pCtxt *pCtxt << << << << << << << _T("<H4>SimpleCalc</H4<BR><BR>"); _T("<FORM ACTION=\"simplecalc.dll?Calc\" METHOD=POST>"); _T("<INPUT NAME=\"num1\" SIZE=5> "); _T("<SELECT NAME=\"mode\"><OPTION>+<OPTION>"); _T("<OPTION>*<OPTION>/</SELECT>"); _T("<INPUT NAME=\"num2\" SIZE=5><BR><BR>"); _T("<INPUT TYPE=SUBMIT></FORM>");

//Print </HTML> </BODY> tags. EndContent(pCtxt); }

Now the "Calc" command handler needs to be written. It must determine the operator chosen by the user and calculate "num1" and "num2" accordingly, then return the result. It will look similar to this:

void CSimpleCalcExtension::Calc(CHttpServerContext* pCtxt, double num1, double num2, LPTSTR mode) { double result; //Prints the <HTML><BODY> tags. StartContent(pCtxt); //Prints the title. WriteTitle(pCtxt); //Determine the operator. switch( mode[0] ) { //Add case '+' : result = num1 + num2; //Print result. *pCtxt << num1 << _T(" + ") << num2 << _T(" = ") << result; break; //Subtract case '-' :

http://devcentral.iticentral.com/articles/MFC/isapi/part2/default.php (3 of 4) [7/9/2001 2:52:05 PM]

DevCentral - Developing ISAPI Extensions With MFC: Part II


result = num1 - num2; //Print result. *pCtxt << num1 << _T(" - ") << num2 << _T(" = ") << result; break; //Multiply case '*' : result = num1 * num2; //Print result. *pCtxt << num1 << _T(" * ") << num2 << _T(" = ") << result; break; //Divide case '/' : result = num1 / num2; //Print result. *pCtxt << num1 << _T(" / ") << num2 << _T(" = ") << result; break; } //Print </HTML> </BODY> tags. EndContent(pCtxt); }

Once the code has been modified, build and run the project. Upon loading the ISA in the browser, the form will be displayed. Enter two numbers, choose an operation, and press submit. The result displayed will look similar to this:

1.000000 + 2.000000 = 3.000000

The Hello Web and SimpleCalc projects are available on this page for download. Make sure that all the directory paths are configured for your server before using. Developed Under: Windows NT 4.0 Visual C++ 4.1 IIS 3.0

2001 Interface Technologies, Inc. All Rights Reserved Questions or Comments? devcentral@iticentral.com PRIVACY POLICY

http://devcentral.iticentral.com/articles/MFC/isapi/part2/default.php (4 of 4) [7/9/2001 2:52:05 PM]

You might also like