Communicating with HTTP Servers
This chapter explains how to create, send, and receive HTTP requests and responses.
Creating a CFHTTP Request
An HTTP request is a message consisting of a method for the remote server to execute, the object to operate on (the URL), message headers, and a message body. The methods are usually one of the following: GET
, HEAD
, PUT
, POST
, DELETE
, TRACE
, CONNECT
or OPTIONS
. Creating an HTTP request with CFHTTP requires four steps:
Generate a CFHTTP message object using the
CFHTTPMessageCreateRequest
function.Set the body of the message using the function
CFHTTPMessageSetBody
.Set the message's headers using the
CFHTTPMessageSetHeaderFieldValue
function.Serialize the message by calling the function
CFHTTPMessageCopySerializedMessage
.
Sample code would look like the code in Listing 3-1.
Listing 3-1 Creating an HTTP request
CFStringRef bodyString = CFSTR(""); // Usually used for POST data |
CFDataRef bodyData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, |
bodyString, kCFStringEncodingUTF8, 0); |
CFStringRef headerFieldName = CFSTR("X-My-Favorite-Field"); |
CFStringRef headerFieldValue = CFSTR("Dreams"); |
CFStringRef url = CFSTR("http://www.apple.com"); |
CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL); |
CFStringRef requestMethod = CFSTR("GET"); |
CFHTTPMessageRef myRequest = |
CFHTTPMessageCreateRequest(kCFAllocatorDefault, requestMethod, myURL, |
kCFHTTPVersion1_1); |
CFDataRef bodyDataExt = CFStringCreateExternalRepresentation(kCFAllocatorDefault, bodyData, kCFStringEncodingUTF8, 0); |
CFHTTPMessageSetBody(myRequest, bodyDataExt); |
CFHTTPMessageSetHeaderFieldValue(myRequest, headerFieldName, headerFieldValue); |
CFDataRef mySerializedRequest = CFHTTPMessageCopySerializedMessage(myRequest); |
In this sample code, url
is first converted into a CFURL object by calling CFURLCreateWithString
. Then CFHTTPMessageCreateRequest
is called with four parameters: kCFAllocatorDefault
specifies that the default system memory allocator is to be used to create the message reference, requestMethod
specifies the method, such as the POST method, myURL
specifies the URL, such as http://www.apple.com
, and kCFHTTPVersion1_1
specifies that message’s HTTP version is to be 1.1.
The message object reference (myRequest
) returned by CFHTTPMessageCreateRequest
is then sent to CFHTTPMessageSetBody
along with the body of the message (bodyData
). Then call CFHTTPMessageSetHeaderFieldValue
using the same message object reference along with the name of the header (headerField
), and the value to be set (value
). The header parameter is a CFString object such as Content-Length
, and the value parameter is a CFString object such as 1260
. Finally, the message is serialized by calling CFHTTPMessageCopySerializedMessage
and should be sent via a write stream to the intended recipient, in this example http://www.apple.com
.
When the message is no longer needed, release the message object and the serialized message. See Listing 3-2 for sample code.
Listing 3-2 Releasing an HTTP request
CFRelease(myRequest); |
CFRelease(myURL); |
CFRelease(url); |
CFRelease(mySerializedRequest); |
myRequest = NULL; |
mySerializedRequest = NULL; |
Creating a CFHTTP Response
The steps for creating an HTTP response are almost identical to those for creating an HTTP request. The only difference is that rather than calling CFHTTPMessageCreateRequest
, you call the function CFHTTPMessageCreateResponse
using the same parameters.
Deserializing an Incoming HTTP Request
To deserialize an incoming HTTP request, create an empty message using the CFHTTPMessageCreateEmpty
function, passing TRUE
as the isRequest
parameter to specify that an empty request message is to be created. Then append the incoming message to the empty message using the function CFHTTPMessageAppendBytes
. CFHTTPMessageAppendBytes
deserializes the message and removes any control information it may contain.
Continue to do this until the function CFHTTPMessageIsHeaderComplete
returns TRUE
. If you do not check for CFHTTPMessageIsHeaderComplete
to return TRUE
, the message may be incomplete and unreliable. A sample of using these two functions can be seen in Listing 3-3.
Listing 3-3 Deserializing a message
CFHTTPMessageRef myMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE); |
if (!CFHTTPMessageAppendBytes(myMessage, &data, numBytes)) { |
//Handle parsing error |
} |
In the example, data
is the data that is to be appended and numBytes
is the length of data
. You may want to call CFHTTPMessageIsHeaderComplete
to verify that the header of the appended message is complete.
if (CFHTTPMessageIsHeaderComplete(myMessage)) { |
// Perform processing. |
} |
With the message deserialized, you can now call any of the following functions to extract information from the message:
CFHTTPMessageCopyBody
to get a copy of the message’s bodyCFHTTPMessageCopyHeaderFieldValue
to get a copy of a specific header field valueCFHTTPMessageCopyAllHeaderFields
to get a copy of all of the message’s header fieldsCFHTTPMessageCopyRequestURL
to get a copy of the message’s URLCFHTTPMessageCopyRequestMethod
to get a copy of the message’s request method
When you no longer need the message, release and dispose of it properly.
Deserializing an Incoming HTTP Response
Just as creating an HTTP request is very similar to creating an HTTP response, deserializing an incoming HTTP request is also very similar to deserializing an incoming HTTP response. The only important difference is that when calling CFHTTPMessageCreateEmpty
, you must pass FALSE
as the isRequest
parameter to specify that the message to be created is a response message.
Using a Read Stream to Serialize and Send HTTP Requests
You can use a CFReadStream object to serialize and send CFHTTP requests. When you use a CFReadStream object to send a CFHTTP request, opening the stream causes the message to be serialized and sent in one step. Using a CFReadStream object to send CFHTTP requests makes it easy to get the response to the request because the response is available as a property of the stream.
Serializing and Sending an HTTP Request
To use a CFReadStream object to serialize and send an HTTP request, first create a CFHTTP request and set the message body and headers as described in Creating a CFHTTP Request. Then create a CFReadStream object by calling the function CFReadStreamCreateForHTTPRequest
and passing the request you just created. Finally, open the read stream with CFReadStreamOpen
.
When CFReadStreamCreateForHTTPRequest
is called, it makes a copy of the CFHTTP request object that it is passed. Thus, if necessary, you could release the CFHTTP request object immediately after calling CFReadStreamCreateForHTTPRequest
.
Because the read stream opens a socket connection with the server specified by the myUrl
parameter when the CFHTTP request was created, some amount of time must be allowed to pass before the stream is considered to be open. Opening the read stream also causes the request to be serialized and sent.
A sample of how to serialize and send an HTTP request can be seen in Listing 3-4.
Listing 3-4 Serializing an HTTP request with a read stream
CFStringRef url = CFSTR("http://www.apple.com"); |
CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, url, NULL); |
CFStringRef requestMethod = CFSTR("GET"); |
CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, |
requestMethod, myUrl, kCFHTTPVersion1_1); |
CFHTTPMessageSetBody(myRequest, bodyData); |
CFHTTPMessageSetHeaderFieldValue(myRequest, headerField, value); |
CFReadStreamRef myReadStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest); |
CFReadStreamOpen(myReadStream); |
Checking the Response
After you schedule the request on a run loop, you will eventually get a header complete callback. At this point, you can call CFReadStreamCopyProperty
to get the message response from the read stream:
CFHTTPMessageRef myResponse = (CFHTTPMessageRef)CFReadStreamCopyProperty(myReadStream, kCFStreamPropertyHTTPResponseHeader); |
You can get the complete status line from the response message by calling the function CFHTTPMessageCopyResponseStatusLine
:
CFStringRef myStatusLine = CFHTTPMessageCopyResponseStatusLine(myResponse); |
Or get just the status code from the response message by calling the function CFHTTPMessageGetResponseStatusCode
:
UInt32 myErrCode = CFHTTPMessageGetResponseStatusCode(myResponse); |
Handling Authentication Errors
If the status code returned by the function CFHTTPMessageGetResponseStatusCode
is 401 (the remote server requires authentication information) or 407 (a proxy server requires authentication), you need to append authentication information to the request and send it again. Please read Communicating with Authenticating HTTP Servers for information on how to handle authentication.
Handling Redirection Errors
When CFReadStreamCreateForHTTPRequest
creates a read stream, automatic redirection for the stream is disabled by default. If the uniform resource locator, or URL, to which the request is sent is redirected to another URL, sending the request will result in an error whose status code ranges from 300 to 307. If you receive a redirection error, you need to close the stream, create the stream again, enable automatic redirection for it, and open the stream. See Listing 3-5.
Listing 3-5 Redirecting an HTTP stream
CFReadStreamClose(myReadStream); |
CFReadStreamRef myReadStream = |
CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest); |
if (CFReadStreamSetProperty(myReadStream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue) == false) { |
// something went wrong, exit |
} |
CFReadStreamOpen(myReadStream); |
You may want to enable automatic redirection whenever you create a read stream.
Canceling a Pending Request
Once a request has been sent, it is not possible to prevent the remote server from acting on it. However, if you no longer care about the response data, you can close the stream.
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-06-11