Home > database >  How to make POST request to a web server with C and Core Foundation APIs for macOS?
How to make POST request to a web server with C and Core Foundation APIs for macOS?

Time:12-26

I'm trying to follow this example to let me make a POST request to a web server and receive its response in pure C using Core Foundation functions. I'll copy and paste it here:

void PostRequest()
{
    // Create the POST request payload.
    CFStringRef payloadString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("{\"test-data-key\" : \"test-data-value\"}"));
    CFDataRef payloadData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, payloadString, kCFStringEncodingUTF8, 0);
    CFRelease(payloadString);
    
    //create request
    CFURLRef theURL = CFURLCreateWithString(kCFAllocatorDefault, CFSTR("https://httpbin.org/post"), NULL); //https://httpbin.org/post returns post data
    CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("POST"), theURL, kCFHTTPVersion1_1);
    CFHTTPMessageSetBody(request, payloadData);
    
    //add some headers
    CFStringRef hostString = CFURLCopyHostName(theURL);
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("HOST"), hostString);
    CFRelease(hostString);
    CFRelease(theURL);
    
    if (payloadData)
    {
        CFStringRef lengthString = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%ld"), CFDataGetLength(payloadData));
        CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Content-Length"), lengthString);
        CFRelease(lengthString);
    }
    
    
    CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Content-Type"), CFSTR("charset=utf-8"));
    
    //create read stream for response
    CFReadStreamRef requestStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, request);
    CFRelease(request);
    
    //set up on separate runloop (with own thread) to avoid blocking the UI
    CFReadStreamScheduleWithRunLoop(requestStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    CFOptionFlags optionFlags = (kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered);
    CFStreamClientContext clientContext = {0, (void *)payloadData, RetainSocketStreamHandle, ReleaseSocketStreamHandle, NULL};
    CFReadStreamSetClient(requestStream, optionFlags, ReadStreamCallBack, &clientContext);
    
    //start request
    CFReadStreamOpen(requestStream);
    
    if (payloadData)
        {
        CFRelease(payloadData);
        }
}

And the callback:

void LogData(CFDataRef responseData)
{
    CFIndex dataLength = CFDataGetLength(responseData);
    UInt8 *bytes = (UInt8 *)malloc(dataLength);
    CFDataGetBytes(responseData, CFRangeMake(0, CFDataGetLength(responseData)), bytes);
    CFStringRef responseString = CFStringCreateWithBytes(kCFAllocatorDefault, bytes, dataLength, kCFStringEncodingUTF8, TRUE);
    CFShow(responseString);
    CFRelease(responseString);
    free(bytes);
}

static void ReadStreamCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo)
{
    CFDataRef passedInData = (CFDataRef)(clientCallBackInfo);
    CFShow(CFSTR("Passed In Data:"));
    LogData(passedInData);
    
    //append data as we receive it
    CFMutableDataRef responseBytes = CFDataCreateMutable(kCFAllocatorDefault, 0);
    CFIndex numberOfBytesRead = 0;
    do
    {
        UInt8 buf[1024];
        numberOfBytesRead = CFReadStreamRead(readStream, buf, sizeof(buf));
        if (numberOfBytesRead > 0)
        {
            CFDataAppendBytes(responseBytes, buf, numberOfBytesRead);
        }
    } while (numberOfBytesRead > 0);
    
    //once all data is appended, package it all together - create a response from the response headers, and add the data received.
    //note: just having the data received is not enough, you need to finish the response by retrieving the response headers here...
    CFHTTPMessageRef response = (CFHTTPMessageRef)CFReadStreamCopyProperty(readStream, kCFStreamPropertyHTTPResponseHeader);
    
    if (responseBytes)
    {
        if (response)
        {
            CFHTTPMessageSetBody(response, responseBytes);
        }
        CFRelease(responseBytes);
    }
    
    
    //close and cleanup
    CFReadStreamClose(readStream);
    CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
    CFRelease(readStream);
    
    //just keep the response body and release requests
    CFDataRef responseBodyData = CFHTTPMessageCopyBody(response);
    if (response)
    {
        CFRelease(response);
    }
    
    //get the response as a string
    if (responseBodyData)
    {
        CFShow(CFSTR("\nResponse Data:"));
        LogData(responseBodyData);
        CFRelease(responseBodyData);
    }
}

I understood how it works, and started implementing it ..... only to get this error:

'CFReadStreamCreateForHTTPRequest' is deprecated: first deprecated in macOS 10.11 - Use NSURLSession API for http requests

There's absolutely zero examples how to use NSURLSession for C , or how to bypass that idiotic "is deprecated" error.

Any help on how am I supposed to code this in C now?

PS. I don't want to use any third-party libraries. This is a simple task that was available with simple API calls (as I showed above.)

PS2. Sorry I am not an Apple developer, and I'm not used to features being deprecated on the whim.

CodePudding user response:

There are 3 options.

  1. Ignore the warning.
  2. Use ObjC runtme.
  3. Use libcurl

The first one is the easiest and the second one is the hardest solutions for your skills. The third option is easy and the most advanced solution - if you extend you software with new features, CFNetwork will lack of functionality.

  • Related