7.2. CFHTTP and CFFTP

The CFNetwork framework provides easy APIs for making HTTP and FTP requests. You can make these requests through CFHTTP and CFFTP APIs. Rather than programming your application to speak a particular protocol, these APIs allow the framework to do all of the dirty work on your behalf, returning the raw protocol in a serialized form, which you can then send through a write stream connected to your desired host, or connect directly to a stream to open.

While many Cocoa classes, such as NString and NSData, allow you to initialize objects with the contents of URLs, the CFHTTP and CFFTP APIs provide more granular control over the protocol layer, as you'll see in the examples to follow.

7.2.1. CFHTTP

You can use the CFHTTP API to create an HTTP request. This allows you to easily invoke HTTP GET, HEAD, PUT, POST, and most other standard requests. Creating a request involves the three-step process of creating the request object, defining the HTTP request message and headers, and serializing the message into raw protocol. Only HTTP POST requests generally contain a message body, which can contain POST form data to send. All other requests use an empty body while embedding the request parameters into the headers.

In the example below, an HTTP/1.1 GET request is created, specifying the URL http://www.oreilly.com and setting the Connection header to instruct the remote end to close the connection after sending data:

CFStringRef requestHeader = CFSTR("Connection");
CFStringRef requestHeaderValue = CFSTR("close");
CFStringRef requestBody = CFSTR("");

CFStringRef url = CFSTR("http://www.oreilly.com">http://www.oreilly.com");
CFStringRef requestMethod = CFSTR("GET");

CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL);
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault,
    requestMethod, requestURL, kCFHTTPVersion1_1);
CFHTTPMessageSetBody(request, requestBody);
CFHTTPMessageSetHeaderFieldValue(request, requestHeader, requestHeaderValue);

CFDataRef serializedRequest = CFHTTPMessageCopySerializedMessage(request);

                                          

The resulting pointer to a CFData structure provides the raw HTTP protocol output, which you would then send through a write stream to the destination server. In the example below, an HTTP GET request is created and opened through a read stream. As data flows in, the read stream's callbacks would normally be invoked to receive the new data:

int makeRequest(const char *requestURL)
{
    CFReadStream readStream;
    CFHTTPMessageRef request;
    CFStreamClientContext CTX = { 0, NULL, NULL, NULL, NULL };

    NSString* requestURLString = [ [ NSString alloc ] initWithCString:
        requestURL ];
    NSURL url = [ NSURL URLWithString: requestURLString ];

    CFStringRef requestMessage = CFSTR("");

    request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"),
        (CFURLRef) url, kCFHTTPVersion1_1);
    if (!request) {
        return -1;
    }
    CFHTTPMessageSetBody(request, (CFDataRef) requestMessage);
    readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
    CFRelease(request);

    if (!readStream) {
        return -1;
    }

    if (!CFReadStreamSetClient(readStream, kCFStreamEventOpenCompleted |
                                           kCFStreamEventHasBytesAvailable |
                                           kCFStreamEventEndEncountered |
                                           kCFStreamEventErrorOccurred,
        ReadCallBack, &CTX))
    {
        CFRelease(readStream);
        return -1;    }

        /* Add to the run loop */
    CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(),
        kCFRunLoopCommonModes);

    if (!CFReadStreamOpen(readStream)) {
        CFReadStreamSetClient(readStream, 0, NULL, NULL);
        CFReadStreamUnscheduleFromRunLoop(readStream,
            CFRunLoopGetCurrent(),
            kCFRunLoopCommonModes);
        CFRelease(readStream);
        return -1;
    }

    return 0;
}

                                          

7.2.2. CFFTP

The CFFTP API is similar to the CFHTTP API, and relies on read streams to transmit FTP data. To create an FTP request, use the CFReadStreamCreateWithFTPURL function, as shown below. This will create the initial read stream to the FTP server:

CFStringRef url = CFSTR("ftp://ftp.somedomain.com/file.txt");
CFURLRef requestURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL);

CFReadStreamRef readStream = CFReadStreamCreateWithFTPURL(
    kCFAllocatorDefault, requestURL);

                                          

Once you have created the read stream, you can tie a callback function to it so that a read function will be called when data is ready:

CFReadStreamSetClient(
    readStream,
    kCFtreamEventHasBytesAvailable,
    readCallBack,
    NULL);

The callback function will be called whenever data is available, and will be able to read the data off the stream:

void readCallBack(
    CFReadStreamRef stream,
    CFStreamEventType eventType,
    void *clientCallBackInfo)
{
    char buffer[1024];
    CFIndex bytes_recvd;
    int recv_len = 0;

    while(recv_len < total_size && recv_len < sizeof(buffer)) {
        bytes_recvd = CFReadStreamRead(stream, buffer + recv_len,
            sizeof(buffer) - recv_len);

        /* Write bytes to output or file here */
    }
    recv_len += bytes_recvd;
}

You can now schedule the request in your main program's run loop, As the read stream is presented with data, your read callback will be invoked and will continue to perform on the data until the connection is closed or until the file has been completed:

CFReadStreamScheduleWithRunLoop(readStream,
    CFRunLoopGetCurrent(), kCFRunLoopCommonModes);

7.2.3. Further Study

  • Have a look at CFFTPStream.h, CFHTTPStream.h, and CFNetwork.h. You'll find these deep within your SDK's Core Foundation headers in /Developer/Platforms/iPhoneOS.platform.