Asynchronous Example Application
The following example demonstrated sending a request asynchronously.
/*++ 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; }
Show: