众所周知,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. uml类图的几种关系

    UML类图几种关系的总结   在UML类图中,常见的有以下几种关系: 泛化(Generalization),  实现(Realization),关联(Association),聚合(Aggregati ...

  2. (Deep) Neural Networks (Deep Learning) , NLP and Text Mining

    (Deep) Neural Networks (Deep Learning) , NLP and Text Mining 最近翻了一下关于Deep Learning 或者 普通的Neural Netw ...

  3. ☀【组件】加载 load

    Bear / js / utils / load.js 动态修改script标签中的src属性存在的问题 javascript创建css.js,onload触发callback兼容主流浏览器的实现 各 ...

  4. (转载)完美解决PHP中文乱码问题

    (转载)http://www.souzz.net/html/edu/php/php1/74181.html 一.首先是PHP网页的编码 1.php文件本身的编码与网页的编码应匹配 a.如果欲使用gb2 ...

  5. LinkedHashMap的实现原理

    1. LinkedHashMap概述: LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映 ...

  6. Red5 1.0.5安装过程记录

    Red5从旧的服务器切换到了github上后,截至20150702仍未更新文档.为了搭建Red5开发环境,我像无头苍蝇一样乱转了很多博客和StackOverflow.藉此记录这次安装过程,希望能够帮助 ...

  7. 【CSS】Beginner3:Color

    1.red rgb(255,0,0) rgb(100%,0%,0%) #ff0000 #f00 2.Predefined color name aqua, black, blue, fuchsia, ...

  8. 在Eclipse中使用Maven插件 博客分类: Java相关技术

    简介 本文介绍如何在Eclipse中通过maven插件编写java项目和web项目. 安装Maven 下载Maven最新版本,见:maven.apache.org/download.html 当前版本 ...

  9. bzoj 2285 [Sdoi2011]保密(二分,spfa + 最大流)

    Description 现在,保密成为一个很重要也很困难的问题.如果没有做好,后果是严重的.比如,有个人没有自己去修电脑,又没有拆硬盘,后来的事大家都知道了. 当然,对保密最需求的当然是军方,其次才是 ...

  10. WCF入门到精通(二)——契约

    第一次接触WCF,如有写的不对的地方有望大家指出来,谢谢!! 本篇文章主要说下WCF中的契约的种类.契约的种类.如何定义契约等内容. 契约是一种双边或多边的协议,是利益相关方就某个问题达成的一种共识, ...