通过NSURLProtocol拦截HTTP转HTTPS来整合SPDY的记录
众所周知,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的记录的更多相关文章
- iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求
这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol. 由于文章的内容较长,会分成两部分,这篇文章介绍 ...
- iOS进阶之使用 NSURLProtocol 拦截 HTTP 请求(转载)
这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol. 由于文章的内容较长,会分成两部分,这篇文章介绍 ...
- iOS应用内抓包、NSURLProtocol 拦截 APP 内的网络请求
前言 开发中遇到需要获取SDK中的数据,由于无法看到代码,所以只能通过监听所有的网络请求数据,截取相应的返回数据,可以通过NSURLProtocol实现,还可用于与H5的交互 一.NSURLProto ...
- Fiddlercore拦截并修改HTTPS链接的网页,实现JS注入
原始出处:https://www.cnblogs.com/Charltsing/p/FiddlerCoreHTTPS.html Fiddlercore可以拦截和修改http的网页内容,代码在百度很多. ...
- EF Core3.0+ 通过拦截器实现读写分离与SQL日志记录
前言 本文主要是讲解EF Core3.0+ 通过拦截器实现读写分离与SQL日志记录 注意拦截器只有EF Core3.0+ 支持,2.1请考虑上下文工厂的形式实现. 说点题外话.. 一晃又大半年没更新技 ...
- iOS WKWebView (NSURLProtocol)拦截js、css,图片资源
项目地址github:<a href="https://github.com/LiuShuoyu/HybirdWKWebVIew/">HybirdWKWebVIew&l ...
- 拦截器及 Spring MVC 整合
一.实验介绍 1.1 实验内容 本节课程主要利用 Spring MVC 框架实现拦截器以及 Spring MVC 框架的整合. 1.2 实验知识点 Spring MVC 框架 拦截器 1.3 实验环境 ...
- redmine与SVN的Https方式整合问题
尼玛啊!这个SVN的整合搞了一晚上,今天早上终于搞定了,FUCK!!! 进入话题: 可以先在bitnami redmine stack的命令行环境下手工运行svn,看是否能取到数据, svn list ...
- Mac上Burpsuite 拦截不到HTTPS流量怎么设置
在百度了一堆以及修修改改下终于拦截到HTTPS流量了. 安装步骤就大致讲一下吧 网上下载burp的安装包,然后Mac上直接打开这个burpUnlimited.jar包就可以了 我直接选择的第一个 ...
随机推荐
- uml类图的几种关系
UML类图几种关系的总结 在UML类图中,常见的有以下几种关系: 泛化(Generalization), 实现(Realization),关联(Association),聚合(Aggregati ...
- (Deep) Neural Networks (Deep Learning) , NLP and Text Mining
(Deep) Neural Networks (Deep Learning) , NLP and Text Mining 最近翻了一下关于Deep Learning 或者 普通的Neural Netw ...
- ☀【组件】加载 load
Bear / js / utils / load.js 动态修改script标签中的src属性存在的问题 javascript创建css.js,onload触发callback兼容主流浏览器的实现 各 ...
- (转载)完美解决PHP中文乱码问题
(转载)http://www.souzz.net/html/edu/php/php1/74181.html 一.首先是PHP网页的编码 1.php文件本身的编码与网页的编码应匹配 a.如果欲使用gb2 ...
- LinkedHashMap的实现原理
1. LinkedHashMap概述: LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映 ...
- Red5 1.0.5安装过程记录
Red5从旧的服务器切换到了github上后,截至20150702仍未更新文档.为了搭建Red5开发环境,我像无头苍蝇一样乱转了很多博客和StackOverflow.藉此记录这次安装过程,希望能够帮助 ...
- 【CSS】Beginner3:Color
1.red rgb(255,0,0) rgb(100%,0%,0%) #ff0000 #f00 2.Predefined color name aqua, black, blue, fuchsia, ...
- 在Eclipse中使用Maven插件 博客分类: Java相关技术
简介 本文介绍如何在Eclipse中通过maven插件编写java项目和web项目. 安装Maven 下载Maven最新版本,见:maven.apache.org/download.html 当前版本 ...
- bzoj 2285 [Sdoi2011]保密(二分,spfa + 最大流)
Description 现在,保密成为一个很重要也很困难的问题.如果没有做好,后果是严重的.比如,有个人没有自己去修电脑,又没有拆硬盘,后来的事大家都知道了. 当然,对保密最需求的当然是军方,其次才是 ...
- WCF入门到精通(二)——契约
第一次接触WCF,如有写的不对的地方有望大家指出来,谢谢!! 本篇文章主要说下WCF中的契约的种类.契约的种类.如何定义契约等内容. 契约是一种双边或多边的协议,是利益相关方就某个问题达成的一种共识, ...