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请求
    1. 6.0.1. 序列化和发送HTTP请求
    2. 6.0.2. 检查响应
    3. 6.0.3. 处理认证错误
    4. 6.0.4. 处理重定向错误
  • 7. 取消正在发送的请求