C++ Asynchronous Http Get Request Program Example
Create a new CLR console application project and you might want to use AsyncHttpGetRequestCP as the solution name and project name as shown in the following Figure.
|
|
Add/edit the following using directives. You may discard the comments.
// This sample illustrates the asynchronous BeginGetResponseStream() method of the HttpWebRequest // class. This sample posts an asynchronous GET request for the given URI with a timeout value. // When the delegate is invoked, the response stream is retrieved and an asynchronous stream // read is posted. A single asynchronous stream read is posted at a time until the entire stream // has been received. For each stream read, a DataBuffer object is created which contains the // data read on the stream. Once the operation completes it is added to a list and an event is // signaled. The main thread waits on this event and when signaled walks the list of completed // DataBuffer objects and then writes them to disk. This is done to prevent a blocking write // call from occurring in the asynchronous delegate for the stream read call. // // Usage: // usage: Executable_file_name [-u URI] [-p proxy] // -u URI URI to download (along with linked content) // -p proxy Name of proxy server // // Sample usage: // Executable_file_name -u http://www.microsoft.com -p http://myproxy/
using namespace System; using namespace System::IO; using namespace System::Net; using namespace System::Text; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Threading; |
Add the DataBuffer class
public ref class DataBuffer { public: static const int BufferSize = 4096; array<Byte>^ byteBuffer; int bytesRead; FileStream^ saveFile; bool done;
/// <summary> /// Initialize the DataBuffer object by allocating the byte array. /// </summary> public: DataBuffer() { byteBuffer = gcnew array<Byte>(BufferSize); done = false; } }; |
Next, add the HttpRequestState class.
/// <summary> /// This class maintains the context information for each asynchronous web request. /// </summary> public ref class HttpRequestState { public: HttpWebRequest^ httpRequest; HttpWebResponse^ httpResponse; Stream^ httpResponseStream; DataBuffer^ readBuffer; ArrayList^ bufferList; ManualResetEvent^ bufferEvent; Uri^ getUri; FileStream^ saveFile; String^ localSavePath; int readCount; int requestTimeout;
public: HttpRequestState(HttpWebRequest^ httpObj, ArrayList^ list, ManualResetEvent^ dataEvent) { httpRequest = httpObj; httpResponse = nullptr; httpResponseStream = nullptr; bufferList = list; bufferEvent = dataEvent; readBuffer = gcnew DataBuffer(); readCount = 0; } }; |
Then, add the AsyncGetClass class.
ref class AsyncGetClass { public: Uri^ ResourceUri; IWebProxy^ HttpProxy; String^ LocalSavePath; bool RecursiveGet; ArrayList^ RetrievedUri; ArrayList^ PendingUri; ArrayList^ PendingBuffer; ManualResetEvent^ BufferEvent; int RequestTimeout;
public: AsyncGetClass() { // Change the uri to other for testing... ResourceUri = gcnew Uri("http://www.google.com/intl/en/images/about_logo.gif"); // ResourceUri = gcnew Uri("http://labs.google.com/index.html"); HttpProxy = WebRequest::DefaultWebProxy; LocalSavePath = "."; RecursiveGet = false; RetrievedUri = gcnew ArrayList(); PendingUri = gcnew ArrayList(); PendingBuffer = gcnew ArrayList(); BufferEvent = gcnew ManualResetEvent( false ); RequestTimeout = 10 * 1000; // 10 second timeout }
/// <summary> /// Displays simple usage information for the application. /// </summary> static void usage() { Console::WriteLine("Usage: Executable_file_name [-u URI] [-p proxy]"); Console::WriteLine("Available options:"); Console::WriteLine(" -u URI URI to download (along with linked content)"); Console::WriteLine(" -p proxy Name of proxy server"); Console::WriteLine(); }
/// <summary> /// Creates any subdirectories and opens the file for writing to. The path is stripped /// from the URI to build the local path to save the file to. This path is appended /// to the local save path supplied. A file is then opened with the same name as /// the retrieved file and the FileStream to it is returned. /// </summary> /// <param name="localSavePath">Local path to append saved resources paths to</param> /// <param name="uriName">URI of destination resource being saved</param> public: static FileStream^ CreateFile( String^ localSavePath, Uri^ uriName ) { FileStream^ localFile = nullptr; String^ fileName;
try { array<String^>^ uriSegments = uriName->Segments; String^ localDirs = "";
// Retrieve the directory path to the file Console::WriteLine("Retrieving the directory path to the file..."); for(int i=0; i < uriSegments->Length-1 ;i++) { localDirs += uriSegments[i]; }
if ( uriSegments->Length > 1 ) { // Replace the forward slashes with back slashes Console::WriteLine("Replacing the forward slashes with back slashes..."); localDirs = localDirs->Replace("/", "\\");
// Remove the escaped spaces Console::WriteLine("Removing the escaped spaces..."); String^ temp = localDirs->Replace("%20", " "); if ( temp != nullptr ) localDirs = temp; Console::WriteLine(" Creating directory: {0}", localSavePath + "\\" + localDirs ); Directory::CreateDirectory( localSavePath + "\\" + localDirs ); fileName = uriSegments[uriSegments->Length - 1]; } else { Console::WriteLine("Using default file name..."); localDirs = "\\"; fileName = "default.html"; }
// Open the file to write to Console::WriteLine("Opening the file to write to..."); String^ saveFileName = localSavePath + localDirs + fileName; localFile = File::Open( saveFileName, IO::FileMode::Create, IO::FileAccess::Write, IO::FileShare::None ); Console::WriteLine("Created File: {0}", saveFileName); } catch ( Exception^ ex ) { Console::WriteLine("WriteHttpContentToFile failed: {0}", ex->Message); Console::WriteLine("Stack:\n{0}", ex->StackTrace); if ( localFile != nullptr ) localFile->Close(); } return localFile; }
private: static void RequestTimeoutCallback( Object^ state, bool timedOut ) { if ( timedOut ) { Console::WriteLine("RequestTimeoutCallback timed out!"); HttpRequestState^ httpState = (HttpRequestState^)state;
if ( ( httpState != nullptr ) && ( httpState->httpRequest != nullptr ) ) { httpState->httpRequest->Abort(); } } }
private: static void HttpStreamReadCallback( IAsyncResult^ ar ) { HttpRequestState^ httpState = (HttpRequestState^) ar->AsyncState;
try { httpState->readBuffer->bytesRead = httpState->httpResponseStream->EndRead( ar ); Console::WriteLine("HttpStreamReadCallback() returned: {0} bytes read (content length = {1})", httpState->readBuffer->bytesRead, httpState->httpResponse->ContentLength ); // Set the FileStream in the DatBuffer object Console::WriteLine("Setting the FileStream in the DatBuffer object..."); httpState->readBuffer->saveFile = httpState->saveFile;
if ( httpState->readBuffer->bytesRead > 0 ) { httpState->bufferList->Add( httpState->readBuffer ); httpState->bufferEvent->Set(); httpState->readBuffer = gcnew DataBuffer(); PostResponseRead( httpState ); } else { httpState->httpResponseStream->Close(); httpState->httpResponse->Close(); httpState->readBuffer->done = true; httpState->bufferList->Add( httpState->readBuffer ); httpState->bufferEvent->Set(); } } catch(WebException^ e) { Console::WriteLine("\nReadCallBack() Exception raised!"); Console::WriteLine("\nMessage:{0}",e->Message); Console::WriteLine("\nStatus:{0}",e->Status); } }
private: static void HttpResponseCallback(IAsyncResult^ ar) { HttpRequestState^ httpState = (HttpRequestState^) ar->AsyncState; Console::WriteLine("HttpResponseCallback() invoked...");
try { // Complete the asynchronous request Console::WriteLine("Completing the asynchronous request..."); httpState->httpResponse = (HttpWebResponse^) httpState->httpRequest->EndGetResponse( ar ); // Read the response into a Stream object. Console::WriteLine("Reading the response into a Stream object..."); httpState->httpResponseStream = httpState->httpResponse->GetResponseStream(); // Create the file where resource is to be saved Console::WriteLine("Creating the file where resource is to be saved..."); httpState->saveFile = CreateFile( httpState->localSavePath, httpState->getUri ); PostResponseRead( httpState ); Console::WriteLine("BeginRead() invoked on response stream..."); } catch(WebException^ e) { Console::WriteLine("\nRespCallback() Exception raised!"); Console::WriteLine("\nMessage:{0}",e->Message); Console::WriteLine("\nStatus:{0}",e->Status); } }
public: static void PostResponseRead( HttpRequestState^ state ) { Monitor::Enter( state ); // state.readBuffer.Count = Interlocked.Increment( ref state.readCount ); // Begin the Reading of the contents of the HTML page and print it to the console. Console::WriteLine("Begin the Reading of the contents of the HTML page and print it to the console..."); IAsyncResult^ asyncStreamRead = state->httpResponseStream->BeginRead( state->readBuffer->byteBuffer, 0, DataBuffer::BufferSize, gcnew AsyncCallback( HttpStreamReadCallback ), state ); Monitor::Exit( state ); }
/// <summary> /// Retrieve the resource specified by the string URI address. The HTTP proxy /// can be specified if needed to go outside the local network. /// </summary> /// <param name="resourceName">String URI to retrieve</param> /// <param name="proxy">Proxy server to use to access resource</param> /// <param name="localSavePath">Local path to append saved resources paths to</param> /// <returns></returns> public: void GetResource(Uri^ getUri, bool recurse) { HttpWebRequest^ httpRequest = nullptr; HttpWebResponse^ httpResponse = nullptr; FileStream^ localFile = nullptr;
try { RetrievedUri->Add(getUri); Console::WriteLine("Retrieving: {0}", getUri->AbsoluteUri); // Create the HTTP request object Console::WriteLine("Creating the HTTP request object..."); httpRequest = (HttpWebRequest^)WebRequest::Create(getUri->AbsoluteUri); // Set some HTTP specific headers Console::WriteLine("Setting some HTTP specific headers..."); httpRequest->UserAgent = "My User Agent/1.0"; // If a proxy was specified create an instance of the WebProxy with it Console::WriteLine("If a proxy was specified create an instance of the WebProxy with it..."); httpRequest->Proxy = HttpProxy; HttpRequestState^ getState = gcnew HttpRequestState(httpRequest, this->PendingBuffer, this->BufferEvent); // Set the local save path in the context block Console::WriteLine("Setting the local save path in the context block..."); getState->localSavePath = this->LocalSavePath; getState->getUri = getUri; // Get the response object Console::WriteLine("Getting the response object..."); IAsyncResult^ ar = httpRequest->BeginGetResponse(gcnew AsyncCallback(HttpResponseCallback), getState); ThreadPool::RegisterWaitForSingleObject( ar->AsyncWaitHandle, gcnew WaitOrTimerCallback(RequestTimeoutCallback), getState, RequestTimeout, true); } catch (WebException^ wex) { Console::WriteLine("Exception occurred on request: {0}", wex->Message); Console::WriteLine("Status code: {0}", wex->Status);
if (wex->Status == WebExceptionStatus::ProtocolError) { // If there was a protocol error then the response object is // valid but there was an error retrieving the response. httpResponse = (HttpWebResponse^)wex->Response; Console::WriteLine("\nThe protocol returned was: {0}", httpResponse->StatusCode.ToString()); httpResponse->Close(); httpResponse = nullptr; } } finally { // Close the resources if still open if (localFile != nullptr) localFile->Close(); if (httpResponse != nullptr) httpResponse->Close(); } } };
|
Finally, add the main() code.
/// <summary> /// This is the main function which parses the command line, initializes the proxy /// if present, and calls the routine to retrieve the specified URI. /// </summary> /// <param name="args">Arguments passed to application</param> int main(array<System::String ^> ^args) { AsyncGetClass^ httpGet = gcnew AsyncGetClass(); // Parse the command line if(args->Length != 0) { for(int i=0; i < args->Length ;i++) { try { if ( ( args[i][0] == '-' ) || ( args[i][0] == '/' ) ) { switch ( Char::ToLower( args[i][1] ) ) { case 'u': // URI to download (get) httpGet->ResourceUri = gcnew Uri( args[ ++i ] ); break; case 'p': // Name of proxy server to use httpGet->HttpProxy = gcnew WebProxy( args[ ++i ] ); break; case 'r': // Retrieve all referenced images and text on the same host httpGet->RecursiveGet = true; break; case 's': // Local save path to append to retrieved resources httpGet->LocalSavePath = args[ ++i ]; break; default: AsyncGetClass::usage(); return 0; } } } catch(Exception^ err) { Console::WriteLine("Error: " + err->Message); AsyncGetClass::usage(); return 0; } } } else { AsyncGetClass::usage(); return 0; }
// Initialize the proxy server and retrieve the resources Console::WriteLine("Initializing the proxy server and retrieve the resources..."); try { bool getCompleted = false; httpGet->GetResource( httpGet->ResourceUri, httpGet->RecursiveGet ); while ( getCompleted == false ) { if ( httpGet->BufferEvent->WaitOne( 5000, true ) == true ) { Console::WriteLine("Buffer event signaled..."); while ( httpGet->PendingBuffer->Count > 0 ) { DataBuffer^ data = (DataBuffer^) httpGet->PendingBuffer[0]; httpGet->PendingBuffer->RemoveAt(0);
if ( data->done == true ) { data->saveFile->Close(); Console::WriteLine("End of stream, closing file..."); getCompleted = true; break; } else if ( data->byteBuffer->Length > 0 ) { data->saveFile->Write( data->byteBuffer, 0, data->bytesRead ); } } httpGet->BufferEvent->Reset(); } else { Console::WriteLine("Buffer event timed out"); } } } catch( Exception^ ex ) { Console::WriteLine("Exception occurred: {0}", ex->Message); } return 0; } |
Build and run the project.
![]() |
|
The following is the sample output.
The following is the sample output when run from the command prompt.
The downloaded file should be saved as Default.html.