iOS开发之网络层探索

目录

  1. 网络层框架 NSURLSession&&NSURLConnection
  2. 网络层Cookie -HTTPCookieStorage
  3. 网络层安全等级 SSL Pinning -URLCredentialStorage
  4. 网络层HTTP缓存策略 - URLRequestCachePolicy
  5. 网络层自定义协议 - URLProtocol
  6. 网络层其他设置 - URLSessionConfiguration
  7. AFN源码分析

1. 网络层框架 NSURLSession&&NSURLConnection

A.NSURLConnection

NSURLConnection是基于Core Fundation/CFNetwork API的抽象,指代的是Foundation框架的URL加载系统,关联组件有NSURLRequestNSURLResponseNSURLProtocolNSURLCacheNSHTTPCookieStorageNSURLCredentialSTorage 以及NSURLConnection
NSURLRequest 被传递给NSURLConnection 被委托对象(遵守非正式协议)异步的返回一个NSURLResponse以及服务器返回的NSData
NSURLConnection 发送请求时的线程情况,NSURLConnection被设计成异步发送,调用了start方法后,NSURLConnection会新建一些线程用底层的CFSocket去发送和接收请求,在发送和接收的一些事件发生后通知原来的线程的RunLoop去回调事件。
NSURLConnection的同步方法sendSynchronousRequest方法也是基于异步的,同样要在其他线程去处理请求的发送和接收,只是同步方法会手动block住线程,发送状态的通知也不是通过RunLoop进行。

B.NSURLSession ( NSConnection的继任者)

iOS7后推出了NSURLSession,它具备了NSURLConnection所具备的方法。同时也比它强大
NSURLSession 也可以发送Get/Post请求,实现文件下载和上传。与NSURLSession关联的API包括 NSURLRequest NSURLCacheNSURLSessionConfiguration 以及 NSURLSessionTask

在NSURLSession 中,任何请求都可以被看做一个任务。其中有三种任务类型

1. NSURLSessionDataTask     :普通的GET/POST请求
2. NSURLSessionDownloadTask :文件下载
3. NSURLSessionUploadTask     :文件上传

--a.普通出站请求

//NSURLSession 发送请求非常简单,与 NSURLConnection 不同的是,任务创建后需要手动开始执行任务。
// 1.得到session对象
NSURLSession* session = [NSURLSession sharedSession];
NSURL* url = [NSURL URLWithString:@""];
NSURLRequest *request = [NSURLRequest requestWithURL:url];

// 2.创建一个task,任务(GET)
NSURLSessionDataTask* dataTask = 
         [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {

    // data 为返回数据
}];

// 3.开始任务
[dataTask resume];

--b.NSURLSessionUploadTask 提交 文件上传

// Upload task 的创建需要使用一个 request,另外加上一个要上传的 NSData 对象或者是一个本地文件的路径对应的 NSURL
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSData *data = ...;

NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
                                                      fromData:data
                                             completionHandler:
    ^(NSData *data, NSURLResponse *response, NSError *error) {
          // ...
    }];

[uploadTask resume];

--c.NSURLSessionDownloadTask 文件下载

 // http://jc.com/file.zip
  NSURLRequest *request = [NSURLRequest requestWithURL:fileURL];
  NSURLSession *session = [NSURLSession sharedSession];
  NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    //  下载成功
    NSURL *documentDirectoryPath = [NSURL fileURLWithPath:locationPath];
    NSURL *newFileLocation = [documentDirectoryPath URLByAppendingPathComponent:[[response URL] lastPathComponent]];//取文件名 lastPathComponent file.zip
    [[NSFileManager defaultManager] copyItemAtURL:location toURL:newFileLocation error:nil];

  }];
  [downloadTask resume];

NSURLSessionTaskDelegate 的代理方法

 /* The task has received a request specific authentication challenge.
 * If this delegate is not implemented, the session specific authentication challenge
 * will *NOT* be called and the behavior will be the same as using the default handling
 * disposition. 
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
                                didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge 
                                  completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler;

completionHandler的两个参数 NSURLSessionAuthChallengeDisposition 代表对应鉴权查询的策略
NSURLCredential 代表需要使用的证书,当前者(应对鉴权查询策略是使用证书时有效,否则为NULL)

C.NSURLSession 与 NSURLConnection的区别

1.NSURLSession最直接的改进在于可以配置每个session的缓存,协议,cookie以及证书策略,甚至跨程序共享这些信息,这将允许程序和网络框架之间相互独立,不会发生干扰。每个NSURLSession 对象都由一个NSURLSessionConfiguration对象来进行初始化,后者制定了策略以及用于增强移动设备性能的新功能。
2.NSURLSession第二大区别:session Task。它负责处理数据加载以及文件和数据在客户端与服务端之间下载和上传。NSURLSessionNSURLConnection都是负责数据加载,不同之处在于所有的task共享其创造者NSURLSession 公共委托者;


在把请求发送给服务器的过程中,服务器可能会发出鉴权查询(authentcation challenge)这可以由共享的cookie或机密存储(credential storage)来自动响应,发送中的请求可以被NSURLProcol 对象拦截,以便在必要的时候改变加载行为。然后系统会查询共享的缓存信息,然后根据策略(policy)以及可用性(availability)的不同,返回缓存数据或者实时数据。


2. 网络层Cookie -HTTPCookieStorage

HTTPCookieStorage 存储了Session所使用的Cookie。
[NSHTTPCookieStorage sharedHTTPCookieStorage]

HTTPCookieAcceptPolicy 决定了Cookie接收策略
HTTPShouldSetCookies制定了请求是否应该使用session存储的cookie

由于HTTP协议是无状态的,客户端通常使用cookie来提供URL请求间数据的持久化存储。URL加载系统提供接口来创建和管理cookie,将cookie作为HTTP请求的一部分进行发送,并在解析一个服务端的响应时获取cookie。

NSHTTPCookie类封装了一个cookie,并提供了大量访问器来访问cookie的各种属性。该类同样提供了方法用于HTTP cookie头与NSHTTPCookie实例之间的互转。URL加载系统自动发送与NSURLRequest对象匹配的任何存储的cookie,除非该请求指明不需要发送cookie。同样,NSURLResponse对象是返回的cookie将根据当前设定的cookie接收策略来处理。

NSHTTPCookieStorage提供接口来管理NSHTTPCookie对象的集合。与MacOS不同的是,iOS的cookie不能在应用间共享。NSHTTPCookieStorage允许一个应用指定cookie接收策略。


3. 网络层安全等级 - URLCredentialStorage SSL Pinning

Https对比Http已经很安全,但在建立安全链接的过程中,可能遭受中间人攻击。防御这种类型攻击的最直接方式是Client使用者能正确鉴定Server发的证书【目前很多浏览器在这方面做的足够好,用户只要不在遇到警告时还继续其中的危险操作】,而对于Client的开发者而言,一种方式保持一个可信的根证书颁发机构列表,确认可信的证书,警告或阻止不是可信根证书颁发机构颁发的证书。

SSL Pinning其实就是证书绑定,一般浏览器的做法是信任可信根证书颁发机构颁发的证书,但在移动端【非浏览器的桌面应用亦如此】,应用只和少数的几个Server有交互,所以可以做得更极致点,直接就在应用内保留需要使用的具体Server的证书。对于iOS开发者而言,如果使用AFNetwoking作为网络库,那么要做到这点就很方便,直接证书作为资源打包进去就好,AFNetworking会自动加载。

URLCredentialStorage 存储了session 所使用的证书。
[NSURLCredentialStorage shareCredentialStorage];
TLSMaximumSupportedProtocol 和TLSMinimumSupportedProtocol确定是session是否支持SSL协议。

补充:SSL 与 TLS 协议

SSL协议(传输层安全协议)工作方式:客户端要收发几个握手信号:
1.发送一个“ClientHello”消息。内容包括支持的协议版本,比如TLS1.0版
2.收到一个“ServerHello”消息。内容包括支持的协议版本,
3.客户端与服务端交换证书。(依靠被选择的公钥系统)
4.服务端请求客户端公钥。客户端有证书即双向身份认证,没证书时随机生成公钥
5.客户端与服务端通过公钥保密协议共同的主私钥(伪随机数)

在SSL 3.0中发现设计缺陷后,SSL 被禁用,之后 ETF将SSL标准化,即 RFC 2246 ,并将其称为TLS(Transport Layer Security),即TLS 1.0
TLS 1.0包括可以降级到SSL 3.0的实现,这削弱了连接的安全性
TLS 1.1 添加对CBC攻击的保护:隐式IV被替换成一个显式的IV。更改分组密码模式中的填充错误。支持IANA登记的参数。
TLS 1.2 可使用密码组合选项指定伪随机函数使用SHA-256替换MD5-SHA-1组合。AES加密的支持
TLS 1.3 草案 2016年1月


4. 网络层HTTP缓存策略 - URLRequestCachePolicy

NSURLRequestCachePolicy—iOS缓存策略
  NSURLRequestCachePolicy指定缓存逻辑。URL加载系统提供了一个磁盘和内存混合的缓存,来相应网络请求。这个缓存允许一个应用减少对网络连接的依赖,并且增加性能。使用缓存的目的是为了使用的应用程序能更快速的响应用户输入,是程序高效的运行。有时候我们需要将远程web服务器获取的数据缓存起来,减少对同一个url多次请求。
  NSURLRequestUseProtocolCachePolicy = 0, 默认缓存策略。具体工作:如果一个NSCachedURLResponse对于请求并不存在,数据将会从源端获取。如果请求拥有一个缓存的响应,那么URL加载系统会检查这个响应来决定,如果它指定内容必须重新生效的话。假如内容必须重新生效,将建立一个连向源端的连接来查看内容是否发生变化。假如内容没有变化,那么响应就从本地缓存返回数据。如果内容变化了,那么数据将从源端获取
  NSURLRequestReloadIgnoringLocalCacheData = 1, URL应该加载源端数据,不使用本地缓存数据
  NSURLRequestReloadIgnoringLocalAndRemoteCacheData =4, 本地缓存数据、代理和其他中介都要忽视他们的缓存,直接加载源数据
  NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData, 两个的设置相同
  NSURLRequestReturnCacheDataElseLoad = 2, 指定已存的缓存数据应该用来响应请求,不管它的生命时长和过期时间。如果在缓存中没有已存数据来响应请求的话,数据从源端加载。
  NSURLRequestReturnCacheDataDontLoad = 3, 指定已存的缓存数据用来满足请求,不管生命时长和过期时间。如果在缓存中没有已存数据来响应URL加载请求的话,不去尝试从源段加载数据,此时认为加载请求失败。这个常量指定了一个类似于离线模式的行为
  NSURLRequestReloadRevalidatingCacheData = 5 指定如果已存的缓存数据被提供它的源段确认为有效则允许使用缓存数据响应请求,否则从源段加载数据。
  只有响应http和https的请求会被缓存。ftp和文件协议当被缓存策略允许的时候尝试接入源段。自定义的NSURLProtocol类能够保护缓存,如果它们被选择使用的话。

小结:NSURLRequestReturnCacheDataDontLoad是用于离线模式的,我为了能让用户在离线下面阅读,我就设计了当没有网络的时候的策略为NSURLRequestReturnCacheDataDontLoad。

if (有网) {
cachePolicy = NSURLRequestUseProtocolCachePolicy;
}else{
cachePolicy = NSURLRequestReturnCacheDataDontLoad;
}

5. 网络层自定义协议 - URLProtocol

NSURLProtocol,可以让开发者重新定义苹果Cocoa的URL加载系统(URL Loading System),URL Loading System 中有NSURL,NSURLRequest,NSURLConnection和NSURLSession等。NSURLProtocol可以对工程中所有的请求统一管理,做些自定义操作,无论是AFN,NSURLSession或者NSURLConnection甚至是UIWebview 发起的请求。

NSURLProtocol的作用:
1)全局性网络请求设置
2)忽略请求,使用本地缓存
3)自定义网路请求的返回结果 如拦截获取图片的Request,然后自行加载自己的图片
4)重定向网络请求 可以将URL请求的域名重定向到指定IP地址( dns 域名劫持时的策略之一)
5)过滤Response、Request中的敏感信息

拦截请求
//子类化NSURLProtocol并在AppDelegate里注册
@interface kSZURLProtocol : NSURLProtocol
@end

 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 
//注册protocol
   [NSURLProtocol registerClass:[kSZURLProtocol class]];
   return YES;
}

在kSZURLProtocol中进行自定义 如:只处理http和https的请求
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
//只处理http和https请求
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))
{
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}

        return YES;
    }
    return NO;
}

/**
 *  返回URLRequest进行处理 (必须实现)
 *  可以直接返回,也可以对URLRequest进行相关操作
 *  如修改Host,全局添加Header等,并返回一个新的URLRequest
 */
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request{
  
  NSMutableURLRequest *mutableRequest = [request mutableCopy];
  mutableRequest = [self redirectHostInRequest:mutableRequest];
  return mutableRequest;
}

/** 
 *  修改 Host
 */
+ (NSMutableURLRequest *)redirectHostInRequest:(NSMutableURLRequest *)request{
 
  if ([request.URL host].length == 0) {
    return request;
  }

  NSString *originUrlString = [request.URL absoluteString];
  NSString *originHostString = [request.URL host];
  NSRange hostRange = [originUrlString rangeOfString:originHostString];
 
  if (hostRange.location == NSNotFound) {
    
    return request;
  }
  
  NSString *ipStr = @"NewHostIP";
  NSString *urlStr = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ipStr];
  NSURL *newURL = [NSURL URLWithString:urlStr];
  request.URL = newURL;

  return request;
}

6. 网络层其他设置 - URLSessionConfiguration

NSURLSessionConfiguration 对NSMutableURLRequest 所提供的网络请求层的设置进行了扩充,提供了更大的灵活性和控制权,包括指定可用网络,cookie,安全性,缓存策略,使用自定义协议,启动事件的设置,以及用于移动设备优化的几个新属性。

NSURLSessionConfiguration 使用工厂方法初始化
+defaultSessionConfiguration 返回一个标准的 configuration,这个配置实际上与 NSURLConnection 的网络堆栈(networking stack)是一样的,具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache 和共享 NSURLCredentialStorage。

+ephemeralSessionConfiguration 返回一个预设配置,这个配置中不会对缓存,Cookie 和证书进行持久性的存储。可用于实现像秘密浏览这种功能。

+backgroundSessionConfiguration:(NSString *)identifier 的独特之处在于,它会创建一个后台 session。后台 session 不同于常规的,普通的 session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程(daemon)提供上下文。

HTTPAdditionalHeaders 跨Session共享默认出站请求(outbound request)的数据头,如内容类型,语言,用户代理和身份认证

NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", user, password];
NSData * userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
NSString *authString = [NSString stringWithFormat:@"Basic %@", base64EncodedCredential];
NSString *userAgentString = @"AppName/com.example.app (iPhone 5s; iOS 7.0.2; Scale/2.0)";

configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
                                        @"Accept-Language": @"en",
                                        @"Authorization": authString,
                                        @"User-Agent": userAgentString};

其他

networkServiceType 对标准的网络流量,网络电话,语音,视频,以及由一个后台进程使用的流量进行了区分。大多数应用程序都不需要设置这个。

allowsCellularAccess 和 discretionary 被用于节省通过蜂窝网络连接的带宽。对于后台传输的情况,推荐大家使用 discretionary 这个属性,而不是 allowsCellularAccess,因为前者会把 WiFi 和电源的可用性考虑在内。

timeoutIntervalForRequest 和 timeoutIntervalForResource 分别指定了对于请求和资源的超时间隔。许多开发人员试图使用 timeoutInterval 去限制发送请求的总时间,但其实它真正的含义是:分组(packet)之间的时间。实际上我们应该使用 timeoutIntervalForResource 来规定整体超时的总时间,但应该只将其用于后台传输,而不是用户实际上可能想要去等待的任何东西。

HTTPMaximumConnectionsPerHost 是 Foundation 框架中 URL 加载系统的一个新的配置选项。它曾经被 NSURLConnection 用于管理私有的连接池。现在有了 NSURLSession,开发者可以在需要时限制连接到特定主机的数量。

HTTPShouldUsePipelining 这个属性在 NSMutableURLRequest 下也有,它可以被用于开启 HTTP 管线化(HTTP pipelining),这可以显着降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的。

sessionSendsLaunchEvents 是另一个新的属性,该属性指定该 session 是否应该从后台启动。

connectionProxyDictionary 指定了 session 连接中的代理服务器。同样地,大多数面向消费者的应用程序都不需要代理,所以基本上不需要配置这个属性。

7. AFN源码分析

为什么使用AFN,而不直接使用原生API?AFNetworking在并发请求的同时如何管理线程的?

1)首先,AFN和其他网络框架一样解决了实现接口和所有网络请求统一管理。

2)原生API,NSURLConnection提供了两个类方法发起同步异步请求,当进行并发请求的时候,需要考虑很多问题,如异步请求所在的子线程生命周期,线程资源竞争,加锁,避免死锁等,当异步请求发起后,子线程会等待网络响应,苹果提供了delegate和block两种方式来帮助开发者处理请求时的线程问题,NURLConnection只完成了单个线程的操作,并没有提供解决多个网络请求时多个线程的管理问题,(这个问题其实可以用NSOperationQueue来解决,替开发者进行队列操作,如创建线程池等),AFN很好的解决了这个问题。后来推出的NSURLSession和AFN是同一个思想,NSURLSession内部维护了两个操作对列,一个处理session的相关回调,一个处理response响应相关的回调。

3)AFN将网络请求继承于NSOperation,将请求加入操作队列,所有和线程相关的操作都在队列里执行,帮开发者进行了队列操作,使用者只需要关注业务参数即可,提高开发效率。


相关链接:
1.https://blog.austinchou.com/dns-anti-spoofing-using-nsurlprotocol-and-happydns/
2.https://developer.apple.com/library/tvos/documentation/Cocoa/Reference/Foundation/Classes/NSURLProtocol_Class/index.html
3.http://nshipster.cn/nsurlprotocol/

你可能感兴趣的:(iOS开发之网络层探索)