How to implement worker threads using MFC ISAPI extension (185518)



The information in this article applies to:

  • Microsoft Internet Information Server 3.0
  • Microsoft Internet Information Server 4.0
  • Microsoft Internet Information Services 5.0
  • Microsoft Internet Information Services version 5.1
  • Microsoft Visual C++, 32-bit Enterprise Edition 5.0
  • Microsoft Visual C++, 32-bit Enterprise Edition 6.0
  • Microsoft Visual C++, 32-bit Professional Edition 5.0
  • Microsoft Visual C++, 32-bit Professional Edition 6.0
  • Microsoft Internet Server Application Programming Interface (API)

This article was previously published under Q185518
We strongly recommend that all users upgrade to Microsoft Internet Information Services (IIS) version 6.0 running on Microsoft Windows Server 2003. IIS 6.0 significantly increases Web infrastructure security. For more information about IIS security-related topics, visit the following Microsoft Web site:

SUMMARY

One method of implementing an ISAPI extension that can potentially require a long processing time (whether it is waiting on database queries or waiting on other network connections) is to implement a worker thread model. This ensures that the IIS Asynchronous Thread Queue (ATQ) thread is not tied up for long periods. While an example of implementing worker threads within an ISAPI extension is distributed with the platform SDK, implementing a similar scheme using an ISAPI Wizard-generated MFC-based extension is not as clear-cut. This article demonstrates how to incorporate worker threads within an MFC-based extension.

MORE INFORMATION

There are several steps involved in implementing worker threads within an MFC based extension. The first step is to override the HttpExtensionProc() virtual method found in the CHttpServer base class. This is very straightforward. The sample code below assumes that a ISAPI Extension Wizard project named MyApp has been created:

  1. In MyApp.h, override HttpExtensionProc():
          class CMyAppExtension : public CHttpServer
          {
          public:
    
          // Code generated by the ClassWizard.
    
          // Simply include the method in the public section of the code
          // generated by the ClassWizard making certain not to alter
          // anything else.
             virtual DWORD HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB);
          };
    					
  2. In MyApp.cpp, override the base class HttpExtensionProc(). It should read something like this:
          DWORD CMyAppExtension::HttpExtensionProc( EXTENSION_CONTROL_BLOCK
                                                    *pECB)
          {
    
          // The result of the base class's (CHttpServer) HttpExtensionProc()
          // must be checked.  If it's successful, you will need to return a
          // HSE_STATUS_PENDING.  This is so that IIS will not shutdown the
          // connection and not destroy the ECB.
          // If the result of the base class is not successful, you should
          // simply propagate the error message up to IIS.
    
             DWORD  dwResult = CHttpServer::HttpExtensionProc(pECB);
             if(dwResult == HSE_STATUS_SUCCESS)
               return HSE_STATUS_PENDING;
             else
               return dwResult;
          }
    					
  3. Now, inside the parse map function (here, I assume that you are simply using the Default function inserted for us by the ClassWizard--the method takes no variable except the CHttpServerContext and returns nothing):
          void CMyAppExtension::Default(CHttpServerContext* pCtxt)
          {
          // Setting the m_bSendHeaders variable to FALSE will prevent
          // IIS from sending headers; this must be handled by your
          // thread.
    
             pCtxt->m_bSendHeaders = FALSE;
    
          // Spawn thread...you must use AfxBeginThread since this is an MFC
          // based extension.
             AfxBeginThread((AFX_THREADPROC)MyThreadProc,
                            (LPVOID)(pCtxt->m_pECB));
          }
    						
    If you are using some other function that takes several parameters, you should create your own class/struct to encapsulate the ECB pointer and your parameters and pass a pointer to that class to the thread process (just make sure you clean up your class/struct).
  4. In your thread process:
          UINT MyThreadProc(LPVOID pvParam)
          {
             EXTENSION_CONTROL_BLOCK *pECB = (EXTENSION_CONTROL_BLOCK*)pvParam;
    
          // Do some processing here...
    
          // To send custom headers, remember to set the m_bSendHeaders to
          // FALSE in the CHttpServerContext from the method that spawned
          // this thread and use the ServerSupportFunction().
    
          // Write data to the client...
          // pECB->WriteClient(...);
    
          // Before returning from the thread routine, you must send the
          // HSE_REQ_DONE_WITH_SESSION flag so IIS can clean up the session.
             pECB->ServerSupportFunction(pECB->ConnID,
                                         HSE_REQ_DONE_WITH_SESSION,
                                         NULL, NULL, 0);
    
             return 0;
          }
    					

Completed Sample

The following files are available for download from the Microsoft Download Center:
For additional information about how to download Microsoft Support files, click the following article number to view the article in the Microsoft Knowledge Base:

119591 How to Obtain Microsoft Support Files from Online Services

Microsoft scanned this file for viruses. Microsoft used the most current virus-detection software that was available on the date that the file was posted. The file is stored on security-enhanced servers that help to prevent any unauthorized changes to the file. To build the sample, first extract the files into a directory, and open the project in Microsoft Visual C++.

NOTE: This example uses a single worker thread per session. A more sophisticated and efficient model would use a pool of worker threads and a queuing mechanism to handle the multiple ECBs. Also remember, that while the ATQ thread is impersonating the IUSR_<MachineName> account, the newly spawned worker thread will be running in the security context of the local System account. To change to the IUSR_<MachineName> account, save the thread security token in the ATQ thread by calling OpenThreadToken() and pass that token along with the ECB to the worker thread and have the worker thread call SetThreadToken().

WARNING: The example above assumes all functions in the parse map will be using the worker thread model. If any function does not use a worker thread, you must notify CMyAppExtension::HttpExtensionProc() to not send back HSE_STATUS_PENDING. The simplest solution is to force all parse map functions to use worker threads. If this is not an acceptable solution, one way to notify the HttpExtensionProc() would be to use Thread Local Storage. If care is not taken to ensure the proper return flag is sent by the HttpExtensionProc(), you can potentially have sessions orphaned.

REFERENCES

For additional information, click the article number below to view the article in the Microsoft Knowledge Base:

197728 Creating a Thread Pool for MFC-Based ISAPI Extensions



See Thread Local Storage in Win32 API Documentation.

Modification Type:MinorLast Reviewed:6/22/2006
Keywords:kbdownload kbFilter kbhowto kbhttp KB185518