AREADCLI.C
/*++ 
 
Copyright (c) 1997  Microsoft Corporation 
 
Module Name: 
 
    AReadCli.c 
 
Abstract: 
 
    This module demonstrates using asynchronous ReadClient call 
    to read data sent from client. On return, this sends the  
    followings back to client: 
     
    1) the number of bytes intended to send 
    2) the number of bytes actually read. 
    3) data content  
 
Author: 
 
    Stanley Tam (stanleyt)   4-June 1997 
     
Revision History: 
   
--*/ 
#include <windows.h> 
#include <stdio.h> 
#include <httpext.h> 
 
 
#define MAX_BUF_SIZE        (49152)                 // Max number of bytes for each read - 48K 
#define MEM_ALLOC_THRESHOLD (1024 * 1024)           // Default heap size is 1M 
 
 
typedef struct _IO_WORK_ITEM { 
 
    PBYTE                       pbDATAFromClient;   // Grand read data holder 
    DWORD                       cbReadSoFar;        // Number of bytes read so far,  
                                                    // and is used as index for pbDATAFromClient 
    EXTENSION_CONTROL_BLOCK *   pecb; 
 
}  IO_WORK_ITEM, * PIOWI; 
 
 
// 
//  Sample Form for doing a POST method 
// 
 
static CHAR             g_szPostForm[] = 
                        "<h2>Asychronous ReadClient Sample Post Form</h2><p>\r\n\r\n" 
                        "This demonstrates a post request being sent by this form to the sample ISAPI - AReadCli.dll<p>\r\n\r\n" 
                        "AReadCli.dll reads data posted by this form and send it back to the browser.<p>\r\n" 
                        "<h3>Post Form</h3>\r\n" 
                        "Please enter data below:<br>\r\n" 
                        "<form method=POST action=\"areadcli.dll\">\r\n" 
                        "<textarea name=\"Data\" cols=48 rows=4></textarea>\r\n\r\n" 
                        "<input type=submit> <input type=reset>\r\n" 
                        "</form>"; 
 
// 
//  Report read data 
// 
 
static CHAR             g_szReport[] =  
                        "Bytes count including \"Data=\"  \r\n" 
                        "ECB Total Bytes:    %d.\r\n" 
                        "Actual Read Bytes:  %d.\r\n"; 
                 
DWORD 
DoInit(IN OUT PIOWI piowi); 
 
VOID 
DoCleanUp(IN PIOWI piowi); 
 
DWORD 
DoAsyncReadClient(IN PIOWI piowi); 
              
VOID WINAPI 
AsyncReadClientIoCompletion(IN LPEXTENSION_CONTROL_BLOCK pecb,  
                            IN PVOID pContext, 
                            IN DWORD cbIO, 
                            IN DWORD dwError); 
 
DWORD 
SendMSGToClient(IN LPEXTENSION_CONTROL_BLOCK pecb,  
                IN LPCSTR pszErrorMsg); 
 
LPVOID  
AR_Allocate(IN LPEXTENSION_CONTROL_BLOCK pecb, IN DWORD dwSize); 
 
BOOL  
AR_Free(IN LPEXTENSION_CONTROL_BLOCK pecb, IN LPVOID pvData); 
 
 
 
DllMain( 
     IN HINSTANCE hinstDll, 
     IN DWORD     fdwReason, 
     IN LPVOID    lpvContext OPTIONAL) 
/*++ 
 
 Routine Description: 
 
   This function DllLibMain() is the main initialization function for 
    this DLL. It initializes local variables and prepares it to be invoked 
    subsequently. 
 
 Arguments: 
 
   hinstDll          Instance Handle of the DLL 
   fdwReason         Reason why NT called this DLL 
   lpvReserved       Reserved parameter for future use. 
 
 fReturn Value: 
 
    fReturns TRUE if successful; otherwise FALSE is fReturned. 
 
--*/ 
{ 
  BOOL    fReturn = TRUE; 
 
 
  switch ( fdwReason) { 
 
    case DLL_PROCESS_ATTACH: 
      { 
          OutputDebugString( "Initializing the global data for areadcli.dll\n"); 
 
          // 
          // Prevent the system from calling DllMain 
          // when threads are created or destroyed. 
          // 
 
          DisableThreadLibraryCalls( hinstDll); 
 
          // 
          // Initialize various data and modules. 
          // 
 
          break; 
 
      } // case DLL_PROCESS_ATTACH 
 
    case DLL_PROCESS_DETACH: 
      { 
 
          if ( lpvContext != NULL) { } 
 
          break; 
      } // case DLL_PROCESS_DETACH 
 
    default: 
      break; 
  }   // switch 
 
  return (fReturn); 
}  // DllLibMain() 
 
 
 
 
BOOL WINAPI 
GetExtensionVersion(HSE_VERSION_INFO * Version) 
/*++ 
 
Routine Description: 
 
    Sets the ISAPI extension version information. 
 
Arguments: 
 
    Version     pointer to HSE_VERSION_INFO structure 
 
Return Value: 
 
    TRUE 
 
--*/ 
{ 
    Version->dwExtensionVersion =  
    MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR); 
 
    strcpy(Version->lpszExtensionDesc, "Asynchronous Read Client Sample ISAPI DLL"); 
 
    return TRUE; 
} 
 
 
 
 
DWORD WINAPI 
HttpExtensionProc(LPEXTENSION_CONTROL_BLOCK pecb) 
/*++ 
 
Routine Description: 
 
    This is the main routine for any ISAPI application. Inside DoASyncReadClient, 
    proper action will be performed to read data from client asynchronously. Any data  
    read from client will be sent back to the client by using synchronous WriteClient. 
 
Arguments: 
 
    pecb        pointer to ECB containing parameters related to the request. 
 
Return Value: 
 
    HSE_STATUS_SUCCESS  or 
    HSE_STATUS_PENDING  or  
    HSE_STATUS_ERROR 
 
--*/ 
{ 
    PIOWI   piowi; 
    DWORD   hseStatus = HSE_STATUS_SUCCESS; 
     
    // 
    // The string length of textarea name "Data=" is 5.  
    // Available bytes <= 5 indicates that no user- 
    // entered data has been sent, and the post form  
    // is shown. 
    // 
 
    if ( pecb->cbAvailable <= 5) {   
 
        hseStatus = SendMSGToClient(pecb, g_szPostForm); 
     
    } else { 
 
        piowi  = (PIOWI ) LocalAlloc( LMEM_FIXED, sizeof( IO_WORK_ITEM)); 
 
        if ( NULL == piowi) { 
 
            SetLastError( ERROR_NOT_ENOUGH_MEMORY); 
            return (HSE_STATUS_ERROR); 
        } 
 
        piowi->pecb = pecb; 
 
        // 
        // Init Grand data holder, assign the first chunk(read-ahead chunk) 
        // and update the index (cbRreadSoFar) of the grand data holder. 
        // 
 
        hseStatus = DoInit( piowi); 
         
        if ( HSE_STATUS_ERROR != hseStatus) { 
             
            // 
            // Now we are ready to do asynchronous readclient here 
            // 
 
            hseStatus = DoAsyncReadClient( piowi); 
         
            if (hseStatus != HSE_STATUS_PENDING) { 
 
                // 
                //  When IO finishes, tell IIS we will end the 
                //  session. Also clean up other resources here 
                // 
 
                DoCleanUp( piowi); 
 
            } 
         
        } 
 
    } 
        
    return (hseStatus); 
} 
 
 
 
 
BOOL WINAPI 
TerminateExtension(DWORD dwFlags) 
/*++ 
 
Routine Description: 
 
This function is called when the WWW service is shutdown 
 
Arguments: 
 
dwFlags - HSE_TERM_ADVISORY_UNLOAD or HSE_TERM_MUST_UNLOAD 
 
Return Value: 
 
    TRUE 
 
--*/ 
{ 
    return TRUE; 
} 
 
 
 
 
DWORD 
SendMSGToClient(IN LPEXTENSION_CONTROL_BLOCK  pecb, IN LPCSTR pszMsg) 
/*++ 
 
Routine Description: 
 
    Prepare header, SendHeaderExInfo struct and write whatever 
    message is intended to send to the client. 
 
Arguments: 
 
    pecb        - pointer to ECB containing parameters related to the request. 
    pszMsg      - pointer to the body of the message that is sent to the content. 
 
Return Value: 
 
    HSE_STATUS_SUCCESS  or HSE_STATUS_ERROR 
 
--*/ 
{ 
    HSE_SEND_HEADER_EX_INFOSHEI; 
 
    BOOL    fReturn; 
    DWORD   cbText; 
    DWORD   hseStatus = HSE_STATUS_SUCCESS; 
    CHAR    *pszText = NULL; 
     
    CHAR    szStatus[] = "200 OK"; 
    CHAR    szHeaderBase[] = "Content-type: text/html\r\n\r\n"; 
     
    // 
    //  Populate SendHeaderExInfo struct 
    // 
    //  NOTE we must send Content-Length header with correct  
    //  byte count in order for keep-alive to work. 
    // 
    // 
 
    SHEI.pszStatus = szStatus; 
    SHEI.pszHeader = szHeaderBase; 
    SHEI.cchStatus = lstrlen(szStatus); 
    SHEI.cchHeader = lstrlen(szHeaderBase); 
    SHEI.fKeepConn = FALSE; 
     
    // 
    //  Build page  
    // 
     
    cbText = strlen("<head><title>Simple Async Read Client Sample</title></head>\n<body></body>\n") 
             + strlen(pszMsg) 
             + 1; 
 
    pszText = (PCHAR) AR_Allocate(pecb, cbText); 
 
    if ( NULL == pszText) { 
 
        SetLastError( ERROR_NOT_ENOUGH_MEMORY); 
        return (HSE_STATUS_ERROR); 
    } 
     
    strcpy(pszText, "<head><title>Simple Async Read Client Sample</title></head>\n"); 
    strcat(pszText, "<body>"); 
    strcat(pszText, pszMsg); 
    strcat(pszText, "</body>\n"); 
    cbText = (DWORD)strlen(pszText);  
         
    // 
    // Send header and body text to client 
    // 
 
    fReturn =  
    pecb->ServerSupportFunction( pecb->ConnID, 
                                 HSE_REQ_SEND_RESPONSE_HEADER_EX,   
                                 &SHEI, 
                                 NULL, 
                                 NULL)  
                                             
                                 && 
 
    pecb->WriteClient( pecb->ConnID, 
                       pszText, 
                       &cbText, 
                       0 ); 
 
    if ( !fReturn)  { 
        hseStatus = HSE_STATUS_ERROR; 
    } 
 
     
    AR_Free( pecb, pszText); 
     
    return ( hseStatus); 
}  
 
 
 
 
DWORD 
DoAsyncReadClient(IN PIOWI piowi) 
/*++ 
 
Routine Description: 
 
    The caller of the asynchrnous read client. 
 
Arguments: 
 
    piowi       pointer to the work item 
     
Return Value: 
 
    HSE_STATUS_SUCCESS  or 
    HSE_STATUS_PENDING  or  
    HSE_STATUS_ERROR 
 
--*/ 
{    
    BOOL    fReturn; 
    BYTE    *pbData = NULL; 
    CHAR    szTmp[MAX_BUF_SIZE]; 
    DWORD   dwFlags; 
    DWORD   cbTotalToRead = MAX_BUF_SIZE; 
    DWORD   hseStatus =  HSE_STATUS_PENDING; 
     
    // 
    // Check if cbTotalBytes == cbAvailable 
    // if so lpbData contains all the data sent by  
    // the client, and complete the session.  
    // 
 
    if (piowi->pecb->cbTotalBytes == piowi->pecb->cbAvailable) { 
         
        // 
        // Construct the report and write it to client 
        // 
 
        pbData = (PBYTE) AR_Allocate(piowi->pecb, piowi->pecb->cbAvailable + MAX_BUF_SIZE); 
             
        if ( NULL == pbData) { 
 
            SetLastError( ERROR_NOT_ENOUGH_MEMORY); 
            return (HSE_STATUS_ERROR); 
        } 
 
        wsprintf ( pbData, 
                   g_szReport, 
                   piowi->pecb->cbTotalBytes, 
                   piowi->pecb->cbAvailable ); 
                     
        strcat( pbData, piowi->pecb->lpbData);   
 
        hseStatus = SendMSGToClient( piowi->pecb, pbData); 
 
 
        AR_Free( piowi->pecb, pbData); 
        DoCleanUp( piowi); 
         
        return ( hseStatus); // HSE_STATUS_SUCCESS or HSE_STATUS_ERROR; 
    } 
 
    // 
    //  More to read... 
    // 
 
    // 
    //  Set a call back function and context that will  
    //  be used for handling asynchrnous IO operations. 
    //  This only needs to set up once. 
    // 
 
    fReturn = 
    piowi->pecb->ServerSupportFunction( 
                    piowi->pecb->ConnID, 
                    HSE_REQ_IO_COMPLETION, 
                    AsyncReadClientIoCompletion, 
                    0, 
                    (LPDWORD)piowi );   
     
    if ( !fReturn) { 
 
        wsprintf ( szTmp, "Problem occurred at ServerSupportFunction() sending HSE_REQ_IO_COMPLETION request."); 
        SendMSGToClient( piowi->pecb, szTmp); 
         
        return ( HSE_STATUS_ERROR); 
    } 
     
 
    // 
    //  Fire off the call to perform an asynchronus read from the client.  
    // 
     
    //  
    // We need to first check if the size of the remaining chunk  
    // is less than MAX_BUF_SIZE, if so just read what is available,  
    // otherwise read MAX_BUF_SIZE bytes of data. 
    //  
 
    cbTotalToRead = piowi->pecb->cbTotalBytes - piowi->cbReadSoFar; 
    if ( cbTotalToRead > MAX_BUF_SIZE )  { 
 
        cbTotalToRead = MAX_BUF_SIZE; 
    } 
 
    dwFlags = HSE_IO_ASYNC; 
    fReturn =  
    piowi->pecb->ServerSupportFunction( 
                        piowi->pecb->ConnID 
                      , HSE_REQ_ASYNC_READ_CLIENT 
                      , piowi->pbDATAFromClient + // append the new chunk to buffer, cbReadSoFar indexes 
                        piowi->cbReadSoFar        // the byte right after the last written byte in the buffer 
                      , &cbTotalToRead 
                      , &dwFlags); 
 
    if (!fReturn) { 
         
        wsprintf ( szTmp, "Problem occurred at ServerSupportFunction() sending HSE_REQ_ASYNC_READ_CLIENT request."); 
        SendMSGToClient( piowi->pecb, szTmp); 
         
        hseStatus = HSE_STATUS_ERROR; 
    } 
 
    return ( hseStatus); // HSE_STATUS_PENDING or HSE_STATUS_ERROR; 
     
} 
 
  
 
 
VOID WINAPI 
AsyncReadClientIoCompletion( 
            IN LPEXTENSION_CONTROL_BLOCK pECB, 
            IN PVOID pContext, 
            IN DWORD cbIO, 
            IN DWORD dwError) 
/*++ 
 
Routine Description: 
 
    This is the callback function for handling completions of asynchronous ReadClient. 
    This function resubmits additional IO to read the next chunk of data from the  
    client. If there is no more data to read or problem during operation, this function  
    will inform IIS that it is about to end the request session. 
     
 
Arguments: 
 
    pecb        - extension control block 
    pContext    - this is a IO_WORK_ITEM 
    cbIO        - bytes just read 
    dwError     - Win32 error status code 
 
fReturn Value: 
 
    None 
 
--*/ 
{ 
    BOOL    fReturn; 
    BYTE    *pbData = NULL; 
    CHAR    szTmp[MAX_BUF_SIZE]; 
    DWORD   dwFlags; 
    DWORD   cbTotalToRead; 
     
    PIOWI   piowi = ( PIOWI) pContext; 
    EXTENSION_CONTROL_BLOCK   * pecb = piowi->pecb; 
     
 
    if (ERROR_SUCCESS == dwError) { 
 
        // 
        // Read successfully, so update current  
        // total bytes read (aka index of grand data holder) 
        // 
 
        piowi->cbReadSoFar += cbIO;      
                                
        // 
        // If they are equal, we finish reading all bytes from client 
        // 
         
        if ( piowi->cbReadSoFar == pecb->cbTotalBytes ) {  
 
            // 
            // Construct the report and write it to client 
            // 
 
            pbData = (PBYTE) AR_Allocate( pecb, piowi->cbReadSoFar + MAX_BUF_SIZE); 
                 
            if ( NULL == pbData) { 
 
                SetLastError( ERROR_NOT_ENOUGH_MEMORY); 
                wsprintf ( szTmp, "Failed to allocate memory inside AsyncReadClientIoCompletion()."); 
                SendMSGToClient( pecb, szTmp); 
                 
                DoCleanUp( piowi); 
  
                return; 
            } 
             
            wsprintf ( pbData, g_szReport, pecb->cbTotalBytes, piowi->cbReadSoFar ); 
            piowi->pbDATAFromClient[piowi->cbReadSoFar] = 0;  
            strcat( pbData, piowi->pbDATAFromClient); 
            SendMSGToClient( pecb, pbData); 
             
 
            AR_Free( piowi->pecb, pbData); 
            DoCleanUp( piowi); 
             
            return; 
             
        } else { 
 
            //  
            // Still have more data to read...  
            //  
            // We need to first check if the size of the remaining chunk  
            // is less than MAX_BUF_SIZE, if so just read what is available,  
            // otherwise read MAX_BUF_SIZE bytes of data. 
            //  
 
            cbTotalToRead = pecb->cbTotalBytes - piowi->cbReadSoFar; 
            if ( cbTotalToRead > MAX_BUF_SIZE )  { 
 
                cbTotalToRead = MAX_BUF_SIZE; 
            } 
             
            //  
            // Fire off another call to perform an asynchronus read from the client.  
            // 
 
            dwFlags = HSE_IO_ASYNC; 
            fReturn =  
            pecb->ServerSupportFunction( 
                          pecb->ConnID 
                        , HSE_REQ_ASYNC_READ_CLIENT 
                        , piowi->pbDATAFromClient + // append the new chunk to buffer, cbReadSoFar indexes 
                          piowi->cbReadSoFar        // the byte right after the last written byte in the buffer 
                        , &cbTotalToRead 
                        , &dwFlags); 
 
            if ( !fReturn) { 
                wsprintf ( szTmp, "Problem occurred at ServerSupportFunction() sending HSE_REQ_ASYNC_READ_CLIENT request."); 
                SendMSGToClient( pecb, szTmp); 
 
                DoCleanUp( piowi); 
                 
                return; 
            } 
 
        } 
     
    } else { 
 
        // 
        // Error on read 
        // 
 
        SetLastError(dwError); 
         
        DoCleanUp( piowi); 
    } 
         
    return; 
         
} // AsyncReadClientIoCompletion 
 
 
 
 
DWORD 
DoInit(IN OUT PIOWI piowi) 
/*++ 
 
Routine Description: 
 
    Initialize the  Grand data holder, assign the first chunk(read-ahead chunk) 
    and update the index (cbRreadSoFar) of the grand data holder. 
     
Arguments: 
 
    piowi       pointer to the work item 
 
fReturn Value: 
 
    HSE_STATUS_SUCCESS or HSE_STATUS_ERROR 
 
--*/ 
{ 
 
    piowi->pbDATAFromClient =  
    (PBYTE) AR_Allocate( piowi->pecb, piowi->pecb->cbTotalBytes + MAX_BUF_SIZE); 
     
    if ( NULL == piowi->pbDATAFromClient) { 
 
        SetLastError( ERROR_NOT_ENOUGH_MEMORY); 
        return (HSE_STATUS_ERROR); 
    } 
 
    // 
    // The first chunk (read-ahead chunk) has arrived.   
    // 
     
    strcpy ( piowi->pbDATAFromClient, piowi->pecb->lpbData); 
    piowi->cbReadSoFar = piowi->pecb->cbAvailable; 
 
    return (HSE_STATUS_SUCCESS); 
} 
 
 
 
 
VOID 
DoCleanUp(IN PIOWI piowi) 
/*++ 
 
Routine Description: 
 
    End the session with IIS and clean up other previous allocated resources. 
     
Arguments: 
 
    piowi       pointer to the work item 
 
fReturn Value: 
 
    None 
 
--*/ 
{ 
     
     
    if ( piowi->pbDATAFromClient != NULL) { 
 
        AR_Free( piowi->pecb, piowi->pbDATAFromClient); 
 
    } 
 
    piowi->pecb->ServerSupportFunction( piowi->pecb->ConnID, 
                                        HSE_REQ_DONE_WITH_SESSION, 
                                        NULL, 
                                        NULL, 
                                        NULL); 
 
    LocalFree( piowi); 
 
    return; 
 
} 
 
 
 
 
LPVOID  
AR_Allocate(IN LPEXTENSION_CONTROL_BLOCK pecb, IN DWORD dwSize) 
/*++ 
 
Routine Description: 
 
    Memory allocation routine. Two different Win32 API's to allocate 
    bytes in memory, which is based on the number of bytes coming from  
    the client. If the size is greater than 1 M bytes VirtualAllocate is  
    used, otherwise HeapAllocate is used. 
     
Arguments: 
 
    pecb        - pointer to ECB containing parameters related to the request. 
    dwSize      - number of bytes to allocate 
 
fReturn Value: 
 
    Pointer to void 
 
--*/ 
{ 
    LPVOID pvData = NULL; 
     
 
    if ( pecb->cbTotalBytes > MEM_ALLOC_THRESHOLD) { 
 
        pvData =  
        VirtualAlloc( NULL,  
                      dwSize, 
                      MEM_RESERVE | MEM_COMMIT,  
                      PAGE_READWRITE); 
 
    } else { 
 
        pvData =  
        HeapAlloc( GetProcessHeap(),  
                   HEAP_ZERO_MEMORY, 
                   dwSize); 
                            
    } 
 
    return ( pvData); 
 
} 
 
 
 
 
BOOL 
AR_Free( IN LPEXTENSION_CONTROL_BLOCK pecb, IN LPVOID pvData) 
/*++ 
 
Routine Description: 
 
    Freeing memory routine, a complementary routine to AR_Allocate.  
    Two different Win32 API's will be used to free up bytes in memory, 
    which is based on the number of bytes coming from the client. If  
    the size is greater than 1 M bytes VirtualFree is used. Otherwise,  
    HeapFree is used. 
     
Arguments: 
 
    pecb        - pointer to ECB containing parameters related to the request. 
    pvData      - pointer to the data to be freed. 
 
fReturn Value: 
 
    TRUE or FALSE 
 
--*/ 
{ 
    BOOL fReturn = FALSE; 
 
 
    if ( pecb->cbTotalBytes > MEM_ALLOC_THRESHOLD) { 
         
        fReturn = VirtualFree( pvData, 0, MEM_RELEASE); 
 
    } else { 
 
        fReturn = HeapFree( GetProcessHeap(), 0, pvData); 
    } 
 
    return (fReturn); 
}