Partager via


Exemple d’application asynchrone

L’exemple suivant illustre l’envoi d’une requête de manière asynchrone.

 /*++
 Copyright (C) Microsoft.  All Rights Reserved.

--*/

#include "async.h"

#pragma warning(disable:4127) // Conditional expression is constant


int __cdecl 
wmain(
    __in int argc,
    __in_ecount(argc) LPWSTR *argv
    )
{
    DWORD Error;
    DWORD OpenType = INTERNET_OPEN_TYPE_PRECONFIG; // Use pre-configured options as default

    CONFIGURATION Configuration = {0};

    PREQUEST_CONTEXT ReqContext = NULL;
    HINTERNET SessionHandle = NULL;

    // Callback function
    INTERNET_STATUS_CALLBACK CallbackPointer;


    // Parse the command line arguments
    Error = ParseArguments(argc, argv, &Configuration);
    if(Error != ERROR_SUCCESS)
    {
        ShowUsage();
        goto Exit; 
    }

    if(Configuration.UseProxy)
    {
        OpenType = INTERNET_OPEN_TYPE_PROXY;
    }
    
    // Create Session handle and specify async Mode
    SessionHandle = InternetOpen(L"WinInet HTTP Async Sample",  // User Agent
                                 OpenType,                      // Preconfig or Proxy
                                 Configuration.ProxyName,       // Proxy name
                                 NULL,                          // Proxy bypass, do not bypass any address
                                 INTERNET_FLAG_ASYNC);          // 0 for Synchronous
    
    if (SessionHandle == NULL)
    {
        LogInetError(GetLastError(), L"InternetOpen");
        goto Exit;
    }


    // Set the status callback for the handle to the Callback function
    CallbackPointer = InternetSetStatusCallback(SessionHandle, 
                                                (INTERNET_STATUS_CALLBACK)CallBack);

    if (CallbackPointer == INTERNET_INVALID_STATUS_CALLBACK)
    {
        fprintf(stderr, "InternetSetStatusCallback failed with INTERNET_INVALID_STATUS_CALLBACK\n");
        goto Exit;
    }


    
    // Initialize the ReqContext to be used in the asynchronous calls
    Error = AllocateAndInitializeRequestContext(SessionHandle,
                                                &Configuration,
                                                &ReqContext);
    if (Error != ERROR_SUCCESS)
    {
        fprintf(stderr, "AllocateAndInitializeRequestContext failed with error %d\n", Error);
        goto Exit;
    }

    //
    // Send out request and receive response
    //
    
    ProcessRequest(ReqContext, ERROR_SUCCESS);


    //
    // Wait for request completion or timeout
    //

    WaitForRequestCompletion(ReqContext, Configuration.UserTimeout);
    

Exit:

    // Clean up the allocated resources
    CleanUpRequestContext(ReqContext);

    CleanUpSessionHandle(SessionHandle);

    return (Error != ERROR_SUCCESS) ? 1 : 0;
}

VOID CALLBACK 
CallBack(
    __in HINTERNET hInternet,
    __in DWORD_PTR dwContext,
    __in DWORD dwInternetStatus,
    __in_bcount(dwStatusInformationLength) LPVOID lpvStatusInformation,
    __in DWORD dwStatusInformationLength
    )
/*++

Routine Description:
    Callback routine for asynchronous WinInet operations

Arguments:
     hInternet - The handle for which the callback function is called.
     dwContext - Pointer to the application defined context.
     dwInternetStatus - Status code indicating why the callback is called.
     lpvStatusInformation - Pointer to a buffer holding callback specific data.
     dwStatusInformationLength - Specifies size of lpvStatusInformation buffer.

Return Value:
    None.

--*/
{
    InternetCookieHistory cookieHistory;
    PREQUEST_CONTEXT ReqContext = (PREQUEST_CONTEXT)dwContext;
 
    UNREFERENCED_PARAMETER(dwStatusInformationLength);

    fprintf(stderr, "Callback Received for Handle %p \t", hInternet);
    
    switch(dwInternetStatus)
    {
        case INTERNET_STATUS_COOKIE_SENT:
            fprintf(stderr, "Status: Cookie found and will be sent with request\n");
            break;
            
        case INTERNET_STATUS_COOKIE_RECEIVED:
            fprintf(stderr, "Status: Cookie Received\n");
            break;
            
        case INTERNET_STATUS_COOKIE_HISTORY:

            fprintf(stderr, "Status: Cookie History\n");

            ASYNC_ASSERT(lpvStatusInformation);
            ASYNC_ASSERT(dwStatusInformationLength == sizeof(InternetCookieHistory));

            cookieHistory = *((InternetCookieHistory*)lpvStatusInformation);
            
            if(cookieHistory.fAccepted)
            {
                fprintf(stderr, "Cookie Accepted\n");
            }
            if(cookieHistory.fLeashed)
            {
                fprintf(stderr, "Cookie Leashed\n");
            }        
            if(cookieHistory.fDowngraded)
            {
                fprintf(stderr, "Cookie Downgraded\n");
            }        
            if(cookieHistory.fRejected)
            {
                fprintf(stderr, "Cookie Rejected\n");
            }

 
            break;
            
        case INTERNET_STATUS_CLOSING_CONNECTION:
            fprintf(stderr, "Status: Closing Connection\n");
            break;
            
        case INTERNET_STATUS_CONNECTED_TO_SERVER:
            fprintf(stderr, "Status: Connected to Server\n");
            break;
            
        case INTERNET_STATUS_CONNECTING_TO_SERVER:
            fprintf(stderr, "Status: Connecting to Server\n");
            break;
            
        case INTERNET_STATUS_CONNECTION_CLOSED:
            fprintf(stderr, "Status: Connection Closed\n");
            break;
            
        case INTERNET_STATUS_HANDLE_CLOSING:
            fprintf(stderr, "Status: Handle Closing\n");

            //
            // Signal the cleanup routine that it is
            // safe to cleanup the request context
            //
            
            ASYNC_ASSERT(ReqContext);
            SetEvent(ReqContext->CleanUpEvent);
            
            break;
            
        case INTERNET_STATUS_HANDLE_CREATED:
            ASYNC_ASSERT(lpvStatusInformation);
            fprintf(stderr,
                    "Handle %x created\n", 
                    ((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwResult);

            break;
            
        case INTERNET_STATUS_INTERMEDIATE_RESPONSE:
            fprintf(stderr, "Status: Intermediate response\n");
            break;
            
        case INTERNET_STATUS_RECEIVING_RESPONSE:
            fprintf(stderr, "Status: Receiving Response\n");    
            break;
            
        case INTERNET_STATUS_RESPONSE_RECEIVED:
            ASYNC_ASSERT(lpvStatusInformation);
            ASYNC_ASSERT(dwStatusInformationLength == sizeof(DWORD));
            
            fprintf(stderr, "Status: Response Received (%d Bytes)\n", *((LPDWORD)lpvStatusInformation));
            
            break;
            
        case INTERNET_STATUS_REDIRECT:
            fprintf(stderr, "Status: Redirect\n");
            break;

        case INTERNET_STATUS_REQUEST_COMPLETE:
            fprintf(stderr, "Status: Request complete\n");

            ASYNC_ASSERT(lpvStatusInformation);
            
            ProcessRequest(ReqContext, ((LPINTERNET_ASYNC_RESULT)lpvStatusInformation)->dwError);

            break;
            
        case INTERNET_STATUS_REQUEST_SENT:
            ASYNC_ASSERT(lpvStatusInformation);
            ASYNC_ASSERT(dwStatusInformationLength == sizeof(DWORD));

            fprintf(stderr,"Status: Request sent (%d Bytes)\n", *((LPDWORD)lpvStatusInformation));
            break;
            
        case INTERNET_STATUS_DETECTING_PROXY:
            fprintf(stderr, "Status: Detecting Proxy\n");
            break;            
            
        case INTERNET_STATUS_RESOLVING_NAME:
            fprintf(stderr, "Status: Resolving Name\n");
            break;
            
        case INTERNET_STATUS_NAME_RESOLVED:
            fprintf(stderr, "Status: Name Resolved\n");
            break;
            
        case INTERNET_STATUS_SENDING_REQUEST:
            fprintf(stderr, "Status: Sending request\n");
            break;
            
        case INTERNET_STATUS_STATE_CHANGE:
            fprintf(stderr, "Status: State Change\n");
            break;
            
        case INTERNET_STATUS_P3P_HEADER:
            fprintf(stderr, "Status: Received P3P header\n");
            break;
            
        default:
            fprintf(stderr, "Status: Unknown (%d)\n", dwInternetStatus);
            break;
    }

    return;
}


VOID
ProcessRequest(
    __inout PREQUEST_CONTEXT ReqContext,
    __in DWORD Error
    )
/*++

Routine Description:
    Process the request context - Sending the request and
    receiving the response

Arguments:
    ReqContext - Pointer to request context structure
    Error - error returned from last asynchronous call

Return Value:
    None.

--*/
{
    BOOL Eof = FALSE;
    
    ASYNC_ASSERT(ReqContext);

    
    while(Error == ERROR_SUCCESS && ReqContext->State != REQ_STATE_COMPLETE)
    {
        
        switch(ReqContext->State)
        {
            case REQ_STATE_SEND_REQ:

                ReqContext->State = REQ_STATE_RESPONSE_RECV_DATA;
                Error = SendRequest(ReqContext);
                
                break;

            case REQ_STATE_SEND_REQ_WITH_BODY: 
                
                ReqContext->State = REQ_STATE_POST_GET_DATA;
                Error = SendRequestWithBody(ReqContext);                

                break;
                
            case REQ_STATE_POST_GET_DATA:
                
                ReqContext->State = REQ_STATE_POST_SEND_DATA;
                Error = GetDataToPost(ReqContext);
                
                break;
                
            case REQ_STATE_POST_SEND_DATA:
                
                ReqContext->State = REQ_STATE_POST_GET_DATA;
                Error = PostDataToServer(ReqContext, &Eof);
                
                if(Eof)
                {
                    ASYNC_ASSERT(Error == ERROR_SUCCESS);
                    ReqContext->State = REQ_STATE_POST_COMPLETE;  
                }
                
                break;
                
            case REQ_STATE_POST_COMPLETE:     
                
                ReqContext->State = REQ_STATE_RESPONSE_RECV_DATA;
                Error = CompleteRequest(ReqContext);
                
                break;                
                
            case REQ_STATE_RESPONSE_RECV_DATA: 
                
                ReqContext->State = REQ_STATE_RESPONSE_WRITE_DATA;
                Error = RecvResponseData(ReqContext);
                
                break;
                
            case REQ_STATE_RESPONSE_WRITE_DATA:

                ReqContext->State = REQ_STATE_RESPONSE_RECV_DATA;
                Error = WriteResponseData(ReqContext, &Eof);

                if(Eof)
                {
                    ASYNC_ASSERT(Error == ERROR_SUCCESS);
                    ReqContext->State = REQ_STATE_COMPLETE;
                }
                
                break;
                
            default:
                
                ASYNC_ASSERT(FALSE);
                
                break;
        }
    }

    if(Error != ERROR_IO_PENDING)
    {
        //
        // Everything has been procesed or has failed. 
        // In either case, the signal processing has
        // completed
        //
        
        SetEvent(ReqContext->CompletionEvent);
    }

    return;
}

DWORD
SendRequest(
    __in PREQUEST_CONTEXT ReqContext
    )
/*++

Routine Description:
    Send the request using HttpSendRequest

Arguments:
    ReqContext - Pointer to request context structure

Return Value:
    Error code for the operation.

--*/
{
    BOOL Success;
    DWORD Error = ERROR_SUCCESS;
    

    ASYNC_ASSERT(ReqContext->Method == METHOD_GET);

    Success = AcquireRequestHandle(ReqContext);
    if(!Success)
    {
        Error = ERROR_OPERATION_ABORTED;
        goto Exit;
    }
    
    Success = HttpSendRequest(ReqContext->RequestHandle,
                              NULL,                   // do not provide additional Headers
                              0,                      // dwHeadersLength 
                              NULL,                   // Do not send any data 
                              0);                     // dwOptionalLength 

    ReleaseRequestHandle(ReqContext);

    if (!Success)
    {
        Error = GetLastError();

        if(Error != ERROR_IO_PENDING)
        {
            LogInetError(Error, L"HttpSendRequest");
        }
        goto Exit;
    }

Exit:

    return Error;
}


DWORD
SendRequestWithBody(
    __in PREQUEST_CONTEXT ReqContext
    )
/*++

Routine Description:
    Send the request with entity-body using HttpSendRequestEx

Arguments:
    ReqContext - Pointer to request context structure

Return Value:
    Error code for the operation.

--*/
{
    BOOL Success;
    INTERNET_BUFFERS BuffersIn;    
    DWORD Error = ERROR_SUCCESS;
    
    //
    // HttpSendRequest can also be used also to post data to a server, 
    // to do so, the data should be provided using the lpOptional
    // parameter and it's size on dwOptionalLength.
    // Here we decided to depict the use of HttpSendRequestEx function.
    //
    

    ASYNC_ASSERT(ReqContext->Method == METHOD_POST);
    
    //Prepare the Buffers to be passed to HttpSendRequestEx
    ZeroMemory(&BuffersIn, sizeof(INTERNET_BUFFERS));
    BuffersIn.dwStructSize = sizeof(INTERNET_BUFFERS);
    BuffersIn.lpvBuffer = NULL;
    BuffersIn.dwBufferLength = 0;
    BuffersIn.dwBufferTotal = ReqContext->FileSize; // content-length of data to post


    Success = AcquireRequestHandle(ReqContext);
    if(!Success)
    {
        Error = ERROR_OPERATION_ABORTED;
        goto Exit;
    }
    
    Success = HttpSendRequestEx(ReqContext->RequestHandle,
                                &BuffersIn,
                                NULL,                 // Do not use output buffers
                                0,                    // dwFlags reserved
                                (DWORD_PTR)ReqContext);

    ReleaseRequestHandle(ReqContext);

    if (!Success)
    {
        Error = GetLastError();

        if(Error != ERROR_IO_PENDING)
        {
            LogInetError(Error, L"HttpSendRequestEx");
        }
        
        goto Exit;
    }
    
Exit:    

    return Error;
}

DWORD 
GetDataToPost(
    __inout PREQUEST_CONTEXT ReqContext
    )
/*++

Routine Description:
    Reads data from a file

Arguments:
    ReqContext - Pointer to request context structure

Return Value:
    Error code for the operation.

--*/
{
    DWORD Error = ERROR_SUCCESS;
    BOOL Success;


    //
    //
    // ReadFile is done inline here assuming that it will return quickly
    // I.E. the file is on disk
    //
    // If you plan to do blocking/intensive operations they should be
    // queued to another thread and not block the callback thread
    //
    //

    Success = ReadFile(ReqContext->UploadFile,
                       ReqContext->OutputBuffer,
                       BUFFER_LEN,
                       &ReqContext->ReadBytes,
                       NULL);
    if(!Success)
    {
        Error = GetLastError();
        LogSysError(Error, L"ReadFile");
        goto Exit;
    }

    
Exit:    
 
    return Error;
}

DWORD 
PostDataToServer(
    __inout PREQUEST_CONTEXT ReqContext,
    __out PBOOL Eof
    )
/*++

Routine Description:
    Post data in the http request

Arguments:
    ReqContext - Pointer to request context structure
    Eof - Done posting data to server

Return Value:
    Error code for the operation.

--*/
{
    DWORD Error = ERROR_SUCCESS;
    BOOL Success;

    *Eof = FALSE;

    if(ReqContext->ReadBytes == 0)
    {
        *Eof = TRUE;
        goto Exit;
    }

        
    Success = AcquireRequestHandle(ReqContext);
    if(!Success)
    {
        Error = ERROR_OPERATION_ABORTED;
        goto Exit;
    }


    //
    // The lpdwNumberOfBytesWritten parameter will be
    // populated on async completion, so it must exist
    // until INTERNET_STATUS_REQUEST_COMPLETE.
    // The same is true of lpBuffer parameter.
    //

    Success = InternetWriteFile(ReqContext->RequestHandle,
                                ReqContext->OutputBuffer,
                                ReqContext->ReadBytes,
                                &ReqContext->WrittenBytes);
    

    ReleaseRequestHandle(ReqContext);
    
    if(!Success)
    {
        Error = GetLastError();
        
        if(Error == ERROR_IO_PENDING)
        {
            fprintf(stderr, "Waiting for InternetWriteFile to complete\n");                
        }
        else
        {
            LogInetError(Error, L"InternetWriteFile");
        }
        
        goto Exit;
        
    }
 
    

Exit:
    
 
    return Error;
}


DWORD 
CompleteRequest(
    __inout PREQUEST_CONTEXT ReqContext
    )
/*++

Routine Description:
    Perform completion of asynchronous post.

Arguments:
    ReqContext - Pointer to request context structure

Return Value:
    Error Code for the operation.

--*/
{

    DWORD Error = ERROR_SUCCESS;
    BOOL Success;

    fprintf(stderr, "Finished posting file\n");
    
    Success = AcquireRequestHandle(ReqContext);
    if(!Success)
    {
        Error = ERROR_OPERATION_ABORTED;
        goto Exit;
    }   

    Success = HttpEndRequest(ReqContext->RequestHandle, NULL, 0, 0);

    ReleaseRequestHandle(ReqContext);
    
    if(!Success)
    {
        Error = GetLastError();
        if(Error == ERROR_IO_PENDING)
        {
            fprintf(stderr, "Waiting for HttpEndRequest to complete \n");
        }
        else
        {
            LogInetError(Error, L"HttpEndRequest");
            goto Exit;
        }
    }

Exit:
    
    return Error;
}



DWORD
RecvResponseData(
    __inout PREQUEST_CONTEXT ReqContext
    )
/*++

Routine Description:
     Receive response

Arguments:
     ReqContext - Pointer to request context structure

Return Value:
     Error Code for the operation.

--*/
{
    DWORD Error = ERROR_SUCCESS;
    BOOL Success;

 

            
    Success = AcquireRequestHandle(ReqContext);
    if(!Success)
    {
        Error = ERROR_OPERATION_ABORTED;
        goto Exit;
    } 

    //
    // The lpdwNumberOfBytesRead parameter will be
    // populated on async completion, so it must exist
    // until INTERNET_STATUS_REQUEST_COMPLETE.
    // The same is true of lpBuffer parameter.
    //
    // InternetReadFile will block until the buffer
    // is completely filled or the response is exhausted.
    //

    
    Success = InternetReadFile(ReqContext->RequestHandle,
                               ReqContext->OutputBuffer,
                               BUFFER_LEN,
                               &ReqContext->DownloadedBytes);
                               
    ReleaseRequestHandle(ReqContext);
    
    if(!Success)
    {
        Error = GetLastError();
        if(Error == ERROR_IO_PENDING)
        {
            fprintf(stderr, "Waiting for InternetReadFile to complete\n");
        }
        else
        {
            LogInetError(Error, L"InternetReadFile");
        }
        
        goto Exit;
    }

    
Exit:

    return Error;
}


DWORD
WriteResponseData(
    __in PREQUEST_CONTEXT ReqContext,
    __out PBOOL Eof
    )
/*++

Routine Description:
     Write response to a file

Arguments:
     ReqContext - Pointer to request context structure
     Eof - Done with response

Return Value:
     Error Code for the operation.

--*/
{
    DWORD Error = ERROR_SUCCESS;
    DWORD BytesWritten = 0;
    
    BOOL Success;

     *Eof = FALSE;

    //
    // Finished receiving response
    //
    
    if (ReqContext->DownloadedBytes == 0)
    {
        *Eof = TRUE;
        goto Exit;
        
    }    
    
    //
    //
    // WriteFile is done inline here assuming that it will return quickly
    // I.E. the file is on disk
    //
    // If you plan to do blocking/intensive operations they should be
    // queued to another thread and not block the callback thread
    //
    //
    
    Success = WriteFile(ReqContext->DownloadFile,
                        ReqContext->OutputBuffer,
                        ReqContext->DownloadedBytes,
                        &BytesWritten,
                        NULL);
    
    if (!Success)
    {
        Error = GetLastError();

        LogSysError(Error, L"WriteFile");
        goto Exit;;
    }

    
Exit:

    return Error;
}





VOID
CloseRequestHandle(
    __inout PREQUEST_CONTEXT ReqContext
)
/*++

Routine Description:
    Safely  close the request handle by synchronizing
    with all threads using the handle.

    When this function returns no more calls can be made with the
    handle.
    
Arguments:
    ReqContext - Pointer to Request context structure
Return Value:
    None.

--*/
{
    BOOL Close = FALSE;
    
    EnterCriticalSection(&ReqContext->CriticalSection);

    //
    // Current implementation only supports the main thread
    // kicking off the request handle close
    //
    // To support multiple threads the lifetime 
    // of the request context must be carefully controlled
    // (most likely guarded by refcount/critsec)
    // so that they are not trying to abort a request
    // where the context has already been freed.
    //
    
    ASYNC_ASSERT(ReqContext->Closing == FALSE);
    ReqContext->Closing = TRUE;
    
    if(ReqContext->HandleUsageCount == 0)
    {
        Close = TRUE;
    }

    LeaveCriticalSection(&ReqContext->CriticalSection);



    if(Close)
    {
        //
        // At this point there must be the guarantee that all calls
        // to wininet with this handle have returned with some value
        // including ERROR_IO_PENDING, and none will be made after
        // InternetCloseHandle.
        //        
        (VOID)InternetCloseHandle(ReqContext->RequestHandle);
    }

    return;
}

BOOL
AcquireRequestHandle(
    __inout PREQUEST_CONTEXT ReqContext
)
/*++

Routine Description:
    Acquire use of the request handle to make a wininet call
Arguments:
    ReqContext - Pointer to Request context structure
Return Value:
    TRUE - Success
    FALSE - Failure
--*/
{
    BOOL Success = TRUE;

    EnterCriticalSection(&ReqContext->CriticalSection);

    if(ReqContext->Closing == TRUE)
    {
        Success = FALSE;        
    }
    else
    {
        ReqContext->HandleUsageCount++;
    }

    LeaveCriticalSection(&ReqContext->CriticalSection);
    
    return Success;
}


VOID
ReleaseRequestHandle(
    __inout PREQUEST_CONTEXT ReqContext
)
/*++

Routine Description:
    release use of the request handle
Arguments:
    ReqContext - Pointer to Request context structure
Return Value:
    None.

--*/
{
    BOOL Close = FALSE;

    EnterCriticalSection(&ReqContext->CriticalSection);

    ASYNC_ASSERT(ReqContext->HandleUsageCount > 0);
    
    ReqContext->HandleUsageCount--;
    
    if(ReqContext->Closing == TRUE && ReqContext->HandleUsageCount == 0)
    {
        Close = TRUE;

    }

    LeaveCriticalSection(&ReqContext->CriticalSection);


    if(Close)
    {
        //
        // At this point there must be the guarantee that all calls
        // to wininet with this handle have returned with some value
        // including ERROR_IO_PENDING, and none will be made after
        // InternetCloseHandle.
        //        
        (VOID)InternetCloseHandle(ReqContext->RequestHandle);
    }
    
    return;
}


DWORD 
AllocateAndInitializeRequestContext(
    __in HINTERNET SessionHandle,
    __in PCONFIGURATION Configuration,
    __deref_out PREQUEST_CONTEXT *ReqContext
    )
/*++

Routine Description:
    Allocate the request context and initialize it values

Arguments:
    ReqContext - Pointer to Request context structure
    Configuration - Pointer to configuration structure
    SessionHandle - Wininet session handle to use when creating
                    connect handle
    
Return Value:
    Error Code for the operation.

--*/
{
    DWORD Error = ERROR_SUCCESS;
    BOOL Success;
    PREQUEST_CONTEXT LocalReqContext;

    *ReqContext = NULL;

    LocalReqContext = (PREQUEST_CONTEXT) malloc(sizeof(REQUEST_CONTEXT));

    if(LocalReqContext == NULL)
    {
        Error = ERROR_NOT_ENOUGH_MEMORY;
        goto Exit;
    }
    
    LocalReqContext->RequestHandle = NULL;
    LocalReqContext->ConnectHandle = NULL;
    LocalReqContext->DownloadedBytes = 0;
    LocalReqContext->WrittenBytes = 0;
    LocalReqContext->ReadBytes = 0;    
    LocalReqContext->UploadFile = INVALID_HANDLE_VALUE;
    LocalReqContext->DownloadFile = INVALID_HANDLE_VALUE;
    LocalReqContext->FileSize = 0;
    LocalReqContext->HandleUsageCount = 0;
    LocalReqContext->Closing = FALSE;
    LocalReqContext->Method = Configuration->Method;
    LocalReqContext->CompletionEvent = NULL;    
    LocalReqContext->CleanUpEvent = NULL;
    LocalReqContext->OutputBuffer = NULL;
    LocalReqContext->State = 
        (LocalReqContext->Method == METHOD_GET) ?  REQ_STATE_SEND_REQ : REQ_STATE_SEND_REQ_WITH_BODY;
    LocalReqContext->CritSecInitialized = FALSE;    
    
       
    // initialize critical section

    Success = InitializeCriticalSectionAndSpinCount(&LocalReqContext->CriticalSection, SPIN_COUNT);
    
    if(!Success)
    {
        Error = GetLastError();
        LogSysError(Error, L"InitializeCriticalSectionAndSpinCount");
        goto Exit;
    }

    LocalReqContext->CritSecInitialized = TRUE;

    LocalReqContext->OutputBuffer = (LPSTR) malloc(BUFFER_LEN);
    
    if(LocalReqContext->OutputBuffer == NULL)
    {
        Error = ERROR_NOT_ENOUGH_MEMORY;
        goto Exit;
    }
        
    // create events
    LocalReqContext->CompletionEvent = CreateEvent(NULL,  // Sec attrib
                                                   FALSE, // Auto reset
                                                   FALSE, // Initial state unsignalled
                                                   NULL); // Name
    if(LocalReqContext->CompletionEvent == NULL)
    {
        Error = GetLastError();
        LogSysError(Error, L"CreateEvent CompletionEvent");
        goto Exit;
    }

    // create events
    LocalReqContext->CleanUpEvent = CreateEvent(NULL,  // Sec attrib
                                                FALSE, // Auto reset
                                                FALSE, // Initial state unsignalled
                                                NULL); // Name
    if(LocalReqContext->CleanUpEvent == NULL)
    {
        Error = GetLastError();
        LogSysError(Error, L"CreateEvent CleanUpEvent");
        goto Exit;
    }    
    
    // Open the file to dump the response entity body and
    // if required the file with the data to post
    Error = OpenFiles(LocalReqContext,
                      Configuration->Method, 
                      Configuration->InputFileName, 
                      Configuration->OutputFileName);
    
    if(Error != ERROR_SUCCESS)
    {
            fprintf(stderr, "OpenFiles failed with %d\n", Error);
            goto Exit;
    }

    // Verify if we've opened a file to post and get its size
    if (LocalReqContext->UploadFile != INVALID_HANDLE_VALUE)
    {
        LocalReqContext->FileSize = GetFileSize(LocalReqContext->UploadFile, NULL);
        if(LocalReqContext->FileSize == INVALID_FILE_SIZE)
        {
            Error = GetLastError();
            LogSysError(Error, L"GetFileSize");
            goto Exit;
        }
    }


    Error = CreateWininetHandles(LocalReqContext, 
                                 SessionHandle,
                                 Configuration->HostName,
                                 Configuration->ResourceOnServer,
                                 Configuration->IsSecureConnection);
    
    if(Error != ERROR_SUCCESS)
    {
        fprintf(stderr, "CreateWininetHandles failed with %d\n", Error);
        goto Exit;        
    }
    

    *ReqContext = LocalReqContext;
    
Exit:  
    
    if(Error != ERROR_SUCCESS)
    {
        CleanUpRequestContext(LocalReqContext);
    }
    
    return Error;
}


DWORD 
CreateWininetHandles(
    __inout PREQUEST_CONTEXT ReqContext,
    __in HINTERNET SessionHandle,
    __in LPWSTR HostName,
    __in LPWSTR Resource,
    __in BOOL IsSecureConnection
    )
/*++

Routine Description:
    Create connect and request handles

Arguments:
    ReqContext - Pointer to Request context structure
    SessionHandle - Wininet session handle used to create
                    connect handle
    HostName - Hostname to connect
    Resource - Resource to get/post
    IsSecureConnection - SSL?

Return Value:
    Error Code for the operation.

--*/
{
    DWORD Error = ERROR_SUCCESS;
    INTERNET_PORT ServerPort = INTERNET_DEFAULT_HTTP_PORT;
    DWORD RequestFlags = 0;
    LPWSTR Verb;


    //
    // Set the correct server port if using SSL
    // Also set the flag for HttpOpenRequest 
    //
    
    if(IsSecureConnection)
    {
        ServerPort = INTERNET_DEFAULT_HTTPS_PORT;
        RequestFlags = INTERNET_FLAG_SECURE;
    }    

    // Create Connection handle and provide context for async operations
    ReqContext->ConnectHandle = InternetConnect(SessionHandle,
                                                HostName,                  // Name of the server to connect to
                                                ServerPort,                // HTTP (80) or HTTPS (443)
                                                NULL,                      // Do not provide a user name for the server
                                                NULL,                      // Do not provide a password for the server
                                                INTERNET_SERVICE_HTTP,
                                                0,                         // Do not provide any special flag
                                                (DWORD_PTR)ReqContext);    // Provide the context to be
                                                                           // used during the callbacks
    //                                                                        
    // For HTTP InternetConnect returns synchronously because it does not
    // actually make the connection.
    //
    // For FTP InternetConnect connects the control channel, and therefore
    // can be completed asynchronously.  This sample would have to be
    // changed, so that the InternetConnect's asynchronous completion
    // is handled correctly to support FTP.
    //
    
    if (ReqContext->ConnectHandle == NULL)
    {
        Error = GetLastError();
        LogInetError(Error, L"InternetConnect");
        goto Exit;
    }


    // Set the Verb depending on the operation to perform
    if(ReqContext->Method == METHOD_GET)
    {
        Verb = L"GET";
    }
    else
    {
        ASYNC_ASSERT(ReqContext->Method == METHOD_POST);
        
        Verb = L"POST";
    }

    //
    // We're overriding WinInet's default behavior.
    // Setting these flags, we make sure we get the response from the server and not the cache.
    // Also ask WinInet not to store the response in the cache.
    //
    // These flags are NOT performant and are only used to show case WinInet's Async I/O.
    // A real WinInet application would not want to use this flags.
    //
    
    RequestFlags |= INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE;

    // Create a Request handle
    ReqContext->RequestHandle = HttpOpenRequest(ReqContext->ConnectHandle,
                                                Verb,                     // GET or POST
                                                Resource,                 // root "/" by default
                                                NULL,                     // Use default HTTP/1.1 as the version
                                                NULL,                     // Do not provide any referrer
                                                NULL,                     // Do not provide Accept types
                                                RequestFlags,
                                                (DWORD_PTR)ReqContext);
    
    if (ReqContext->RequestHandle == NULL)
    {
        Error = GetLastError();
        LogInetError(Error, L"HttpOpenRequest");
        
        goto Exit;
    }
    
    
Exit:
    
    return Error;
}

VOID
WaitForRequestCompletion(
    __in PREQUEST_CONTEXT ReqContext,
    __in DWORD Timeout
)
/*++

Routine Description:
    Wait for the request to complete or timeout to occur

Arguments:
    ReqContext - Pointer to request context structure

Return Value:
    None.

--*/
{
    DWORD SyncResult;

    //
    // The preferred method of doing timeouts is to
    // use the timeout options through InternetSetOption,
    // but this overall timeout is being used to show 
    // the correct way to abort and close a request.
    //
    
    SyncResult = WaitForSingleObject(ReqContext->CompletionEvent,
                                     Timeout);              // Wait until we receive the completion
    
    switch(SyncResult)
    {
        case WAIT_OBJECT_0:
            
            printf("Done!\n");
            break;
            
        case WAIT_TIMEOUT:

            fprintf(stderr,
                    "Timeout while waiting for completion event (request will be cancelled)\n");
            break;
            
        case WAIT_FAILED:
            
            fprintf(stderr,
                    "Wait failed with Error %d while waiting for completion event (request will be cancelled)\n", 
                    GetLastError());
            break;
            
        default:
            // Not expecting any other error codes
            ASYNC_ASSERT(FALSE);
            break;
            
 
    }

    return;
}
    
VOID 
CleanUpRequestContext(
    __inout_opt PREQUEST_CONTEXT ReqContext
    )
/*++

Routine Description:
    Used to cleanup the request context before exiting.

Arguments:
    ReqContext - Pointer to request context structure

Return Value:
    None.

--*/
{
    if(ReqContext == NULL)
    {
        goto Exit;
    }

    if(ReqContext->RequestHandle) 
    {
        CloseRequestHandle(ReqContext);
        
        //
        // Wait for the closing of the handle to complete
        // (waiting for all async operations to complete)
        //
        // This is the only safe way to get rid of the context
        //
        
        (VOID)WaitForSingleObject(ReqContext->CleanUpEvent, INFINITE);           
    }
    
    if(ReqContext->ConnectHandle)
    {
        //
        // Remove the callback from the ConnectHandle since
        // we don't want the closing notification
        // The callback was inherited from the session handle
        //
        (VOID)InternetSetStatusCallback(ReqContext->ConnectHandle, 
                                        NULL);

        (VOID)InternetCloseHandle(ReqContext->ConnectHandle);
    }    

    if(ReqContext->UploadFile != INVALID_HANDLE_VALUE) 
    {
        CloseHandle(ReqContext->UploadFile);
    }
    
    if(ReqContext->DownloadFile != INVALID_HANDLE_VALUE) 
    {
        CloseHandle(ReqContext->DownloadFile);
    }

    if(ReqContext->CompletionEvent) 
    {
        CloseHandle(ReqContext->CompletionEvent);
    }

    if(ReqContext->CleanUpEvent) 
    {
        CloseHandle(ReqContext->CleanUpEvent);
    }
    
    if(ReqContext->CritSecInitialized)
    {
        DeleteCriticalSection(&ReqContext->CriticalSection);
    }
    
    if(ReqContext->OutputBuffer)
    {
        free(ReqContext->OutputBuffer);
    }
    

    
    free(ReqContext);
    
    
Exit:
    
    return;
}



VOID 
CleanUpSessionHandle(
    __in HINTERNET SessionHandle
    )
/*++

Routine Description:
    Used to cleanup session before exiting.

Arguments:
    SessionHandle - Wininet session handle

Return Value:
    None.

--*/
{

    if(SessionHandle) 
    {
        //
        // Remove the callback from the SessionHandle since
        // we don't want the closing notification
        //
        (VOID)InternetSetStatusCallback(SessionHandle, 
                                        NULL);
        //
        // Call InternetCloseHandle and do not wait for the closing notification 
        // in the callback function
        //
        (VOID)InternetCloseHandle(SessionHandle);

    }

    return;
}


DWORD 
OpenFiles(
    __inout PREQUEST_CONTEXT ReqContext,
    __in DWORD Method,
    __in LPWSTR InputFileName,
    __in LPWSTR OutputFileName    
    )
/*++

Routine Description:
    This routine opens files, one to post data from, and
    one to write response into

Arguments:
    ReqContext - Pointer to request context structure
    Method - GET or POST - do we need to open the input file
    InputFileName - Input file name
    OutputFileName - output file name

Return Value:
    Error Code for the operation.

--*/
{
    DWORD Error = ERROR_SUCCESS;
    
    if (Method == METHOD_POST)
    {
        // Open input file
        ReqContext->UploadFile = CreateFile(InputFileName,
                                            GENERIC_READ,
                                            FILE_SHARE_READ, 
                                            NULL,                   // handle cannot be inherited
                                            OPEN_ALWAYS,            // if file exists, open it
                                            FILE_ATTRIBUTE_NORMAL,
                                            NULL);                  // No template file

        if (ReqContext->UploadFile == INVALID_HANDLE_VALUE)
        {
            Error = GetLastError();
            LogSysError(Error, L"CreateFile for input file");
            goto Exit;
        }
    }

    // Open output file
    ReqContext->DownloadFile = CreateFile(OutputFileName,
                                          GENERIC_WRITE,
                                          0,                        // Open exclusively
                                          NULL,                     // handle cannot be inherited
                                          CREATE_ALWAYS,            // if file exists, delete it
                                          FILE_ATTRIBUTE_NORMAL,
                                          NULL);                    // No template file
    
    if (ReqContext->DownloadFile == INVALID_HANDLE_VALUE)
    {
            Error = GetLastError();
            LogSysError(Error, L"CreateFile for output file");
            goto Exit;
    }
    
Exit:
    return Error;
}


DWORD 
ParseArguments(
    __in int argc,
    __in_ecount(argc) LPWSTR *argv,
    __inout PCONFIGURATION Configuration
    )
/*++

Routine Description:
     This routine is used to Parse command line arguments. Flags are
     case sensitive.

Arguments:
     argc - Number of arguments
     argv - Pointer to the argument vector
     Configuration - pointer to configuration struct to write configuration

Return Value:
    Error Code for the operation.

--*/
{ 
    int i;
    DWORD Error = ERROR_SUCCESS;

    for (i = 1; i < argc; ++i)
    {        
        if (wcsncmp(argv[i], L"-", 1))
        {
            printf("Invalid switch %ws\n", argv[i]);
            i++;
            continue;
        }
        
        switch(argv[i][1])
        {            
            case L'p':
                
                Configuration->UseProxy = 1;
                if (i < argc - 1)
                {
                    Configuration->ProxyName = argv[++i];
                }
                break;
                
            case L'h':
                
                if (i < argc - 1)
                {
                    Configuration->HostName = argv[++i];
                }
                
                break;
                
            case L'o':
                
                if (i < argc - 1)
                {
                    Configuration->ResourceOnServer = argv[++i];
                }
                
                break;
                
            case L'r':
            
                if (i < argc - 1)
                {
                    Configuration->InputFileName = argv[++i];
                }
                
                break;

            case L'w':
                
                if (i < argc - 1)
                {
                    Configuration->OutputFileName = argv[++i];
                }
                
                break;
            
            case L'm':
                
                if (i < argc - 1)
                {
                    if (!_wcsnicmp(argv[i + 1], L"get", 3))
                    {
                        Configuration->Method = METHOD_GET;
                    }
                    else if (!_wcsnicmp(argv[i + 1], L"post", 4))
                    {
                        Configuration->Method = METHOD_POST;
                    }
                }
                ++i;
                break;

            case L's':
                Configuration->IsSecureConnection = TRUE;
                break;

            case L't':
                if (i < argc - 1)
                {
                    Configuration->UserTimeout = _wtoi(argv[++i]);
                }
                break;      

            default:
                Error = ERROR_INVALID_PARAMETER;
                break;
        }
    }

    if(Error == ERROR_SUCCESS)
    {
        if(Configuration->UseProxy && Configuration->ProxyName == NULL)
        {
            printf("No proxy server name provided!\n\n");
            Error = ERROR_INVALID_PARAMETER;
            goto Exit;
        }

        if(Configuration->HostName == NULL)
        {
            printf("Defaulting hostname to: %ws\n", DEFAULT_HOSTNAME);
            Configuration->HostName = DEFAULT_HOSTNAME;
        }

        if(Configuration->Method == METHOD_NONE)
        {
            printf("Defaulting method to: GET\n");
            Configuration->Method = METHOD_GET;
        }

        if(Configuration->ResourceOnServer == NULL)
        {
            printf("Defaulting resource to: %ws\n", DEFAULT_RESOURCE);
            Configuration->ResourceOnServer = DEFAULT_RESOURCE; 
        }

        if(Configuration->UserTimeout == 0)
        {
            printf("Defaulting timeout to: %d\n", DEFAULT_TIMEOUT);
            Configuration->UserTimeout = DEFAULT_TIMEOUT;         
        }

        if(Configuration->InputFileName == NULL && Configuration->Method == METHOD_POST)
        {
            printf("Error: File to post not specified\n");
            Error = ERROR_INVALID_PARAMETER;
            goto Exit;
        }
        
        if(Configuration->OutputFileName == NULL)
        {
            printf("Defaulting output file to: %ws\n", DEFAULT_OUTPUT_FILE_NAME);
            
            Configuration->OutputFileName = DEFAULT_OUTPUT_FILE_NAME;
        }

    }

Exit:    
    return Error;
}


VOID
ShowUsage(
    VOID
    )
/*++

Routine Description:
      Shows the usage of the application.

Arguments:
      None.

Return Value:
      None.

--*/
{
    printf("Usage: async [-m {GET|POST}] [-h <hostname>] [-o <resourcename>] [-s] ");
    printf("[-p <proxyname>] [-w <output filename>] [-r <file to post>] [-t <userTimeout>]\n");
    printf("Option Semantics: \n");
    printf("-m : Specify method (Default: \"GET\")\n");
    printf("-h : Specify hostname (Default: \"%ws\"\n", DEFAULT_HOSTNAME);
    printf("-o : Specify resource name on the server (Default: \"%ws\")\n", DEFAULT_RESOURCE);
    printf("-s : Use secure connection - https\n");
    printf("-p : Specify proxy\n");
    printf("-w : Specify file to write output to (Default: \"%ws\")\n", DEFAULT_OUTPUT_FILE_NAME);
    printf("-r : Specify file to post data from\n");
    printf("-t : Specify time to wait in ms for operation completion (Default: %d)\n", DEFAULT_TIMEOUT);
    
    return;
}

VOID 
LogInetError(
    __in DWORD Err,
    __in LPCWSTR Str
    )
/*++

Routine Description:
     This routine is used to log WinInet errors in human readable form.

Arguments:
     Err - Error number obtained from GetLastError()
     Str - String pointer holding caller-context information 

Return Value:
    None.

--*/
{
    DWORD Result;
    PWSTR MsgBuffer = NULL;
    
    Result = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                           FORMAT_MESSAGE_FROM_HMODULE,
                           GetModuleHandle(L"wininet.dll"),
                           Err,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                           (LPWSTR)&MsgBuffer,
                           ERR_MSG_LEN,
                           NULL);
    
    if (Result)
    {
        fprintf(stderr, "%ws: %ws\n", Str, MsgBuffer);
        LocalFree(MsgBuffer);
    }
    else
    {
        fprintf(stderr,
                "Error %d while formatting message for %d in %ws\n",
                GetLastError(),
                Err,
                Str);
    }

    return;
}

VOID
LogSysError(
    __in DWORD Err,
    __in LPCWSTR Str
    )
/*++

Routine Description:
     This routine is used to log System Errors in human readable form.

Arguments:
     Err - Error number obtained from GetLastError()
     Str - String pointer holding caller-context information 

Return Value:
    None.

--*/
{
    DWORD Result;
    PWSTR MsgBuffer = NULL;

    Result = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                           FORMAT_MESSAGE_FROM_SYSTEM,
                           NULL,
                           Err,
                           MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                           (LPWSTR)&MsgBuffer,
                           ERR_MSG_LEN,
                           NULL);
    
    if (Result)
    {
        fprintf(stderr,
                "%ws: %ws\n",
                Str,
                MsgBuffer);
       LocalFree(MsgBuffer);
    }
    else
    {
        fprintf(stderr,
                "Error %d while formatting message for %d in %ws\n",
                GetLastError(),
                Err,
                Str);    
    }

    return;
}