HOWTO: Implement Worker Threads Using MFC ISAPI Extension

ID: Q185518

The information in this article applies to:

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 IIS 4.0, 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 file is available for download from the Microsoft Software Library:

 ~ MFCWkThr.exe (size: 34578 bytes) 

For more information about downloading files from the Microsoft Software Library, please see the following article in the Microsoft Knowledge Base:

   ARTICLE-ID: Q119591
   TITLE     : How to Obtain Microsoft Support Files from Online Services

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

See \InetPub\iissamples\sdk\isapi\extensions\WorkerThread (distributed with IIS 4.0) for example of implementing worker threads using non-MFC based ISAPI extension.

See Thread Local Storage in Win32 API Documentation.

Additional query words: kbDSupport kbdsi

Technology        : kbInetDev
Version           : WINNT:3.0,4.0,5.0
Platform          : winnt
Issue type        : kbhowto

Last Reviewed: December 21, 1998