众所周知,iOS 9.0之后苹果引入ATS限制,苹果也推荐尽量不要使用HTTP通讯了,毕竟是很不安全的。而国内各个有(wu)节操的运营商也会经常篡改请求HTTP请求。所以如果可能,在不影响性能的情况下,使用https总是更好一点。但是移动网络下HTTPS的握手耗时,也总是很让人难已接受。那么考虑整合Spdy来减少握手时间的损耗,复用链接来进行通讯,是一个不错的尝试。

但对于老的app,尤其是本地已经存储了大量的老旧URL来说,尝试数据升级将本地数据库里的各种http转成https的操作也是令人发指的,尤其是这种操作很不适合做灰度测试发布。总不能数据改来改去,对于那些可能本地存了几万几十万条消息记录的app来说,简直是灾难。因此如何寻求在老的app上完成http的请求转https的请求,以及整合spdy来减少握手时间,提升弱网效果就显的很重要,而我们应该寻找优雅的解决方式来完成过渡。

注:这里不讨论为什么不用HTTP2.0,不选择总有不选择的原因,再换个角度,你完成了spdy的接入,HTTP2.0的接入从大体思路上是类似的。

注:由于NSURLProtocol的拦截及再发送,涉及的坑很多,这里先不讲了,大家也可以参考下这个(https://github.com/marcuswestin/WebViewProxy),里面已经埋了不少坑。

Spdy的选择及注意事项

对于iOS来说,现有的spdy开源库,暂时可以考虑CocoaSpdy(https://github.com/twitter/CocoaSPDY),说起Spdy,大家第一反应一般是多路复用请求(multiplexing requests),头部压缩等特性,其实Spdy的设计里充分考虑了cancel的需要,这个特性其实也是非常重要的,否则复用链接会引入一个灾难的问题(就是上层已经cancel的请求在复用链路中堆积而影响后续各种请求)。

注:以下我们考虑的是采用NSURLConnection的请求的拦截。

CocoaSpdy的设计里采用在SPDYURLConnectionProtocol的load函数里将自己注册到NSURLProtocol里,作为独立的第三方库,这个可以帮你省却一些烦恼,快速接入Spdy。

但是当你发现你的app可能因为已经引入另一个NSURLProtocol的子类来做流量统计,缓存命中,甚至HTTPDNS的转换的时候,那么默认的load自动注册可能会引发拦截顺序问题,所以这里我还是注释掉了cocoaspdy的这个代码。改由自己手动注册,注意如果你要关注下注册的顺序和生效的顺序,先注册的后生效。

接入基本步骤:

1. 注册SPDYURLConnectionProtocol

2. 注册original,即针对哪个scheme,host,port进行拦截

3. 设置logger的delegate和logLevel,另外可以考虑设置并发数。

4. 等待相关的URL请求触发相关protocol的拦截,CocoaSpdy内部会解析请求,并进行相关请求及返回。

也可以看下如下的基本代码

// 先注册spdy的protocol

[NSURLProtocol registerClass:[SPDYURLConnectionProtocol class]];

// 下面这个可以先忽略,用于拦截http转https等用处

[NSURLProtocol registerClass:NSClassFromString(@"ILURLProtocol")];

// 注册拦截的规则,这里是预注册,

如果你使用过程中有新引入一些域名,也可以临时再添加注册

[SPDYURLConnectionProtocol

registerOrigin:@"https://static.localdomain.com"];

// log的delegate和loglevel

[SPDYProtocol setLogger:self];

[SPDYProtocol setLoggerLevel:SPDYLogLevelDebug];

// 设置pool的并发数,注意,这个是针对每个origin的的size。

SPDYConfiguration *configration = [SPDYProtocol currentConfiguration];

configration.sessionPoolSize = 3;

[SPDYProtocol setConfiguration:configration];

完成后,就可以通过下载https图片来观察下载情况了(注意spdy的日志打印)

比如引入sdwebimage显示图片,

NSString imageurl = @"https://static.localdomain.com/a.jpg";

[cell.imageView sd_setImageWithURL:[NSURL URLWithString:imageurl]

placeholderImage:[UIImage imageNamed:@"lock"]

options:SDWebImageRefreshCached|SDWebImageCacheMemoryOnly

completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {

NSLog(@"%@ %@ %ld", image, error, (long)cacheType);

}];

Spdy的一些基本概念和关系

Spdy在SSL层上加了一个SPDY session 层,来实现并发和stream机制。对CocoaSpdy来说,也就有SPDYSessionManager的概念,每个origin(SPDYStream)都有一个session manager(SPDYSessionManager),管理session pool和stream队列,每个session(SPDYSession,相当于一条spdy链接)都可以用来发送stream,每个stream就意味着上层发起的一个request,cancel一个请求,实际上是cancel一个stream,cancel也不是简单的移除stream,更会向服务端发起cancel stream的操作。服务端收到后会停止继续推送当前正在处理的stream请求的数据。这对于大数据量的文件下载尤为重要。

HTTP进行HTTPDNS和HTTPS的URL拦截转换

我们需要拦截HTTP,也要拦截那些指定域名被HTTPDNS之后的请求,比如 @“http://static.localdomain.com/a.jpg” 经过httpdns处理之后,获取到的ip可能为 @“http://129.11.1.1/media/a.jpg“,host填为static.localdomain.com。 此时我们同样可以将这个http://129.11.1.1/media/a.jpg 拦截下来(通过在request header里的host判断,当然对于spdy来说,你还需要注册对于129.11.1.1的拦截,而且最好还要添加对于host里的域名判断)。

这里还涉及到如何让spdy支持对IP类型的地址进行https握手建连的问题,这里需要说明的是,服务端配spdy的时候需要支持SNI的扩展,客户端在ssl建连的时候也需要主动握手参数添加kCFStreamSSLPeerName值为host来覆盖用于证书校验的名字。

我修改了下CocoaSpdy里的_tryTLSHandhshake的部分代码,从而让ip类型的服务器地址支持https握手建连。

// 注:SpdyOrigin类被改造支持domainHost参数来保存host值,

这样原host就可以保存ip,这里保存时机host了。

if (_endpoint.origin.domainHost.length > 0) {

NSMutableDictionary *newTlsSettings = [tlsOp->_tlsSettings mutableCopy];

newTlsSettings[(__bridge NSString *)kCFStreamSSLPeerName] = _endpoint.origin.domainHost;

tlsOp->_tlsSettings = newTlsSettings;

}

spdy里http转https的一些主要代码

+ (BOOL)canInitWithRequest:(NSURLRequest *)request

{

// 由于我们做的是将http转成https,所以就不怕行程循环触发protocol

if ([request.URL.scheme isEqualToString:@"http"]) {

return YES;

}

return NO;

}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

{

return [self tryGetHttpsRequest:request];

}

- (NSURLRequest *)tryGetHttpsRequest:(NSURLRequest *)oldReq

{

NSString *oldScheme = [[oldReq.URL scheme] lowercaseString];

if ([oldScheme isEqualToString:@"http"] && [self shouldInterceptorRequest:oldReq]) {

NSMutableURLRequest *newReq = [oldReq mutableCopy];

NSURL *newUrl = [[NSURL alloc] initWithScheme:@"https" host:oldReq.URL.host path:oldReq.URL.path];

newReq.URL = newUrl;

// 下面这段代码是因为现有的有spdy服务器对于strem header里中文直接断开连接,而cocoaspdy里获取的是应用的名称,如果你的名称正好是中文,会导致steam一发送就被rst,所以我们主动去创建一个host,这样spdy就不会用内部的defaultuseragent来覆盖

if (![newReq valueForHTTPHeaderField:@"User-Agent"]) {

NSMutableDictionary *allHTTPHeaderFields = [newReq.allHTTPHeaderFields mutableCopy];

[allHTTPHeaderFields setObject:[self getDefaultUserAgent] forKey:@"User-Agent"];

newReq.allHTTPHeaderFields = allHTTPHeaderFields;

}

return newReq;

}

return oldReq;

}

- (instancetype)initWithHosts:(NSArray *)hosts

{

self = [super init];

if (self)

{

//拦截HTTP,并且host为指定的host

_predicate = [NSPredicate predicateWithFormat:@"scheme MATCHES 'http' AND host IN[cd] %@", hosts];

_hostPredicate = [NSPredicate predicateWithFormat:@"SELF IN[cd] %@", hosts];

}

return self;

}

- (BOOL)shouldInterceptorRequest:(NSURLRequest *)request

{

if ([self.predicate evaluateWithObject:request.URL])

{

return YES;

}

// 这个是针对httpdns之后,host已经转到request里的host字段里了,所以做二次判断。

if ([self isHeaderHostVaild:request]) {

return YES;

}

return NO;

}

- (BOOL)isHeaderHostVaild:(NSURLRequest *)request

{

NSString *hostInHeader = [request valueForHTTPHeaderField:@"Host"];

if (hostInHeader && [self.hostPredicate evaluateWithObject:hostInHeader]) {

return YES;

}

return NO;

}

One more thing

当你完成http转httpdns,转https之后,你可能碰到sdwebimage在滚出页面的时候,会去cancel request,但是走spdy之后,它会触发protocol的stopLoading方法,此时需要关注这个stopLoading触发的时候,此前用于2次转发的NSUrlConnection对象我们需要判断handler是否可用,如果可用,说明并不是正常结束,而是被cancel了,此时应该执行[connectoin cancel]操作。

关于ATS

这里简单说明下ATS,不细说,详情各位看官自己google下,一大堆资料,也可以查看“阅读原文”。

阅读原文

通过NSURLProtocol拦截HTTP转HTTPS来整合SPDY的记录的更多相关文章

  1. iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求

    这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol. 由于文章的内容较长,会分成两部分,这篇文章介绍 ...

  2. iOS进阶之使用 NSURLProtocol 拦截 HTTP 请求(转载)

    这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol. 由于文章的内容较长,会分成两部分,这篇文章介绍 ...

  3. iOS应用内抓包、NSURLProtocol 拦截 APP 内的网络请求

    前言 开发中遇到需要获取SDK中的数据,由于无法看到代码,所以只能通过监听所有的网络请求数据,截取相应的返回数据,可以通过NSURLProtocol实现,还可用于与H5的交互 一.NSURLProto ...

  4. Fiddlercore拦截并修改HTTPS链接的网页,实现JS注入

    原始出处:https://www.cnblogs.com/Charltsing/p/FiddlerCoreHTTPS.html Fiddlercore可以拦截和修改http的网页内容,代码在百度很多. ...

  5. EF Core3.0+ 通过拦截器实现读写分离与SQL日志记录

    前言 本文主要是讲解EF Core3.0+ 通过拦截器实现读写分离与SQL日志记录 注意拦截器只有EF Core3.0+ 支持,2.1请考虑上下文工厂的形式实现. 说点题外话.. 一晃又大半年没更新技 ...

  6. iOS WKWebView (NSURLProtocol)拦截js、css,图片资源

    项目地址github:<a href="https://github.com/LiuShuoyu/HybirdWKWebVIew/">HybirdWKWebVIew&l ...

  7. 拦截器及 Spring MVC 整合

    一.实验介绍 1.1 实验内容 本节课程主要利用 Spring MVC 框架实现拦截器以及 Spring MVC 框架的整合. 1.2 实验知识点 Spring MVC 框架 拦截器 1.3 实验环境 ...

  8. redmine与SVN的Https方式整合问题

    尼玛啊!这个SVN的整合搞了一晚上,今天早上终于搞定了,FUCK!!! 进入话题: 可以先在bitnami redmine stack的命令行环境下手工运行svn,看是否能取到数据, svn list ...

  9. Mac上Burpsuite 拦截不到HTTPS流量怎么设置

    在百度了一堆以及修修改改下终于拦截到HTTPS流量了. 安装步骤就大致讲一下吧 网上下载burp的安装包,然后Mac上直接打开这个burpUnlimited.jar包就可以了 我直接选择的第一个   ...

随机推荐

  1. springboot 配置多数据源

    1.首先在创建应用对象时引入autoConfig package com; import org.springframework.boot.SpringApplication; import org. ...

  2. 《深入理解linux内核》第一章 序论

    硬链接的限制

  3. 查看wtmp文件内容

    1./var/log/wtmp文件的作用     /var/log/wtmp也是一个二进制文件,记录每个用户的登录次数和持续时间等信息!   2.查看方法:可以用last命令输出当中内容 1 2 3 ...

  4. CMD命令窗口复制与粘贴

    cmd命令提示符窗口中快速复制粘贴的方法常规方法 在“命令提 示符”窗口的任意一处,点击右键,在弹出的快捷菜单中选择“标记”命令. 此时在窗口的左上角处闪烁着一个长方块状的光标,将鼠标移动到希望复制的 ...

  5. ununtu卸载软件

    sudo apt-get remove vim

  6. WCF 双工模式

    WCF之消息模式分为:1.请求/答复模式2.单向模式3.双工模式 其中,请求/答复模式,在博文: WCF 入门教程一(动手新建第一个WCF程序并部署) WCF 入门教程二 中进行了详细介绍,此处将主要 ...

  7. 两种应该掌握的排序方法--------1.shell Sort

    先了解下什么都有什么排序算法 https://en.wikipedia.org/wiki/Sorting_algorithm http://zh.wikipedia.org/zh/%E6%8E%92% ...

  8. wpa_supplicant 和 802.11g WPA 认证的配置

    # cd /etc/init.d# ln -s net.lo net.eth0 默认的接口名是 wlan0,让它开机时自动 up:cp /etc/init.d/net.lo /etc/init.d/n ...

  9. [Stephen]关于Ext.net fileupload 的兼容性解决问题

    在firefox下,利用fileupload上传图片后,通过后端将image 的src路径进行更新,刷新前段界面显示没有问题. 但是在以IE为内核的360中,这种上传后的更新导致一个命名为Action ...

  10. oracle 高水位线

    一.oracle 高水位线详解 一.什么是水线(High Water Mark)? 概念: 1.块: 是粒度最小的存储单位,现在标准的块大小是8K,ORACLE每一次I/O操作也是按块来操作的,也就是 ...