标签: objective-c HTTP
前言
前面写过了AFNetworking的数据请求和下载的部分,也对AFNetworking有了一个基本的了解,这部分讲一下它是如何实现HTTP请求的,同时也剖析一下AFNetworking整个类的构成。
正文
AFNetworking3.0的代码比起以前的版本,精简了很多,可读性也大大的提高了。在这版本中,它的实现是对苹果给出的两套有关于网络请求的API及其代理的封装分别为:NSURLSession和NSURLConnection。其中NSURLSession是在iOS 7 或 Mac OS X 10.9以后才出现的API旨在用来替代NSURLConnection。为了兼容以前的代码,库里面还是把NSURLConnection加进来了。之前写关于AFNetworking的博客都是基于NSURLSession实现的,本文不打算讲AFNetworking基于NSURLConnection实现的部分,必竟现在都不太用了。
提到HTTP,就叉开一下,复习一下关于HTTP请求的东西。
HTTP请求
当浏览器向Web服务器发出请求时,它向服务器传递了一个数据块,也就是请求信息,它由3个部分组成:
- 请求方法URI协议/版本
- 请求头(Request Header)
- 请求正文
请求格式:
<request-line>
<headers>
<blank line>
[<request-body>]
一个典型的例子:
GET / HTTP/1.1
Host: youku.com
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
这是刚刚抓的请求包头。 第一行GET / HTTP/1.1分别为请求方法,URL(这里是根路径),协议版本1.1。从第二行开始都是请求头,请求头包含许多有关的客户端环境和请求正文的有用信息。例如,请求头可以声明浏览器所用的语言,请求正文的长度等。最后是是请求正文,这里没有。请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。请求正文中可以包含客户提交的查询字符。
HTTP请求方法用的最多的为GET方法与POST方法。
-
GET方法:默认的HTTP请求方法,我们日常用GET方法来提交表单数据,然而用GET方法提交的表单数据只经过了简单的编码,同时它将作为URL的一部分向Web服务器发送,因此,如果使用GET方法来提交表单数据就存在着安全隐患上。例如Http://127.0.0.1/login.jsp?Name=zhangshi&Age=30&Submit=%cc%E+%BD%BB从上面的URL请求中,很容易就可以辩认出表单提交的内容。(?之后的内容)另外由于GET方法提交的数据是作为URL请求的一部分所以提交的数据量不能太大。
-
POST方法:POST方法是GET方法的一个替代方法,它主要是向Web服务器提交表单数据,尤其是大批量的数据。POST方法克服了GET方法的一些缺点。通过POST方法提交表单数据时,数据不是作为URL请求的一部分而是作为标准数据传送给Web服务器,这就克服了GET方法中的信息无法保密和数据量太小的缺点。因此,出于安全的考虑以及对用户隐私的尊重,通常表单提交时采用POST方法。
HTTP响应也由三个部分组成,分别是:
- 状态行
- 消息报头
- 响应正文。
响应格式:
<status-line>
<headers>
<blank line>
[<response-body>]
一个典型的例子:
HTTP/1.1 200 OK
Date: Thu, 18 May 2017 12:38:15 GMT
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
第二行为协议状态版本代码描述,这里应答码为200,接下来都是响应头。
HTTP应答码也称为状态码,它反映了Web服务器处理HTTP请求状态。HTTP应答码由3位数字构成,其中首位数字定义了应答码的类型:
-
1XX-信息类(Information):表示收到Web浏览器请求,正在进一步的处理中。
-
2XX-成功类(Successful):表示用户请求被正确接收,理解和处理例如:200 OK。
-
3XX-重定向类(Redirection):表示请求没有成功,客户必须采取进一步的动作。
-
4XX-客户端错误(Client Error):表示客户端提交的请求有错误。例如:404 NOTFound,意味着请求中所引用的文档不存在。
-
5XX-服务器错误(Server Error):表示服务器不能完成对请求的处理:如 500。
好了,现在叉回来。前面讲过AFURLSessionManager是整个库的核心,因为在HTTP这一部分,都是在这个类的基础上实现的。通过源码也可以看到AFURLSessionManager这个类有1000多行代码而接下来要讲的AFHTTPSessionManager类,只有300行多一点,而且AFHTTPSessionManager是继承自AFURLSessionManager的,因此HTTP请求部分的绝大多数工作都交给了AFHTTPSessionManager的父类那部分。来看看整个库的类的构成:
简单介绍一下这张图上的每个类吧,其实 有些类的功能,我也没用过,不过往后可以试试:
-
AFURLSessionManager:这个类前面讲过,它对NSURLSession及NSURLSessionTaskDelegate、NSURLSessionDataDelegate、NSURLSessionDownloadDelegate和NSURLSessionDelegate进行了封装,实现了诸如数据请求,下载和上传任务。
-
AFURLConnectionOperation:它是NSOperation的子类,实现了NSURLConnection代理方法,这是iOS 6 或 Mac OS X 10.8以前用的API,现在基本不用了。
-
AFHTTPSessionManager:这是基于NSURLSession实现的用于HTTP的GET、POST等类,也是本文主要讲的东西。
-
AFHTTPRequestOperation:这是基于AFURLConnectionOperation子类,它和AFHTTPRequestOperationManager是一起配套使用的。这个类封装了可接受的状态码和可接受的内容类型。
-
AFHTTPRequestOperationManager:这个是实现HTTP各个请求方法的和AFHTTPRequestOperation配套使用的。
-
AFURLRequestSerialization:用来封装参数的,也可以用来设置请求头,请求的正文的格式等。比如,可以将请求的HTTP的body设置成JSON并在请求头部分将Content-Type字段的值设成application/json。
-
AFURLResponseSerialization:根据服务器的响应细节来解析响应的数据,此外还可以对响应的数据进行验证。例如,如果期待得到的是JSON格式的数据,那么它会检查响应码是否是2xx开头的,同时还会检查响应头的Content-Type字段是否为application/json,从而将响应的数据正解的解析成对象。
-
AFNetworkReachabilityManager:用来监控网络状态。
-
AFSecurityPolicy:用来管控网络安全的。
抛开基于NSURLConnection那一部分,真正核心的就两个两类了AFHTTPSessionManager和AFURLSessionManager。
还是以官方给的例子讲吧,这里从网上找了一张图片,使用了GET方法,获取图片并且显示在一个UIImageView上:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
//使用了默认的序列化器
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
//发起一个GET请求还是巨方便的。
[manager GET:@"http://img04.tooopen.com/images/20130701/tooopen_10055061.jpg" parameters:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) {
//这里知道得到的数据是一张图片,所以就直接这样生成图片了
self.imgView.image = [UIImage imageWithData:responseObject];
} failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
NSLog(@"%@", error);
}];
从[AFHTTPSessionManager GET:parameters:success:failure:]方法来一步一步看看,manager都干了什么。
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
[dataTask resume];
return dataTask;
}
代码还是很少的,就是根据给定的URL地址调用本类的[AFHTTPSessionManager:dataTaskWithHTTPMethod:URLString:parameters:success:failure]方法生成一个dataTask然后启动它。这方法比较关键,一起来看看:
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
//设置request,根据HTTP的请求方法添加参数
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
}
return nil;
}
__block NSURLSessionDataTask *dataTask = nil;
//这个方法调用的是父类的方法
dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
之所以说很关键,是因为它做了两次事,一是生成request,二是调用了父类的方法,用于设置代理,回调之类的,前面说过AFHTTPSessionManager继承自AFURLSessionManager的。这样AFHTTPSessionManager把剩下的部分全部分交给了AFURLSessionManager部分。对于AFURLSessionManager不熟悉的请出门左拐。库中的AFURLRequestSerialization和AFHTTPResponseSerializer也挺重要的,找个时间看看。到这里整个库也差不多看了一半多吧。写的这些都是根据官方给出的用法,去一点一点看探究它的实现。刚开始的时候,也想造个轮子,但是慢慢地发现造轮子考虑的东西太多,费时费力,而且又有写好开源的轮子,何不拿来用呢。世界这么大,得把时间用到别的地方去。用归用,但是也得看看源码,看看人家是怎么实现的,不是一个好的使用者,肯定不适合造轮子。
–EOF–