Communicating With HTTP Servers
@官方文檔翻譯-李冰
@譯文
建立HTTP服務(wù)連接
這一章展示了怎樣去創(chuàng)建,發(fā)送善涨,和接收HTTP請求和響應(yīng)虚循。
創(chuàng)建CFHTTP請求
一個HTTP請求是遠程服務(wù)執(zhí)行方法的消息組成管怠,對象的操作(URL),消息頭,消息體验残。方法經(jīng)常是以下的一種:GET, HEAD, PUT, POST, DELETE, TRACE, CONNECT 或 OPTIONS。用CFHTTP創(chuàng)建一個請求需要四步:
- 1.用CFHTTPMessageCreateRequest方法生成一個CFHTTP消息對象巾乳。
- 2.用CFHTTPMessageSetBody方法設(shè)置消息體您没。
- 3.用CFHTTPMessageSetHeaderFieldValue方法設(shè)置消息頭。
- 4.用CFHTTPMessageCopySerializedMessage消息序列化消息
示例代碼如表3-1
表3-1創(chuàng)建一個HTTP請求
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);
在示例代碼中胆绊,首先調(diào)用CFURLCreateWithString把url轉(zhuǎn)換成CFURL對象氨鹏。然后調(diào)用CFHTTPMessageCreateRequest傳入四個參數(shù):kCFAllocatorDefault指定使用系統(tǒng)默認的分配器創(chuàng)建消息引用,requestMethod指定請求方法压状,比如POST方法仆抵,myURL指定URL,比如 http://www.apple.com何缓,kCFHTTPVersion1_1 指定消息的HTTP版本為1.1肢础。
CFHTTPMessageCreateRequest返回消息對象引用(myRequest)然后隨著消息體 (bodyData)發(fā)送到CFHTTPMessageSetBody 。然后用相同的消息引用隨著頭名調(diào)用(headerField)CFHTTPMessageSetHeaderFieldValue碌廓,設(shè)置值(value)传轰。頭蠶食是CFString對象比如 Content-Length,值參數(shù)是CFString對象比如1260谷婆。最后慨蛙,調(diào)用CFHTTPMessageCopySerializedMessage 序列化消息并通過寫流發(fā)送給預(yù)期接收者,在這個例子中纪挎。
注意:請求體經(jīng)常被省略期贫。請求地最主要的地方是勇于POST請求中POST數(shù)據(jù)容器。它也可能用于例如WebDAV和HTTP相關(guān)的擴展類型請求張异袄。更多信息參考RFC 2616通砍。
當(dāng)不再需要消息,釋放消息對象和序列化的消息。如表3-2示例代碼封孙。
表3-2釋放一個HTTP請求
CFRelease(myRequest);
CFRelease(myURL);
CFRelease(url);
CFRelease(mySerializedRequest);
myRequest = NULL;
mySerializedRequest = NULL;
創(chuàng)建一個CFHTTP響應(yīng)
創(chuàng)建HTTP響應(yīng)和創(chuàng)建一個HTTP請求的步驟大部分相同迹冤。唯一的區(qū)別在于不是調(diào)用CFHTTPMessageCreateRequest,而是用相同的參數(shù)調(diào)用CFHTTPMessageCreateResponse方法虎忌。
反序列化一個進入的HTTP請求
去反序列化一個進入的HTTP請求泡徙,調(diào)用CFHTTPMessageCreateEmpty方法創(chuàng)建一個空消息,傳入TRUE作為isRequest參數(shù)去指定要創(chuàng)建空請求消息膜蠢。然后調(diào)用CFHTTPMessageAppendBytes把進入的消息添加到空消息對象堪藐。CFHTTPMessageAppendBytes反序列化消息和移除任何可能包含的控制信息。
持續(xù)進行反序列化直到CFHTTPMessageIsHeaderComplete 返回TRUE挑围。如果不去檢測CFHTTPMessageIsHeaderComplete返回TRUE礁竞,這個消息可能不完整和不可靠。這兩個方法的示例代碼如表3-3.
表3-3 反序列化一個消息
CFHTTPMessageRef myMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);
if (!CFHTTPMessageAppendBytes(myMessage, &data, numBytes)) {
//Handle parsing error
}
在示例中贪惹,data是被添加的數(shù)據(jù)苏章,numBytes是data長度。你可能想調(diào)用CFHTTPMessageIsHeaderComplete去查實附加消息的數(shù)據(jù)頭已經(jīng)完成奏瞬。
if (CFHTTPMessageIsHeaderComplete(myMessage)) {
// Perform processing.
}
經(jīng)過反序列化的消息枫绅,你現(xiàn)在可以調(diào)用一下方法從消息中提取信息:
- CFHTTPMessageCopyBody得到一個拷貝的消息體
- CFHTTPMessageCopyHeaderFieldValue得到一個拷貝的特定的消息頭字段
- CFHTTPMessageCopyAllHeaderFields得到拷貝的所有消息頭字段
- CFHTTPMessageCopyRequestURL得到拷貝的消息的URL
- CFHTTPMessageCopyRequestMethod得到拷貝的消息請求方法
當(dāng)你不再需要這些消息,釋放和適當(dāng)?shù)奶幚怼?/p>
反序列化一個進入的HTTP響應(yīng)
創(chuàng)建一個HTTP響應(yīng)就像創(chuàng)建一個HTTP請求一樣非常簡單硼端,同樣反序列化一個進入的HTTP響應(yīng)也和反序列化進入的HTTP請求一樣簡單。唯一重要的不同點是當(dāng)調(diào)用CFHTTPMessageCreateEmpty珍昨,你必須傳入FALSE表示isRequest參數(shù)指定創(chuàng)建一個響應(yīng)消息县耽。
使用讀流去序列化和發(fā)送HTTP請求
你可以使用CFReadStream對象去序列化和發(fā)送CFHTTP請求。當(dāng)你使用CFReadStream對象去發(fā)送一個CFHTTP請求镣典,第一步打開流導(dǎo)致消息被序列化和發(fā)送兔毙。使用一個CFReadStream對象去發(fā)送CFHTTP請求讓它更簡單得到對應(yīng)請求的響應(yīng),因為響應(yīng)是作為流的一個可用屬性兄春。
序列化和發(fā)送一個HTTP請求
用CFReadStream對象去序列化和發(fā)送一個HTTP請求澎剥,首先創(chuàng)建一個CFHTTP請求和設(shè)置消息體和頭作為創(chuàng)建CFHTTP請求的描述。然后調(diào)用CFReadStreamCreateForHTTPRequest傳入剛創(chuàng)建的請求創(chuàng)建CFReadStream對象赶舆。最后哑姚,調(diào)用CFReadStreamOpen打開讀流。
當(dāng)CFReadStreamCreateForHTTPRequest被調(diào)用芜茵,它會拷貝一個通過的CFHTTP請求對象叙量。因此,如果有必要九串,當(dāng)調(diào)用CFReadStreamCreateForHTTPRequest后你需要立刻釋放CFHTTP請求對象绞佩。
因為當(dāng)CFHTTP請求被創(chuàng)建后讀流打開一個套接字連接指定的myUrl參數(shù)服務(wù)器,在流被認為打開之前必須需要一定數(shù)量的時間去允許通過。打開讀流也導(dǎo)致請求被序列化和發(fā)送征炼。
表3-4示例直觀表示怎么去序列化和發(fā)送HTTP請求
表3-4用讀流序列化一個HTTP請求
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);
檢查響應(yīng)
當(dāng)你把一個請求排進run loop中析既,你將最后得到一個完成回調(diào)的數(shù)據(jù)頭。同時谆奥,你可以調(diào)用CFReadStreamCopyProperty從讀流得到消息響應(yīng):
CFHTTPMessageRef myResponse = (CFHTTPMessageRef)CFReadStreamCopyProperty(myReadStream, kCFStreamPropertyHTTPResponseHeader);
你可以調(diào)用CFHTTPMessageCopyResponseStatusLine得到完成的響應(yīng)消息的狀態(tài)行
CFStringRef myStatusLine = CFHTTPMessageCopyResponseStatusLine(myResponse);
或者調(diào)用CFHTTPMessageGetResponseStatusCode得到響應(yīng)消息的狀態(tài)碼
UInt32 myErrCode = CFHTTPMessageGetResponseStatusCode(myResponse);
注意:如果你在通發(fā)布使用這個類(不在run loop上調(diào)度它),你必須在調(diào)用CFReadStreamCopyProperty之前至少調(diào)用一次CFReadStreamRead讀取消息拂玻。CFReadStreamRead調(diào)用blocks知道數(shù)據(jù)可用(或者鏈接失斔嵝)。不要在程序主線程執(zhí)行這些檐蚜。
處理身份驗證錯誤
如果CFHTTPMessageGetResponseStatusCode方法返回狀態(tài)碼401(遠程服務(wù)器需要身份驗證信息)或者407(代理服務(wù)器需要身份驗證信息)魄懂,你需要添加身份驗證信息到請求并在此發(fā)送。請閱讀Communicating with Authenticating HTTP Servers如何處理用戶認證闯第。
處理重定向錯誤
當(dāng)CFReadStreamCreateForHTTPRequest創(chuàng)建一個讀流市栗,自動默認禁用流的重定向。如果統(tǒng)一資源定位器咳短,或者URL填帽,請求被重定向到另一個URL,發(fā)送請求將返回錯誤狀態(tài)碼300到307咙好。如果你接收到重定向錯誤篡腌,你需要去關(guān)閉流,并在此創(chuàng)建流勾效,打開流的自動重定向并打開流嘹悼。看表3-5层宫。
表3-5重定向HTTP流
CFReadStreamClose(myReadStream);
CFReadStreamRef myReadStream =
CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest);
if (CFReadStreamSetProperty(myReadStream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue) == false) {
// something went wrong, exit
}
CFReadStreamOpen(myReadStream);
每當(dāng)創(chuàng)建一個讀流的時候杨伙,你也許希望啟動自動重定向。
取消掛起的請求
一旦一個請求被發(fā)送萌腿,不可能阻止遠程服務(wù)器對它的操作限匣。當(dāng)然,如果你不在關(guān)心這個響應(yīng)數(shù)據(jù)哮奇,你可以關(guān)閉流膛腐。
重要:當(dāng)另一個線程等待來自該流的內(nèi)容時,不要關(guān)閉該流不論任何線程鼎俘。如果你需要能夠終止請求哲身,當(dāng)使用流工作時為了防止阻塞使用非阻塞I/O來處理。關(guān)閉流之前一定要從run loop中移除贸伐。
CFNetwork Programming Guide
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:
- 1.Generate a CFHTTP message object using the CFHTTPMessageCreateRequest function.
- 2.Set the body of the message using the function CFHTTPMessageSetBody.
- 3.Set the message's headers using the CFHTTPMessageSetHeaderFieldValue function.
- 4.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.
Note: The request body is usually omitted. The main place a request body is used is in a POST request to contain the POST data. It may also be used in some other request types related to HTTP extensions such as WebDAV. See RFC 2616 for more information.
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 body
- CFHTTPMessageCopyHeaderFieldValue to get a copy of a specific header field value
- CFHTTPMessageCopyAllHeaderFields to get a copy of all of the message’s header fields
- CFHTTPMessageCopyRequestURL to get a copy of the message’s URL
- CFHTTPMessageCopyRequestMethod 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);
Note: If you are using this class synchronously (without scheduling it on a run loop), you must begin reading the message by making at least one call to CFReadStreamRead prior to calling CFReadStreamCopyProperty. The CFReadStreamRead call blocks until data is available (or the connection fails). Do not do this on your main application thread.
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.
Important: Do not close a stream from any thread while another thread is waiting for content from that stream. If you need to be able to terminate a request, you should us non-blocking I/O as described in Preventing Blocking When Working with Streams. Be sure to remove the stream from your run loop before closing it.