IIS ISAPI ServerSupportFunction and Keep-Alive SessionID: Q171617
|
When you create an ISAPI application for Internet Information Server (IIS), this application will require that the session between the client browser and IIS be maintained via the Keep-Alive header.
To implement Keep-Alive, IIS uses an async I/O read request via a
completion port on the session to maintain it. This method requires
that the thread that sent the read request still be active when the
read request returns, or else the session will be torn down.
When an IIS ISAPI extension implements a WorkerFunction, and then
that worker function completes its task, the worker function thread
and the associated thread within IIS that issued the read request
are torn down. As a result, the session is torn down.
To test if you are running into this situation, you can add a
statement ("Sleep ( INFINITE );" ) to the end of your WorkerFunction
to delay it so the read request can return.
To verify this is what is taking place, you should take a network
trace and check to make sure that the socket number for the session
is being maintained when the client hits refresh or some other
object on the web page.
The following are examples of how to maintain the session with
Keep-Alive, and one example that shows it failing.
// **************************************************************
// Code sample showing Keep-Alive while implementing
// a worker thread.
//
// KeepAlive.c -> Sample ISAPI Extension demonstrating Keep-Alive.
// **************************************************************
#include <windows.h>
#include <httpext.h>
#include <stdio.h>
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
lstrcpyn(pVer->lpszExtensionDesc, "ISAPI Keep-Alive Extension Sample",
HSE_MAX_EXT_DLL_NAME_LEN);
return TRUE;
}
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
DWORD dwSize;
// This header will be filled in with the content length
char szHeader[]="Connection: Keep-Alive\r\nContent-Length: %lu\r\nContent-type:
text/html\r\n\r\n";
char szContent[]="<html> <form method=get action=KeepAlive.dll><input type=submit>
</form></html>";
char szBuffer[4096];
// Send outgoing header
sprintf(szBuffer, szHeader, strlen(szContent));
dwSize = strlen(szBuffer);
pECB->ServerSupportFunction(pECB, HSE_REQ_SEND_RESPONSE_HEADER,
NULL, &dwSize, (unsigned long *)szBuffer);
// Send content
dwSize = strlen(szContent);
pECB->WriteClient(pECB, szContent, &dwSize, 0);
return HSE_STATUS_SUCCESS_AND_KEEP_CONN;
}
// *******************************************************************
// The same code sample as above, but using a worker thread
// implementing Keep-Alive.
//
// KeepAliveT.c -> Sample ISAPI Extension demonstrating Keep-Alive
// in a worker thread that terminates immediately which causes the
// session to NOT be maintained.
// *******************************************************************
#include <windows.h>
#include <httpext.h>
#include <stdio.h>
DWORD WINAPI WorkerFunction( LPVOID );
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
lstrcpyn(pVer->lpszExtensionDesc, "ISAPI Keep-Alive Extension Sample",
HSE_MAX_EXT_DLL_NAME_LEN);
return TRUE;
}
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
DWORD dwThreadID;
CreateThread(NULL, 0, WorkerFunction, pECB, 0, &dwThreadID);
return HSE_STATUS_PENDING;
}
DWORD WINAPI WorkerFunction(LPVOID vECB)
{
EXTENSION_CONTROL_BLOCK *pECB;
DWORD dwState, dwSize;
//This header will be filled in with the content length
char szHeader[]="Connection: Keep-Alive\r\nContent-Length: %lu\r\nContent-type: text/html\r\n\r\n";
char szContent[]="<html> <form method=get action=KeepAliveT.dll><input type=submit> </form></html>";
char szBuffer[4096];
pECB = vECB;
// Send outgoing header
sprintf(szBuffer, szHeader, strlen(szContent));
dwSize = strlen(szBuffer);
pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
NULL, &dwSize, (unsigned long *)szBuffer);
// Send content
dwSize = strlen(szContent);
pECB->WriteClient(pECB->ConnID, szContent, &dwSize, 0);
dwState = HSE_STATUS_SUCCESS_AND_KEEP_CONN;
pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_DONE_WITH_SESSION, &dwState, NULL, 0);
return 0;
}
// ***************************************************************
// This program demonstates the recommendated method for
// implementing Keep-Alive in a worker function using a thread pool
// and work queue.
//
// KeepAliveP.c -> Sample ISAPI Extension demonstrating
// Keep-Alive with a thread pool.
// ***************************************************************
#include <windows.h>
#include <httpext.h>
#include <stdio.h>
#define POOL_THREADS 2
#define WORK_QUEUE_ENTRIES 2
typedef struct {
EXTENSION_CONTROL_BLOCK * pECB;
DWORD dwNextEntry;
} ECB_QUEUE_ENTRY;
ECB_QUEUE_ENTRY ECBqueue[WORK_QUEUE_ENTRIES];
DWORD dwCurrentEntry, dwLastEntry;
CRITICAL_SECTION csQueueLock;
BOOL fFirstCall;
HANDLE hWorkSem;
BOOL AddWorkQueueEntry(EXTENSION_CONTROL_BLOCK *);
BOOL GetWorkQueueEntry(EXTENSION_CONTROL_BLOCK ** ppECB);
DWORD WINAPI WorkerFunction(LPVOID vThreadNum);
BOOL WINAPI DllMain(IN HINSTANCE hinstDll, IN DWORD fdwReason, IN LPVOID lpvContext OPTIONAL)
{
BOOL fReturn = TRUE;
DWORD i;
DWORD dwThreadID;
switch (fdwReason )
{
case DLL_PROCESS_ATTACH:
{
// Create Semaphore in nonsignaled state
if( (hWorkSem = CreateSemaphore( NULL, 0, 0x7fffffff, NULL )) ==NULL)
return FALSE;
InitializeCriticalSection(&csQueueLock );
fFirstCall=TRUE;
// Create Pool Threads
for(i=0; i<POOL_THREADS; i++)
{
if(CreateThread(NULL, 0, WorkerFunction, (LPVOID)i, 0, &dwThreadID)==NULL)
return FALSE;
}
// Clear work queue
ZeroMemory(ECBqueue, WORK_QUEUE_ENTRIES*sizeof(ECB_QUEUE_ENTRY) );
}
}
return fReturn;
}
DWORD WINAPI WorkerFunction( LPVOID );
BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer)
{
pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR);
lstrcpyn(pVer->lpszExtensionDesc, "ISAPI Keep-Alive Extension Sample",
HSE_MAX_EXT_DLL_NAME_LEN);
return TRUE;
}
DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB)
{
DWORD dwSize;
char szHeader[]="Connection: Keep-Alive\r\nContent-Length: %lu\r\nContent-type: text/html\r\n\r\n";
char szContent[]="<html> <form method=get action=KeepAliveP.dll> <input type=submit> " \
"<br>pECB->ConnID=%lu <br>Server was too busy. </form></html>";
char szBuffer[4096];
char szBuffer2[4096];
EnterCriticalSection(&csQueueLock);
if(!AddWorkQueueEntry(pECB)) // if ECB could not be assigned
{
LeaveCriticalSection(&csQueueLock);
sprintf(szBuffer2, szContent, pECB->ConnID);
// Send outgoing header
sprintf(szBuffer, szHeader, strlen(szBuffer2));
dwSize = strlen(szBuffer);
pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
NULL, &dwSize, (unsigned long *)szBuffer);
// Send content
dwSize = strlen(szBuffer2);
pECB->WriteClient(pECB->ConnID, szBuffer2, &dwSize, 0);
return HSE_STATUS_SUCCESS_AND_KEEP_CONN;
}
else
{
ReleaseSemaphore( hWorkSem, 1, NULL ); //Release 1 thread from pool
LeaveCriticalSection(&csQueueLock);
}
return HSE_STATUS_PENDING;
}
BOOL AddWorkQueueEntry(EXTENSION_CONTROL_BLOCK * pECB)
{
DWORD i;
for(i=0; i<WORK_QUEUE_ENTRIES; i++)
{
if (ECBqueue[i].pECB==NULL)
{
if(fFirstCall)
{
dwCurrentEntry=i;
fFirstCall = FALSE;
}
else
ECBqueue[dwLastEntry].dwNextEntry=i;
ECBqueue[i].pECB=pECB;
dwLastEntry=i;
return TRUE;
}
}
// If no NULL queue entry found, indicate failure
return FALSE;
}
BOOL GetWorkQueueEntry(EXTENSION_CONTROL_BLOCK ** ppECB)
{
if( (*ppECB=ECBqueue[dwCurrentEntry].pECB) == NULL)
return FALSE;
else
{
ECBqueue[dwCurrentEntry].pECB = NULL;
if(dwCurrentEntry == dwLastEntry) // If this is only pending item
fFirstCall = TRUE;
else
dwCurrentEntry = ECBqueue[dwCurrentEntry].dwNextEntry;
}
return TRUE;
}
DWORD WINAPI WorkerFunction(LPVOID pvThreadNum)
{
EXTENSION_CONTROL_BLOCK *pECB;
DWORD dwRet, dwState, dwSize, dwThreadNum;
//This header will be filled in with the content length
char szHeader[]="Connection: Keep-Alive\r\nContent-Length: %lu\r\nContent-type: text/html\r\n\r\n";
char szContent[]="<html> <form method=get action=KeepAliveP.dll><input type=submit> "\
"<br>pECB->ConnID=%lu <br>dwThreadNum=%lu</form></html>";
char szBuffer[4096];
char szBuffer2[4096];
dwThreadNum=(DWORD)pvThreadNum;
while(TRUE)
{
dwRet = WaitForSingleObject( hWorkSem, INFINITE );
if ( dwRet == WAIT_OBJECT_0 )
{
EnterCriticalSection(&csQueueLock);
if( GetWorkQueueEntry(&pECB) ) // This function should always return true
{
LeaveCriticalSection(&csQueueLock);
sprintf(szBuffer2, szContent, pECB->ConnID, dwThreadNum);
// Send outgoing header
sprintf(szBuffer, szHeader, strlen(szBuffer2));
dwSize = strlen(szBuffer);
pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_SEND_RESPONSE_HEADER,
NULL, &dwSize, (unsigned long *)szBuffer);
Sleep(3000);
// Send content
dwSize = strlen(szBuffer2);
pECB->WriteClient(pECB->ConnID, szBuffer2, &dwSize, 0);
dwState = HSE_STATUS_SUCCESS_AND_KEEP_CONN;
pECB->ServerSupportFunction(pECB->ConnID, HSE_REQ_DONE_WITH_SESSION, &dwState, NULL, 0);
}
else
LeaveCriticalSection(&csQueueLock);
}
else
break;
}
return 0;
}
Additional query words: iis
Keywords : kbinterop iisscript
Version : WinNT:3.0
Platform : winnt
Issue type : kbhowto
Last Reviewed: April 30, 1999