CFNetwork(三)

Communicating With HTTP Servers

@官方文档翻译-李冰

@译文

建立HTTP服务连接

这一章展示了怎样去创建,发送,和接收HTTP请求和响应。

创建CFHTTP请求

一个HTTP请求是远程服务执行方法的消息组成,对象的操作(URL),消息头,消息体。方法经常是以下的一种:GET, HEAD, PUT, POST, DELETE, TRACE, CONNECT 或 OPTIONS。用CFHTTP创建一个请求需要四步:

  • 1.用CFHTTPMessageCreateRequest方法生成一个CFHTTP消息对象。
  • 2.用CFHTTPMessageSetBody方法设置消息体。
  • 3.用CFHTTPMessageSetHeaderFieldValue方法设置消息头。
  • 4.用CFHTTPMessageCopySerializedMessage消息序列化消息

示例代码如表3-1
表3-1创建一个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);

在示例代码中,首先调用CFURLCreateWithString把url转换成CFURL对象。然后调用CFHTTPMessageCreateRequest传入四个参数:kCFAllocatorDefault指定使用系统默认的分配器创建消息引用,requestMethod指定请求方法,比如POST方法,myURL指定URL,比如 http://www.apple.com,kCFHTTPVersion1_1 指定消息的HTTP版本为1.1。

CFHTTPMessageCreateRequest返回消息对象引用(myRequest)然后随着消息体 (bodyData)发送到CFHTTPMessageSetBody 。然后用相同的消息引用随着头名调用(headerField)CFHTTPMessageSetHeaderFieldValue,设置值(value)。头蚕食是CFString对象比如 Content-Length,值参数是CFString对象比如1260。最后,调用CFHTTPMessageCopySerializedMessage 序列化消息并通过写流发送给预期接收者,在这个例子中。

注意:请求体经常被省略。请求地最主要的地方是勇于POST请求中POST数据容器。它也可能用于例如WebDAV和HTTP相关的扩展类型请求张。更多信息参考RFC 2616。

当不再需要消息,释放消息对象和序列化的消息。如表3-2示例代码。

表3-2释放一个HTTP请求

CFRelease(myRequest);
CFRelease(myURL);
CFRelease(url);
CFRelease(mySerializedRequest);
myRequest = NULL;
mySerializedRequest = NULL;

创建一个CFHTTP响应

创建HTTP响应和创建一个HTTP请求的步骤大部分相同。唯一的区别在于不是调用CFHTTPMessageCreateRequest,而是用相同的参数调用CFHTTPMessageCreateResponse方法。

反序列化一个进入的HTTP请求

去反序列化一个进入的HTTP请求,调用CFHTTPMessageCreateEmpty方法创建一个空消息,传入TRUE作为isRequest参数去指定要创建空请求消息。然后调用CFHTTPMessageAppendBytes把进入的消息添加到空消息对象。CFHTTPMessageAppendBytes反序列化消息和移除任何可能包含的控制信息。

持续进行反序列化直到CFHTTPMessageIsHeaderComplete 返回TRUE。如果不去检测CFHTTPMessageIsHeaderComplete返回TRUE,这个消息可能不完整和不可靠。这两个方法的示例代码如表3-3.

表3-3 反序列化一个消息

CFHTTPMessageRef myMessage = CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);
if (!CFHTTPMessageAppendBytes(myMessage, &data, numBytes)) {
    //Handle parsing error
}

在示例中,data是被添加的数据,numBytes是data长度。你可能想调用CFHTTPMessageIsHeaderComplete去查实附加消息的数据头已经完成。

if (CFHTTPMessageIsHeaderComplete(myMessage)) {
    // Perform processing.
}

经过反序列化的消息,你现在可以调用一下方法从消息中提取信息:

  • CFHTTPMessageCopyBody得到一个拷贝的消息体
  • CFHTTPMessageCopyHeaderFieldValue得到一个拷贝的特定的消息头字段
  • CFHTTPMessageCopyAllHeaderFields得到拷贝的所有消息头字段
  • CFHTTPMessageCopyRequestURL得到拷贝的消息的URL
  • CFHTTPMessageCopyRequestMethod得到拷贝的消息请求方法

当你不再需要这些消息,释放和适当的处理。

反序列化一个进入的HTTP响应

创建一个HTTP响应就像创建一个HTTP请求一样非常简单,同样反序列化一个进入的HTTP响应也和反序列化进入的HTTP请求一样简单。唯一重要的不同点是当调用CFHTTPMessageCreateEmpty,你必须传入FALSE表示isRequest参数指定创建一个响应消息。

使用读流去序列化和发送HTTP请求

你可以使用CFReadStream对象去序列化和发送CFHTTP请求。当你使用CFReadStream对象去发送一个CFHTTP请求,第一步打开流导致消息被序列化和发送。使用一个CFReadStream对象去发送CFHTTP请求让它更简单得到对应请求的响应,因为响应是作为流的一个可用属性。

序列化和发送一个HTTP请求

用CFReadStream对象去序列化和发送一个HTTP请求,首先创建一个CFHTTP请求和设置消息体和头作为创建CFHTTP请求的描述。然后调用CFReadStreamCreateForHTTPRequest传入刚创建的请求创建CFReadStream对象。最后,调用CFReadStreamOpen打开读流。

当CFReadStreamCreateForHTTPRequest被调用,它会拷贝一个通过的CFHTTP请求对象。因此,如果有必要,当调用CFReadStreamCreateForHTTPRequest后你需要立刻释放CFHTTP请求对象。

因为当CFHTTP请求被创建后读流打开一个套接字连接指定的myUrl参数服务器,在流被认为打开之前必须需要一定数量的时间去允许通过。打开读流也导致请求被序列化和发送。

表3-4示例直观表示怎么去序列化和发送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);

检查响应

当你把一个请求排进run loop中,你将最后得到一个完成回调的数据头。同时,你可以调用CFReadStreamCopyProperty从读流得到消息响应:

CFHTTPMessageRef myResponse = (CFHTTPMessageRef)CFReadStreamCopyProperty(myReadStream, kCFStreamPropertyHTTPResponseHeader);

你可以调用CFHTTPMessageCopyResponseStatusLine得到完成的响应消息的状态行

CFStringRef myStatusLine = CFHTTPMessageCopyResponseStatusLine(myResponse);

或者调用CFHTTPMessageGetResponseStatusCode得到响应消息的状态码

UInt32 myErrCode = CFHTTPMessageGetResponseStatusCode(myResponse);

注意:如果你在通发布使用这个类(不在run loop上调度它),你必须在调用CFReadStreamCopyProperty之前至少调用一次CFReadStreamRead读取消息。CFReadStreamRead调用blocks知道数据可用(或者链接失败)。不要在程序主线程执行这些。

处理身份验证错误

如果CFHTTPMessageGetResponseStatusCode方法返回状态码401(远程服务器需要身份验证信息)或者407(代理服务器需要身份验证信息),你需要添加身份验证信息到请求并在此发送。请阅读Communicating with Authenticating HTTP Servers如何处理用户认证。

处理重定向错误

当CFReadStreamCreateForHTTPRequest创建一个读流,自动默认禁用流的重定向。如果统一资源定位器,或者URL,请求被重定向到另一个URL,发送请求将返回错误状态码300到307。如果你接收到重定向错误,你需要去关闭流,并在此创建流,打开流的自动重定向并打开流。看表3-5。

表3-5重定向HTTP流

CFReadStreamClose(myReadStream);
CFReadStreamRef myReadStream =
    CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest);
if (CFReadStreamSetProperty(myReadStream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue) == false) {
    // something went wrong, exit
}
CFReadStreamOpen(myReadStream);

每当创建一个读流的时候,你也许希望启动自动重定向。

取消挂起的请求

一旦一个请求被发送,不可能阻止远程服务器对它的操作。当然,如果你不在关心这个响应数据,你可以关闭流。

重要:当另一个线程等待来自该流的内容时,不要关闭该流不论任何线程。如果你需要能够终止请求,当使用流工作时为了防止阻塞使用非阻塞I/O来处理。关闭流之前一定要从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.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容