In Lesson 2, you were introduced to Microsoft's Web server applications: IIS 4.0 and PWS. You learned how to use these applications to organize and deploy content such as HTML pages and ActiveX documents on a Web site.
In this lesson you will learn how to write programs that extend the functionality of a Microsoft Web server using ISAPI. You will learn about two types of programs: ISAPI server extensions and ISAPI filters, both of which are implemented as DLLs. ISAPI server extensions are programs that run within the Internet server process. ISAPI filters intercept data travelling to and from the server, and thus can perform specialized logging, encryption and other tasks.
After this lesson, you will be able to:Estimated lesson time: 30 minutes
- Describe how to create an ISAPI server extension using MFC.
- Describe how to make an ISAPI server extension generate customized content based on parameters submitted from an HTML form.
- Describe how to create an ISAPI filter using MFC.
- Describe how to install an ISAPI filter.
An ISAPI server extension is a server-side program that runs in response to requests from a Web browser. A server extension is implemented as a DLL, which is loaded by IIS. The browser can run the program by specifying the name of the .dll file in a URL like this:
http://myserver/apps/charts.dll |
The ISAPI server extension implements functions that can be called by the browser. To call a function, you append the function name to the URL as follows:
http://myserver/apps/charts.dll?GetChart |
This example references the charts.dll server extension and calls the GetChart() function. All ISAPI server extensions must define a default function that is called if the client does not specify a function. Server extension functions can take parameters, which can be specified in the URL as follows:
http://myserver/apps/charts.dll?GetChart?Fund=ARSC |
An ISAPI server extension can be linked to an HTML form by assigning the extension's URL to the action attribute inside a <FORM> tag, as follows:
<FORM action="myextension.dll?GetColor" method=POST> <!— form controls go here —> </FORM> |
Values that the user enters into the form's controls are automatically passed as parameters to the ISAPI server extension when the user submits the form. ISAPI server extension functions typically send back customized HTML code based on these parameter values.
ISAPI server extensions are based on the Common Gateway Interface (CGI) standard. CGI is part of the HTTP, which was developed as a way to allow browser programs to interact with scripts or separate executable programs running on a Web server. Without altering the HTTP/CGI specifications, Microsoft developed IIS to allow any browser to load and run a server extension DLL. Because DLLs run as part of the process that loads them, server extensions are faster than scripts that might need to load separate executable programs.
CGI shifts the programming burden to the server. Using CGI parameters, the browser sends small amounts of information to the server. The server can do absolutely anything with this information, including access a database, generate images, and control peripheral devices. The server sends a file (HTML or otherwise) back to the browser, which can be read from the server's disk or generated by the program.
To see a basic ISAPI server extension in action, you will now use the MFC ISAPI Extension Wizard to create one of your own.
Figure 12.19 The ISAPI Extension Wizard
http://[your computer name]/apps/myextension.dll |
This request will call the default function of MyExtension.dll. The output of this should appear in the browser as shown in Figure 12.20.
Figure 12.20 Internet Explorer 5 displaying output from MyExtension.dll
If you look at the source code in the MyExtension project, you will see that the ISAPI Extension Wizard has created a single class for you named CMyExtensionExtension. This class is derived from the MFC base class CHttpServer. You can add member functions to this class to implement the functions exported by the DLL.
MFC provides a code construct called a parse map to define the DLL functions and map them to member functions of your CHttpServer-derived class. The parse map is declared in the header file with the DECLARE_PARSE_MAP macro, and implemented with BEGIN_PARSE_MAP and END_PARSE_MAP macros, as shown in the following example from your MyExtension.cpp file:
BEGIN_PARSE_MAP(CMyExtensionExtension, CHttpServer) // TODO: insert your ON_PARSE_COMMAND() and // ON_PARSE_COMMAND_PARAMS() here to hook up your commands. // For example: ON_PARSE_COMMAND(Default, CMyExtensionExtension, ITS_EMPTY) DEFAULT_PARSE_COMMAND(Default, CMyExtensionExtension) END_PARSE_MAP(CMyExtensionExtension) |
You can see that the ISAPI Extension Wizard has added the DEFAULT_PARSE COMMAND macro to specify the name of the default function as Default(). The Wizard provides a simple implementation of this function that can be found further down the file:
void CMyExtensionExtension::Default(CHttpServerContext* pCtxt) { StartContent(pCtxt); WriteTitle(pCtxt); *pCtxt << _T("This default message was produced by the Internet"); *pCtxt << _T(" Server DLL Wizard. Edit your CMyExtensionExtension::Default()"); *pCtxt << _T(" implementation to change it.\r\n"); EndContent(pCtxt); } |
The output from this function is shown in Figure 12.20.
The Default() function, like all ISAPI extension functions, takes a pointer to a CHttpServerContext object. One CHttpServerContext object is created by CHttpServer for each HTTP client/server transaction. As the server extension DLL processes requests, it uses CHttpServerContext member functions to perform tasks such as retrieving details contained in the header of the HTTP client request (using CHttpServerContext::GetServerVariable()), or inserting HTML text into the response file that is sent back to the client (using the overloaded << operator).
The server extension's CHttpServer object creates a CHttpServerContext object for each client request. Each object is created on a separate thread to allow multiple simultaneous calls to the CHttpServer object by different client connections. You must be careful to perform synchronization for global variables, or for any data members of your CHttpServer class.
You will now add a simple function named GetColor() to your ISAPI server extension. GetColor() takes any one of the HTML color names as a parameter and generates a page with an appropriately colored background.
ON_PARSE_COMMAND(GetColor, CMyExtensionExtension, ITS_PSTR) ON_PARSE_COMMAND_PARAMS("color") |
The ON_PARSE_COMMAND macro specifies the name of the function—the class name to map the function to. The parameters received by the function are specified as the third (and subsequent) parameters of the ON_PARSE_COMMAND macro. The parameter types are specified by the ON_PARSE_COMMAND_PARAMS macro. The lines in the example just shown specify that the GetColor() function will have a single string parameter named color.
void GetColor(CHttpServerContext *pCtxt, LPCTSTR pstrColor) |
void CMyExtensionExtension::GetColor(CHttpServerContext *pCtxt, LPCTSTR pstrColor) { StartContent(pCtxt); WriteTitle(pCtxt); *pCtxt << _T("You have chosen the "); *pCtxt << pstrColor << _T(" page.\r\n"); *pCtxt << _T("<SCRIPT language=\"JavaScript\"> document.bgColor = \""); *pCtxt << pstrColor << _T("\" </SCRIPT>\r\n"); EndContent(pCtxt); } |
net stop w3svc |
net start w3svc |
The revised DLL will be loaded at the first client request.
On a computer running Windows 95 or Windows 98, you can stop and start the Web service from the Properties menu of the Personal Web Manager. To gain access to the DLL, you might have to restart your computer after stopping the service.
The following HTML document called Form.htm allows you to test your new extension function. This document implements a simple form that allows the user to pick a color from a drop-down list box. The user clicks a Submit button to retrieve a page of the chosen color from the server.
<!-- Form.htm --> <HTML> <HEAD> <TITLE> Color Form </TITLE> </HEAD> <BODY> <H3> Choose a color from the list box and click SUBMIT</H3> <FORM action="myextension.dll?GetColor" method=POST> <SELECT name="color"> <option> pink <option> green <option> yellow <option> blue </SELECT> <P> <input type="submit"> </FORM> </BODY> </HTML> |
Form.htm can be found in the Chapter 12\Exercises folder. Place a copy of Form.htm in the \INetPub\WWWRoot\MyISExtensions folder.
http://[your computer name]/apps/form.htm |
You will see the page appear as shown in Figure 12.21.
Figure 12.21 Form.htm displayed in Internet Explorer 5
ISAPI filters sit between the network connection to the clients and the HTTP server. They can be used to enhance Internet servers with custom features such as the enhanced logging of HTTP requests or custom encryption and compression schemes, or to implement alternative authentication methods. Filters are implemented as DLLs that are loaded when the WWW service is started. You can use the Internet Service Manager to assign filters to your IIS 4.0 Web sites. Personal Web Server does not support ISAPI filters.
Filters can be set to receive notification when selected server events occur. For example, the server might notify the filter that it is processing raw data from a client request, or that it is sending data to the client, or that it has just written to the log or closed a transaction. As the filter processes these notifications, it has access to the server data related to the notification event. For example, a filter that is notified that the server is sending raw data to the client has access to the data being sent so that the filter can modify the data, perhaps applying a compression or encryption algorithm.
The following exercises demonstrate a simple ISAPI filter that is built using the ISAPI Extension Wizard.
Figure 12.22 Creating an ISAPI filter
If you examine the generated code, you will see that the ISAPI Extension Wizard has created a single class CMyFilterFilter that is derived from the MFC class CHttpFilter. A handler function has been added to this class for each notification that you specified in Step 2 of the ISAPI Extension Wizard, as shown in this extract from the CMyFilterFilter class definition:
virtual DWORD OnReadRawData(CHttpFilterContext* pCtxt, PHTTP_FILTER_RAW_DATA pRawData); virtual DWORD OnEndOfNetSession(CHttpFilterContext* pCtxt); |
These functions are overloaded versions of CHttpFilter member functions. The Wizard provides stub functions in the .cpp file. You define your filter behavior by implementing these functions.
Note that another overloaded function, GetFilterVersion(), is implemented for your class. This function is called by the server to learn which notifications the filter will handle. To make the filtering process more efficient, the server sends notifications only for the events specified in this function. The following line from the MyFilter::GetFilterVersion() function shows how to specify the events:
pVer->dwFlags |= SF_NOTIFY_ORDER_LOW | SF_NOTIFY_SECURE_PORT | SF_NOTIFY_NONSECURE_PORT | SF_NOTIFY_READ_RAW_DATA | SF_NOTIFY_END_OF_NET_SESSION; |
Be aware that if you add handler functions for additional notification events, you must also add the corresponding flags to this line. Although you can use ClassWizard to add notification event handler functions, ClassWizard does not update the GetFilterVersion() function for you, so you must remember to do this manually.
Notification handler functions receive data structures that contain information about the event they are handling. They also receive a pointer to a CHttpFilterContext object, which like the CHttpServerContext object, contains information about the current client connection and provides methods to retrieve information about the connection or to write data into the client response.
In the following exercises, you will provide a simple implementation of the MyFilter::OnReadRawData() function. This will use the CHttpFilterContext:: GetServerData() function to retrieve the IP address of the remote server that is currently connected, and then write it to a log file.
DWORD CMyFilterFilter::OnReadRawData(CHttpFilterContext* pCtxt, PHTTP_FILTER_RAW_DATA pRawData) { char pchVar[64]; DWORD dwSize = 64; CStdioFile logfile("C:\\iislog.txt", CFile::modeCreate | CFile::modeWrite); BOOL bRet = pCtxt->GetServerVariable( " REMOTE_HOST", pchVar, &dwSize); if(bRet) logfile.Write(pchVar, dwSize); logfile.Close(); return SF_STATUS_REQ_NEXT_NOTIFICATION; } |
Figure 12.23 Installing ISAPI filters on a Web server
net stop w3svc |
followed by:
net start w3svc |
ISAPI allows you to write programs that extend the functionality of IIS 4.0 and its desktop counterpart, PWS.
There are two types of ISAPI programs: server extensions and filters, both of which are implemented as DLLs. ISAPI server extensions are compiled server-side programs that can be launched by an Internet client request. ISAPI filters intercept data travelling to and from the server and thus can perform specialized logging, encryption and other tasks.
ISAPI server extensions are based on the CGI standard and can be invoked from a URL by specifying the name of the DLL. Server extensions expose functions that can take parameters. You can specify the function name and parameter values as part of a URL, or you can connect the extension to an HTML form so that it receives parameters from the form controls. You use the ISAPI Extension Wizard to create a server extension project based around the CHttpServer and CHttpServerContext classes.
ISAPI filters sit between the network connection to the clients and the HTTP server, and they add custom features such as enhanced logging or encryption and compression to your Internet server. The filter is implemented as a DLL that is loaded when the WWW service is started. You can use the Internet Service Manager to assign filters to your IIS 4.0 Web sites. PWS does not support ISAPI filters. Filters receive notification when selected server events occur. As the filter processes these notifications, it has access to the server data related to the notification event. You use the ISAPI Extension Wizard to create an ISAPI filter project based around the CHttpFilter and CHttpFilterContext classes.