Kealdish's Studio.

CFNetwork学习笔记(三)

字数统计: 1.5k阅读时长: 5 min
2016/10/07 Share

前言

本篇文章主要介绍如何创建、发送和接收HTTP请求和响应。

创建CFHTTP请求

HTTP请求实际上是包含了请求方法、URL、消息头和消息体的消息。请求方法通常是GET, HEAD, PUT, POST, DELETE, TRACE, CONNECT 或 OPTIONS。用CFHTTP创建HTTP请求需要四个步骤:

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 创建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);

在上面这段代码中,url会被CFURLCreateWithString方法转换成CFURL对象。然后,调用CFHTTPMessageCreateRequest创建HTTP请求对象。之后,将创建的对象连同消息体(bodyData)传入CFHTTPMessageSetBody方法中,将请求对象连同消息头的名字(headerField)和值(value)传入CFHTTPMessageSetHeaderFieldValue方法中,headerField参数接受CFString类型的对象如Content-Length,value参数接受CFString类型的对象如1260。最后,调用CFHTTPMessageCopySerializedMessage方法对消息进行序列化并且通过写入流发送到接受者那里。

当请求不再需要时,我们需要释放消息对象和序列化的消息。

1
2
3
4
5
6
CFRelease(myRequest);
CFRelease(myURL);
CFRelease(url);
CFRelease(mySerializedRequest);
myRequest = NULL;
mySerializedRequest = NULL;

创建CFHTTP响应

创建HTTP响应的步骤与创建HTTP请求的步骤是一致的。唯一的区别在于是调用CFHTTPMessageCreateResponse方法而不是CFHTTPMessageCreateRequest方法。

反序列化HTTP请求

要对收到的HTTP请求进行反序列化操作,首先要调用CFHTTPMessageCreateEmpty方法创建空的消息对象,并在isRequest参数中传入true表示创建的是请求消息对象。然后,调用CFHTTPMessageAppendBytes方法将接收到的HTTP请求添加到消息对象中去。CFHTTPMessageAppendBytes方法会反序列化消息并且移除它保存的任何控制信息。一直不停地循环去调用该方法直到CFHTTPMessageIsHeaderComplete返回true。若CFHTTPMessageIsHeaderComplete没有返回true,说明消息是不完整的。

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

CFHTTPMessageAppendBytes方法中,data参数表示要被添加进消息对象中的数据,numBytes参数表示data的大小。你可以调用CFHTTPMessageIsHeaderComplete方法去验证添加消息是否完成。

1
2
3
if (CFHTTPMessageIsHeaderComplete(myMessage)) {
// Perform processing.
}

当消息反序列化完成后,你可以调用下面的方法从消息对象中截取信息:

  • CFHTTPMessageCopyBody:获取消息体的拷贝
  • CFHTTPMessageCopyHeaderFieldValue:获取指定头部区域的拷贝
  • CFHTTPMessageCopyAllHeaderFields:获取所有消息头的拷贝
  • CFHTTPMessageCopyRequestURL:获取消息的URL的拷贝
  • CFHTTPMessageCopyRequestMethod:获取消息请求方法的拷贝

当你不在需要消息对象时,需要正确地对其进行释放和销毁操作。

反序列化HTTP响应

正如创建HTTP请求和创建HTTP响应很相似,反序列化HTTP请求和反序列化HTTP响应也非常相似。唯一重要的区别在于当调用CFHTTPMessageCreateEmpty方法时,你必须给isRequest参数传入false表示创建的消息是响应消息。

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

你可以使用CFReadStream对象序列化和发送CFHTTP请求。当你使用CFReadStream对象发送CFHTTP请求时,打开流会让消息的序列化和发送在一步中完成。使用CFReadStream对象发送CFHTTP请求让获取响应变得容易因为响应会作为流的属性而被访问。

序列化和发送HTTP请求

要使用CFReadStream对象序列化和发送HTTP请求,首先要创建CFHTTP请求并设置消息头和消息体。然后,调用CFReadStreamCreateForHTTPRequest方法创建CFReadStream对象并传入先前创建的请求。最后,调用CFReadStreamOpen打开读流。

CFReadStreamCreateForHTTPRequest方法被调用时,它会将传入的CFHTTP请求对象进行拷贝。因而,如果必要,你应该在调用完CFReadStreamCreateForHTTPRequest方法后立即释放CFHTTP请求对象。因为读流要与指定的服务器建立套接字连接,所以在流可以打开之前需要耗费一部分时间。打开读流同样会引起请求的序列化和发送操作。

1
2
3
4
5
6
7
8
9
10
11
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);
检查响应

在你将请求添加到runloop中后,你最终会得到完整的头部回调。这时候,你可以调用CFReadStreamCopyProperty方法从读流中获取消息的响应。

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

你可以调用CFHTTPMessageCopyResponseStatusLine方法从响应消息中得到状态行。

1
CFStringRef myStatusLine = CFHTTPMessageCopyResponseStatusLine(myResponse);

或者,调用CFHTTPMessageGetResponseStatusCode方法得到响应消息的状态码。

1
UInt32 myErrCode = CFHTTPMessageGetResponseStatusCode(myResponse);
处理认证错误

如果CFHTTPMessageGetResponseStatusCode方法返回的状态码是401(远程服务器要求认证信息)或407(代理服务器要求认证),你需要将认证信息添加到请求中并重新发送。

处理重定向错误

当调用CFReadStreamCreateForHTTPRequest方法创建读流,默认情况下流的自动重定向是被禁用的。如果请求指向的URL被重定向给另一个URL,你需要关闭流并重新创建流,打开流的重定向选项,最后打开流。

1
2
3
4
5
6
7
CFReadStreamClose(myReadStream);
CFReadStreamRef myReadStream =
CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest);
if (CFReadStreamSetProperty(myReadStream, kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue) == false) {
// something went wrong, exit
}
CFReadStreamOpen(myReadStream);

取消正在发送的请求

一旦请求发送出去后,就不可能阻止远程服务器对其进行操作。然而,如果你不再关心响应数据,你可以关闭流。注意,当另一个线程在同一个流中等待数据时不要从任何线程中关闭流。如果你需要终结请求,你需要使用不阻塞的方式(参见CFNetwork学习笔记(二))。在关闭流之前要确保将流从runloop中移除。

CATALOG
  1. 1. 前言
  2. 2. 创建CFHTTP请求
  3. 3. 创建CFHTTP响应
  4. 4. 反序列化HTTP请求
  5. 5. 反序列化HTTP响应
  6. 6. 使用读流序列化和发送HTTP请求
  • 7. 取消正在发送的请求