Archive for April, 2013

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.