翻译:CFNetwork编程指南(三)——与HTTP服务器通信(Communicating with HTTP Servers)

本文解释了如何创建、发送和接收HTTP请求和响应。

创建一个CFHTTP请求

HTTP请求是一个消息,这个消息由远程服务器执行的方法,操作的对象(URL),消息头和消息体。方法通常是下面之一:GET, HEAD, PUT, POST, DELETE, TRACE, CONNECTOPTIONS。用CFHTTP创建一个HTTP请求分为四个步骤:

  • 使用CFHTTPMessageCreateRequest 函数生成CFHTTP消息对象
  • 使用CFHTTPMessageSetBody函数设置消息体
  • 使用CFHTTPMessageSetHeaderFieldValue 函数设置消息头
  • 通过调用CFHTTPMessageCopySerializedMessage函数序列化消息

示例代码类似列表3-1中的代码。

列表3-1 创建一个HTTP请求

<pre><code>
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);
</pre></code>

在此示例代码中,通过调用CFURLCreateWithStringurl 是首先转换成一个CFURL对象。然后调用CFHTTPMessageCreateRequest ,有四个参数:kCFAllocatorDefault 指定默认系统内存分配器用来创建消息应用,requestMethod 指定方法,例如POST方法,myURL 用来指定URL,例如,http://www.apple.comkCFHTTPVersion1_1指定消息HTTP版本是1.1.

CFHTTPMessageCreateRequest 返回的消息对象引用(myRequest)和消息体(bodyData)一起发送到CFHTTPMessageSetBody 。然后调用CFHTTPMessageSetHeaderFieldValue 使用相同消息对象引用,头(headerField)名称和设置的值(value)。头参数是个CFString对象,例如Content-Length,值参数是一个CFString对象例如1260.最后,调用CFHTTPMessageCopySerializedMessage 序列化消息,通过写入流发送到接受者,例子详见http://www.apple.com

注意:请求主体通常省略。请求主体通常用于一个包含POST数据的POST请求。它也可以用于其他有关HTTP扩展的请求类型,例如WebDAV。更多信息参见RFC 2616

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

列表3-2 释放一个HTTP请求
<pre><code>
CFRelease(myRequest);

CFRelease(myURL);

CFRelease(url);

CFRelease(mySerializedRequest);

myRequest = NULL;

mySerializedRequest = NULL;

</pre></code>

创建一个CFHTTP响应

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

反序列化传入的HTTP请求

反序列化一个传入的HTTP请求,使用CFHTTPMessageCreateEmpty 函数,创建一个空消息,isRequest 参数设为TRUE 指定创建一个空的请求消息。然后调用CFHTTPMessageAppendBytes函数将传入的消息添加到空消息中。CFHTTPMessageAppendBytes反序列化消息并移除任何可能包含的控制信息。

继续这样做直到CFHTTPMessageIsHeaderComplete 函数返回TRUE。如果你不检查CFHTTPMessageIsHeaderComplete 是否返回TRUE,消息可能是不完整的或不可信的。列表3-3可以看到这两个函数使用的例子。

列表3-3 反序列化消息
<pre><code>
CFHTTPMessageRef myMessage =

CFHTTPMessageCreateEmpty(kCFAllocatorDefault, TRUE);

if (!CFHTTPMessageAppendBytes(myMessage, &data, numBytes)) {

//Handle parsing error

}
</pre></code>

在示例中,data 是添加的数据而numBytes 是的data 长度。调用CFHTTPMessageIsHeaderComplete 验证附加消息的头是完整的。
<pre><code>
if (CFHTTPMessageIsHeaderComplete(myMessage)) {

// Perform processing.

}
</pre></code>

消息反序列化后,你可以调用如下函数从消息中提取信息:

  • 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请求并设置消息主体和头,在创建CFHTTP请求(Creating a CFHTTP Request)中有描述。然后,调用CFReadStreamCreateForHTTPRequest 函数创建一个CFReadStream 对象,传递你刚刚创建的请求。最后,通过CFReadStreamOpen打开读取流。

当调用CFReadStreamCreateForHTTPRequest ,复制一份传入的CFHTTP请求对象。因此,如果有必要,你可以在调用CFReadStreamCreateForHTTPRequest 之后立即释放CFHTTP请求对象。

因为当创建CFHTTP请求时,读取流打开一个套接字连接myUrl 参数指定的服务器,两者之间允许有些时间差。打开读取流也会导致请求被序列化和发送。

列表3-4是一个关于如何序列化和发送HTTP请求的例子

列表3-4 读取流序列化HTTP请求
<pre><code>
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);

</pre></code>

检查响应

在你安排请求到运行循环后,你将最终会得到一个头完成回调。在这一点上,你可以调用CFReadStreamCopyProperty 从读取流获取消息响应。
<pre><code>
CFHTTPMessageRef myResponse =

(CFHTTPMessageRef)CFReadStreamCopyProperty(myReadStream,

kCFStreamPropertyHTTPResponseHeader);
</pre></code>

你可以通过调用CFHTTPMessageCopyResponseStatusLine函数,从消息响应获取完整状态行:
<pre><code>
CFStringRef myStatusLine =

CFHTTPMessageCopyResponseStatusLine(myResponse);
</pre></code>

通过调用CFHTTPMessageGetResponseStatusCode函数获取消息响应的状态码:
<pre><code>
UInt32 myErrCode = CFHTTPMessageGetResponseStatusCode(myResponse);
</pre></code>

注意:如果你正在同步使用这个类(没有安排到运行循环),你必须在调用CFReadStreamCopyProperty之前调用CFReadStreamRead ,先读取消息。CFReadStreamRead 调用一直阻塞直到数据可用(或连接失败)。不要在你的主应用线程中使用。

处理身份验证错误

如果CFHTTPMessageGetResponseStatusCode 函数返回的状态码是401(远程服务器需要身份验证信息)或407(代理服务器需要身份验证),你需要将身份验证信息附加到请求并重新发送。关于如何处理身份验证的信息,请查看与需要身份验证HTTP服务器通信( Communicating with Authenticating HTTP Servers)。

处理重定向错误

CFReadStreamCreateForHTTPRequest 创建一个读取流,默认情况下流的自动重定向是禁用的。如果请求发送的统一资源定位符或URL重定向到另一个URL,发送该请求将导致一个错误,状态码在300到307之间。如果你接收到一个重定向错误,你需要关闭流,创建流,启用重定向并打开流。参见列表3-5.

列表3-5 重定向HTTP流
<pre><code>
CFReadStreamClose(myReadStream);

CFReadStreamRef myReadStream =

CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest);

if (CFReadStreamSetProperty(myReadStream,

kCFStreamPropertyHTTPShouldAutoredirect, kCFBooleanTrue) == false) {

// something went wrong, exit

}
CFReadStreamOpen(myReadStream);

</pre></code>

当你创建一个读取流,你可能想要启用自动重定向。

取消一个待定请求

一旦请求已经发送,不能阻止远程服务器处理它。然而,你不再关心响应数据,你可以关闭流。

重要:如果另一个线程正在等待某个线程的流的内容,则不要关闭该流。如果你需要终止请求时,你应该使用非阻塞 I/O,在使用流时防止阻塞(Preventing Blocking When Working with Streams)中有描述。确保在关闭流之前从你的运行循环上移除流。

官方原文地址:

https://developer.apple.com/library/ios/documentation/Networking/Conceptual/CFNetwork/CFHTTPTasks/CFHTTPTasks.html#//apple_ref/doc/uid/TP30001132-CH5-SW2

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

推荐阅读更多精彩内容