Archive for the ‘C++’ Category

Asynchronous HTTP request using CFNetwork framework.

I wrote a post a few months ago concerning HTTP requests with CFNetwork and WinInet. Read it here:

Making HTTP Requests in C++ with WinInet and CFNetwork

There was a question about making the OS X CFNetwork code asynchronous, so here’s an example of how to do it:

#include <iostream>
#include <fstream>
#include <CoreFoundation/CoreFoundation.h>
#include <CFNetwork/CFNetwork.h>
#include <CFNetwork/CFHTTPStream.h>

std::string strFile;
std::ofstream file;

void _handleFinishCondition( CFReadStreamRef stream, bool bDeleteDownloadedFile, int nExitCode )
{
  file.close();

  if( bDeleteDownloadedFile )
  {
    remove( strFile.c_str() );
  }

  CFReadStreamUnscheduleFromRunLoop( stream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes );
  CFReadStreamClose(stream);
  CFRelease(stream);

  std::cout << "exiting with code:  " << nExitCode << std::endl;
  exit(nExitCode);
}

void _httpReqCallback( CFReadStreamRef stream, CFStreamEventType event, void* ptr )
{
  if( !file.is_open() )
  {
    file.open( strFile.c_str(), std::ios::out|std::ios::binary);
  }

  switch( event )
  {
    case kCFStreamEventHasBytesAvailable:
    {
      UInt8 buff[1024];
      CFIndex nBytesRead = CFReadStreamRead(stream, buff, 1024);

      if( nBytesRead>0 )
      {
        file.write( (const char*)buff, nBytesRead );
      }

      break;
    }
    case kCFStreamEventErrorOccurred:
    {
      CFStreamError err = CFReadStreamGetError(stream);
      _handleFinishCondition(stream, true, err.error);
    }
    case kCFStreamEventEndEncountered:
    {
      _handleFinishCondition(stream, false, 0);
    }
  }
}

void doHttpRequest(const char* pstrUrl, const char* pstrOutFile)
{
  strFile = pstrOutFile;
  CFStringRef cfstrUrl = CFStringCreateWithCString(kCFAllocatorDefault, pstrUrl, kCFStringEncodingUTF8);

  CFURLRef cfUrl = CFURLCreateWithString(kCFAllocatorDefault, cfstrUrl, NULL);
  CFHTTPMessageRef cfHttpReq = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), cfUrl, kCFHTTPVersion1_1);

  CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfHttpReq);
  CFOptionFlags optEvents = kCFStreamEventHasBytesAvailable|kCFStreamEventErrorOccurred|kCFStreamEventEndEncountered;

  CFStreamClientContext context = {0, NULL, NULL, NULL, NULL };

  CFReadStreamSetClient(readStream, optEvents, _httpReqCallback, &context );
  CFReadStreamScheduleWithRunLoop( readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes );
  CFReadStreamOpen( readStream );

  CFRunLoopRun();

  CFRelease(cfUrl);
  CFRelease(cfHttpReq);
}

int main(int argc, const char * argv[])
{
  doHttpRequest("http://foo.com/stuff", "/Users/cbarnes/Desktop/file.out");
  return 0;
}

As always, I don’t want to do all the work for you. This is working code, NOT safe code. Please make sure you understand what is happening here, and that you do your own error checking. Please take a look at the CFNetwork docs for detailed info:

CFNetwork Programming Guide

If there are any questions/comments/improvements, please comment.

Making HTTP Requests in C++ with WinInet and CFNetwork

First, a disclaimer: I love cURL, but there are times when you simply do not want/need to use third-party libraries. This post has nothing to do with cURL. It has everything to do with making HTTP requests with the native API’s for doing HTTP requests on Windows and OS X.

If you are here, odds are that you’ve just spent the last few hours in a futile attempt to understand either the WinInet or CFNetwork documentation. In fairness, the documentation from Microsoft and Apple is decent enough … But let’s face it, you want a quick and dirty example and there are none.

The following snippets are complete working examples. They are simple HTTP GET requests from start to finish. They DO NOT take into consideration proxy servers or HTTP auth. They DO NOT show you how to do an HTTP post. Once you get the basics though, it’s really not that far of a leap to get the rest.

If anyone would like to dive into those subject in more detail, we certainly can. Let me know, and we can talk.

Note that there’s not a lot of error handling in the code below. Just take a look at the API docs for information about what these functions return, and how to respond to errors.

Windows via WinInet:

#include <Windows.h>
#include <WinInet.h>
#include <iostream>
#include <string>

int main(int argc, char *argv[])
{
  HINTERNET hInternet = InternetOpenW(L"MyUserAgent", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);

  if( hInternet==NULL )
  {
    std::cout << "InternetOpenW failed with error code " << GetLastError() << std::endl;
  }
  else
  {
    HINTERNET hConnect = InternetConnectW(hInternet, L"www.your_server.com", 80, NULL, NULL, INTERNET_SERVICE_HTTP, 0, NULL);

    if( hConnect==NULL )
    {
      std::cout << "InternetConnectW failed with error code " << GetLastError() << std::endl;
    }
    else
    {
      const wchar_t* parrAcceptTypes[] = { L"text/*", NULL };
      HINTERNET hRequest = HttpOpenRequestW(hConnect, L"GET", L"", NULL, NULL, parrAcceptTypes, 0, 0);

      if( hRequest==NULL )
      {
        std::cout << "HttpOpenRequestW failed with error code " << GetLastError() << std::endl;
      }
      else
      {
        BOOL bRequestSent = HttpSendRequestW(hRequest, NULL, 0, NULL, 0);

        if( !bRequestSent )
        {
          std::cout << "HttpSendRequestW failed with error code " << GetLastError() << std::endl;
        }
        else
        {
          std::string strResponse;
          const int nBuffSize = 1024;
          char buff[nBuffSize];

          BOOL bKeepReading = true;
          DWORD dwBytesRead = -1;

          while(bKeepReading && dwBytesRead!=0)
          {
            bKeepReading = InternetReadFile( hRequest, buff, nBuffSize, &dwBytesRead );
            strResponse.append(buff, dwBytesRead);
          }

          std::cout << strResponse << std::endl;
        }

        InternetCloseHandle(hRequest);
      }

      InternetCloseHandle(hConnect);
    }

    InternetCloseHandle(hInternet);
  }

  return 0;
}

OS X via CFNetwork*

#include <iostream>
#include <fstream>
#include <CoreFoundation/CoreFoundation.h>
#include <CFNetwork/CFNetwork.h>
#include <CFNetwork/CFHTTPStream.h>

int main(int argc, char *argv[])
{
  CFURLRef cfUrl = CFURLCreateWithString(kCFAllocatorDefault, CFSTR("http://www.foo.com/stuff"), NULL);
  CFHTTPMessageRef cfHttpReq = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), cfUrl, kCFHTTPVersion1_1);

  CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, cfHttpReq);
  CFReadStreamOpen(readStream);

  CFMutableDataRef cfResp = CFDataCreateMutable(kCFAllocatorDefault, 0);

  CFIndex numBytesRead;

  do
  {
    const int nBuffSize = 1024;
    UInt8 buff[nBuffSize];
    numBytesRead = CFReadStreamRead(readStream, buff, nBuffSize);

    if( numBytesRead > 0 )
    {
      CFDataAppendBytes(cfResp, buff, numBytesRead);
    }
    else if( numBytesRead < 0 )
    {
      CFStreamError error = CFReadStreamGetError(readStream);
      std::cout << error.error << std::endl;
    }
  } while( numBytesRead > 0 );

  CFReadStreamClose(readStream);

  // to write to file, uncomment code below.
  //std::ofstream oFile;
  //oFile.open("/Users/cbarnes/Desktop/file.out", std::ios::out|std::ios::binary);
  //oFile.write( (const char*)CFDataGetBytePtr(cfResp), CFDataGetLength(cfResp));

  CFRelease(cfUrl);
  CFRelease(cfHttpReq);
  CFRelease(readStream);
  CFRelease(cfResp);

  return 0;
}

* To keep things simple, this code does not use run loops, polling, or callbacks. It just blocks until the request is complete. If you’d like to see an example of such, let me know.