众所周知,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. Java Memory Management(1)

    Java Memory Management, with its built-in garbage collection, is one of the language’s finest achiev ...

  2. cgi ISAP sapi等

    1.CGI和FastCGI是apache处理php脚本的其中两种工作模式,还有ISAPI,SAPI等 2.而php-fpm并不是一种工作模式,而是一个PHP在FastCGI模式运行下的进程管理器,全称 ...

  3. [转] POJ 题目分类

    转载来自http://www.cnblogs.com/kuangbin/archive/2011/07/29/2120667.html 初期:一.基本算法:     (1)枚举. (poj1753,p ...

  4. [NYOJ 536] 开心的mdd

    开心的mdd 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述himdd有一天闲着无聊,随手拿了一本书,随手翻到一页,上面描述了一个神奇的问题,貌似是一个和矩阵有关的东西. ...

  5. 【转】Ubuntu下配置支持Windows访问的samba共享

    原文网址:http://blog.csdn.net/i_chips/article/details/19191957 一.安装Ubuntu samba服务器 $ sudo apt-get instal ...

  6. c# 模拟http post 带cookie

    下面的代码是自动向cnblogs中的小组发帖.........注意小组ID,主题ID,小组类型 首先采用firebug分析到发帖时的post地址以及参数,其中在headers中包含了cookies,把 ...

  7. MEF学习小结 z

    1.什么是MEF. MEF,全称是Managed Extensibility Framework.它是.NET Framework4.0的一个类库,其主要目的是为了创建可扩展的应用程序.按照官方说法就 ...

  8. 【转】Install MATLAB 2013a on CentOS 6.4 x64 with mode silent

    首先要下载安装光盘. Matlab801_MacUnix.iso [root@db-172-16-3-150 mnt]# md5sum /ssd1/Matlab801_MacUnix.iso  0d3 ...

  9. 搭建hdfs服务器集群的搭建+trash

    完全分布式搭建需要三台机器:node1.node2和node3 搭建时间之前首先要保持时间一致:date ntpdateyum install ntpdatentpdate -u ntp.sjtu.e ...

  10. android获取屏幕分辨率

    DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); dm. ...