CustomHTTPProtocol
http://blog.csdn.net/jingcheng345413/article/details/54967739
一、概念
NSURLProtocol也是苹果众多黑魔法中的一种,使用它可以轻松地重定义整个URL Loading System。当你注册自定义NSURLProtocol后,就有机会对所有的请求进行统一的处理,基于这一点它可以让你:
1.自定义请求和响应
2.提供自定义的全局缓存支持
3.重定向网络请求
4.提供HTTP Mocking (方便前期测试)
5.其他一些全局的网络请求修改需求
二、使用方法
1.继承NSURLPorotocl,并注册你的NSURLProtocol
- [NSURLProtocol registerClass:[MyURLProtocol class]];
当NSURLConnection准备发起请求时,它会遍历所有已注册的NSURLProtocol,询问它们能否处理当前请求。所以你需要尽早注册这个Protocol。
2.对于NSURLSession的请求,注册NSURLProtocol的方式稍有不同,是通过NSURLSessionConfiguration注册的:
- NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
- NSArray *protocolArray = @[ [MyURLProtocol class] ];
- configuration.protocolClasses = protocolArray;
- NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
- NSURLSessionTask *task = [session dataTaskWithRequest:request];
- [task resume];
3. 请求结束后注销NSURLProtocol
- [NSURLProtocol unregisterClass:[MyURLProtocol class]];
4.实现NSURLProtocol的相关方法
(1)当遍历到我们自定义的NSURLProtocol时,系统先会调用canInitWithRequest:这个方法。顾名思义,这是整个流程的入口,只有这个方法返回YES我们才能够继续后续的处理。我们可以在这个方法的实现里面进行请求的过滤,筛选出需要进行处理的请求。
- + (BOOL)canInitWithRequest:(NSURLRequest *)request
- {
- if ([NSURLProtocol propertyForKey:MyURLProtocolHandled inRequest:request])
- {
- return NO;
- }
- if (![scheme hasPrefix:@"http"])
- {
- return NO;
- }
- return YES;
- }
(2)当筛选出需要处理的请求后,就可以进行后续的处理,需要至少实现如下4个方法
- + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
- {
- return request;
- }
- + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
- {
- return [super requestIsCacheEquivalent:a toRequest:b];
- }
- - (void)startLoading
- {
- NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
- [NSURLProtocol setProperty:@(YES) forKey:MyURLProtocolHandled inRequest:mutableReqeust];
- self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
- }
- - (void)stopLoading
- {
- [self.connection cancel];
- self.connection = nil;
- }
说明:
(1)canonicalRequestForRequest: 返回规范化后的request,一般就只是返回当前request即可。
(2)requestIsCacheEquivalent:toRequest: 用于判断你的自定义reqeust是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。
(3)startLoading和stopLoading 实现请求和取消流程。
5.实现NSURLConnectionDelegate和NSURLConnectionDataDelegate
因为在第二步中我们接管了整个请求过程,所以需要实现相应的协议并使用NSURLProtocolClient将消息回传给URL Loading System。在我们的场景中推荐实现所有协议。
- - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
- {
- [self.client URLProtocol:self didFailWithError:error];
- }
- - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
- {
- if (response != nil)
- {
- [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
- }
- return request;
- }
- - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
- {
- return YES;
- }
- - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
- {
- [self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
- }
- - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
- {
- [self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
- }
- - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
- {
- [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:[[self request] cachePolicy]];
- }
- - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
- {
- [self.client URLProtocol:self didLoadData:data];
- }
- - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
- {
- return cachedResponse;
- }
- - (void)connectionDidFinishLoading:(NSURLConnection *)connection
- {
- [self.client URLProtocolDidFinishLoading:self];
- }
三、NSURLProtocol那些坑
坑1:企图在canonicalRequestForRequest:进行request的自定义操作,导致各种递归调用导致连接超时。这个API的表述其实很暧昧:
It is up to each concrete protocol implementation to define what “canonical” means. A protocol should guarantee that the same input request always yields the same canonical form.
所谓的canonical form到底是什么呢?而围观了包括NSEtcHosts和RNCachingURLProtocol在内的实现,它们都是直接返回当前request。在这个方法内进行request的修改非常容易导致递归调用(即使通过setProperty:forKey:inRequest:对请求打了标记)
坑2:没有实现足够的回调方法导致各种奇葩问题。如connection:willSendRequest:redirectResponse: 内如果没有通过[self client]回传消息,那么需要重定向的网页就会出现问题:host不对或者造成跨域调用导致资源无法加载。
坑3.崩溃报错:
- 0 libobjc.A.dylib objc_msgSend + 16
- 1 CFNetwork CFURLProtocol_NS::forgetProtocolClient() + 124
有一点苹果说明的不是很清楚,苹果自己实现CustomHTTPProtocol源码中很好的体现了这一点:
NSURLProtocolClient回调动作必须跟请求的托管发送保持在一个线程、相同的Runloop,具体实现逻辑如下:
(1)在start方法中记录当前线程和Runloop模式;
(2)所有对于NSURLProtocolClient的回调,都在记录的线程、以相同的Runloop模式触发,使用如下方法:
- [self performSelector:onThread:withObject:waitUntilDone:modes:];
坑4:httpBody
NSURLProtocol在拦截NSURLSession的POST请求时不能获取到Request中的HTTPBody。苹果官方的解释是Body是NSData类型,而且还没有大小限制。为了性能考虑,拦截时就没有拷贝。
5.如果还有什么其它问题,建议仔细看看苹果的CustomHTTPProtocol源码,应该会发现一些问题。
CustomHTTPProtocol的更多相关文章
- iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求
这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol. 由于文章的内容较长,会分成两部分,这篇文章介绍 ...
- iOS苹果官方Demo合集
Mirror of Apple’s iOS samples This repository mirrors Apple’s iOS samples. Name Topic Framework Desc ...
- iOS进阶之使用 NSURLProtocol 拦截 HTTP 请求(转载)
这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol. 由于文章的内容较长,会分成两部分,这篇文章介绍 ...
- iOS WKWebView (NSURLProtocol)拦截js、css,图片资源
项目地址github:<a href="https://github.com/LiuShuoyu/HybirdWKWebVIew/">HybirdWKWebVIew&l ...
- Sanic框架
Sanic框架 1. 入门 Sanic 是一款类似Flask的Web服务器,它运行在Python 3.5+上. 除了与Flask功能类似之外,它还支持异步请求处理,这意味着你可以使用Python3.5 ...
- sanic官方文档解析之Custom Protocols(自定义协议)和Socket(网络套接字)
1,Custom Protocol:自定义协议 温馨提示:自定义协议是一个高级用法,大多数的读者不需要用到此功能 通过特殊的自定义协议,你可以改变sanic的协议,自定义协议需要继承子类asyncio ...
随机推荐
- c语言数据结构之线性表的顺序存储结构
线性表,即线性存储结构,将具有“一对一”关系的数据“线性”地存储到物理空间中,这种存储结构就称为线性存储结构,简称线性表. 注意:使用线性表存储的数据,要求数据类型必须一致,线性表存储的数据,要么全不 ...
- Elasticsearch常见用法-入门
前台启动 默认是只有本地可以访问 ./bin/elasticsearch 远程访问 修改elasticsearch.yml,把network.host(注意配置文件格式不是以 # 开头的要空一格, : ...
- WebApi自定义全局异常过滤器及返回数据格式化
WebApi在这里就不多说了,一种轻量级的服务,应用非常广泛.我这这里主要记录下有关 WebApi的相关知识,以便日后使用. 当WebApi应用程序出现异常时,我们都会使用到异常过滤器进行日志记录,并 ...
- kubernetes(k8s) Prometheus+grafana监控告警安装部署
主机数据收集 主机数据的采集是集群监控的基础:外部模块收集各个主机采集到的数据分析就能对整个集群完成监控和告警等功能.一般主机数据采集和对外提供数据使用cAdvisor 和node-exporter等 ...
- C# Linq 使用总结
隐式类型匿名类型自动属性初始化器委托泛型泛型委托匿名方法Lambda表达式扩展方法迭代器LINQ System.Linq var arr = new[] { "c", " ...
- Mybatis 原理分析
对于入门程序的流程分析 使用过程 读配置文件 读取配置文件时绝对路径和相对路径(web工程部署后没有src路径)都有一定问题,实际开发中一般有两种方法 使用类加载器,它只能读取类路径的配置文件 使用S ...
- SQLi_Labs通关文档【1-65关】
SQLi_Labs通关文档[1-65关] 为了不干扰自己本机环境,SQL-LAB我就用的码头工人,跑起来的,搭建也非常简单,也就两条命令 docker pull acgpiano/sqli-labs ...
- 21、解决关于 vue项目中 点击按钮路由多了个问号
在vue项目开发过程中,点击按钮结果页面刷新了一遍 后来发现路径变成了 localhost:8080/?#/login 原因: 这里是 form 表单,点击了button 按钮,触发了他的默认事件,就 ...
- python学习之:序列类型 之列表,元组,range
列表 列表是可变序列,通常用于存放同类项目的集合(其中精确的相似程度将根据应用而变化). class list([iterable]) 可以用多种方式构建列表: 使用一对方括号来表示空列表: [ ] ...
- 自制微擎AI面相识别算术阈值
有时在朋友圈或其他地方会看到一些AI面相的分享链接或小程序,不是面相算命的有多吸引人,而是前面有"AI"两个字母.于是我就上网找了一下相关代码,发现了一个微擎系统的面相模块.下载下 ...