CFNetwork框架详细解析(五) —— CFNetwork编程指导之与HTTP服务器通信(四)

版本记录

版本号 时间
V1.0 2018.06.09

前言

CFNetwork框架访问网络服务并处理网络配置的变化。 建立在网络协议抽象的基础上,可以简化诸如使用BSD套接字,管理HTTP和FTP服务器以及管理Bonjour服务等任务。接下来几篇我们就一起看一下这个框架。感兴趣的可以看上面几篇文章。
1. CFNetwork框架详细解析(一) —— 基本概览
2. CFNetwork框架详细解析(二) —— CFNetwork编程指导之简介(一)
3. CFNetwork框架详细解析(三) —— CFNetwork编程指导之CFNetwork概念(二)
4. CFNetwork框架详细解析(四) —— CFNetwork编程指导之流的处理(三)

Communicating with HTTP Servers - 与HTTP服务器通信

本章介绍如何创建,发送和接收HTTP请求和响应。


Creating a CFHTTP Request - 创建一个CFHTTP请求

HTTP请求是由远程服务器执行的方法,对其进行操作的对象(URL),消息标题message headers和消息正文message body组成的消息。 这些方法通常是以下其中一种:GET,HEAD,PUT,POST,DELETE,TRACE,CONNECT或OPTIONS。 使用CFHTTP创建HTTP请求需要四个步骤:

  • 使用CFHTTPMessageCreateRequest函数生成一个CFHTTP消息对象。
  • 使用函数CFHTTPMessageSetBody设置消息的正文。
  • 使用CFHTTPMessageSetHeaderFieldValue函数设置消息的标题。
  • 通过调用函数CFHTTPMessageCopySerializedMessage来序列化消息。

示例代码如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);

在这个示例代码中,首先通过调用CFURLCreateWithString将url转换为CFURL对象。然后使用四个参数调用CFHTTPMessageCreateRequestkCFAllocatorDefault指定默认的系统内存分配器用于创建消息引用,requestMethod指定方法,例如POST方法,myURL指定URL,例如http:// www.apple.comkCFHTTPVersion1_1指定该消息的HTTP版本为1.1。

然后,由CFHTTPMessageCreateRequest返回的消息对象引用(myRequest)随同消息体(bodyData)一起被发送到CFHTTPMessageSetBody。然后调用CFHTTPMessageSetHeaderFieldValue,使用相同的消息对象引用以及头的名称(headerField)和要设置的值(value)header参数是一个CFString对象,比如Content-Length,而value参数是一个CFString对象,如1260.最后,通过调用CFHTTPMessageCopySerializedMessage对消息进行序列化,并且应该通过写入流发送给预期的接收者,在这个例子中http://www.apple.com

注意:请求体通常被省略。 使用请求主体的主要位置在POST请求中以包含POST数据。 它也可以用于与HTTP扩展相关的其他一些请求类型,例如WebDAV。 有关更多信息,请参阅RFC 2616

当消息不再需要时,释放消息对象和序列化消息。 有关示例代码,请参见Listing 3-2

Listing 3-2  Releasing an HTTP request

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

Creating a CFHTTP Response - 创建一个CFHTTP响应

创建HTTP响应的步骤几乎与创建HTTP请求的步骤相同。 唯一的区别是不是调用CFHTTPMessageCreateRequest,而是使用相同的参数调用函数CFHTTPMessageCreateResponse


Deserializing an Incoming HTTP Request - 反序列化传入的HTTP请求

要反序列化传入的HTTP请求,请使用CFHTTPMessageCreateEmpty函数创建一条空消息,并以isRequest参数的形式传递TRUE以指定将创建一个空请求消息。 然后使用函数CFHTTPMessageAppendBytes将传入消息附加到空消息。 CFHTTPMessageAppendBytes将消息反序列化并移除它可能包含的任何控制信息。

继续执行此操作,直到函数CFHTTPMessageIsHeaderComplete返回TRUE。 如果您没有检查CFHTTPMessageIsHeaderComplete是否返回TRUE,则该消息可能不完整且不可靠。 在Listing 3-3中可以看到使用这两个函数的示例。

Listing 3-3  Deserializing a message

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获取消息请求方法的副本

当你不再需要信息时,正确地释放和处理它。


Deserializing an Incoming HTTP Response - 反序列化传入的HTTP响应

正如创建HTTP请求与创建HTTP响应非常类似,反序列化传入的HTTP请求也非常类似于反序列化传入的HTTP响应。 唯一重要的区别是当调用CFHTTPMessageCreateEmpty时,必须将FALSE传递给isRequest参数,以指定要创建的消息是响应消息。


Using a Read Stream to Serialize and Send HTTP Requests - 使用读取流来序列化和发送HTTP请求

您可以使用CFReadStream对象序列化并发送CFHTTP请求。 当您使用CFReadStream对象发送CFHTTP请求时,打开流会导致消息被序列化并一步发送。 使用CFReadStream对象发送CFHTTP请求可以很容易地获得对请求的响应,因为响应可用作流的属性。

1. Serializing and Sending an HTTP Request - 序列化和发送HTTP请求

要使用CFReadStream对象序列化并发送HTTP请求,请首先按照Creating a CFHTTP Request中的描述创建CFHTTP请求并设置消息正文和标头。 然后通过调用函数CFReadStreamCreateForHTTPRequest并传递刚刚创建的请求来创建一个CFReadStream对象。 最后,用CFReadStreamOpen打开读取流。

当调用CFReadStreamCreateForHTTPRequest时,它会复制它传递的CFHTTP请求对象。 因此,如果需要,可以在调用CFReadStreamCreateForHTTPRequest之后立即释放CFHTTP请求对象。

由于在创建CFHTTP请求时,读取流将与myUrl参数指定的服务器建立套接字连接,因此在认为该流打开之前必须允许经过一段时间。 打开读取流也会导致请求被序列化并发送。

Listing 3-4中可以看到如何序列化和发送HTTP请求的示例。

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);

2. Checking the Response - 检查响应

在运行循环中调度请求之后,您最终将获得标题完成回调。 此时,您可以调用CFReadStreamCopyProperty从读取流获取消息响应。

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

您可以通过调用函数CFHTTPMessageCopyResponseStatusLine从响应消息中获取完整的状态行:

CFStringRef myStatusLine = CFHTTPMessageCopyResponseStatusLine(myResponse);

或者通过调用函数CFHTTPMessageGetResponseStatusCode从响应消息中获取状态码:

UInt32 myErrCode = CFHTTPMessageGetResponseStatusCode(myResponse);

注意:如果您正在同步使用此类(无需在运行循环中调度它),则必须在调用CFReadStreamCopyProperty之前至少调用一次CFReadStreamRead来读取消息。 CFReadStreamRead调用阻塞,直到数据可用(或连接失败)。 不要在主应用程序线程上执行此操作。

Handling Authentication Errors - 处理认证错误

如果函数CFHTTPMessageGetResponseStatusCode返回的状态代码是401(远程服务器需要认证信息)或407(代理服务器需要认证),则需要将认证信息附加到请求并再次发送。 请阅读Communicating with Authenticating HTTP Servers以获取有关如何处理身份验证的信息。

Handling Redirection Errors - 处理重定向错误

CFReadStreamCreateForHTTPRequest创建读取流时,默认情况下禁用流的自动重定向。 如果发送请求的统一资源定位符或URL被重定向到另一个URL,则发送该请求将导致错误,其状态码范围为300307,如果收到重定向错误,则需要关闭流,再次创建流,为其启用自动重定向,并打开流。 参见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);

您可能希望在创建读取流时启用自动重定向。


Canceling a Pending Request - 取消待处理的请求

一旦请求发送完毕,就无法阻止远程服务器对其执行操作。 但是,如果您不再关心响应数据,则可以关闭流。

重要提示:当另一个线程正在等待来自该流的内容时,不要关闭任何线程中的流。 如果您需要能够终止请求,则应使用非阻塞I / O,如Preventing Blocking When Working with Streams中所述。 确保在关闭它之前从运行循环中移除流。

后记

本篇主要讲述了与HTTP服务器通信,感兴趣的给个赞或者关注~~~~

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

推荐阅读更多精彩内容