http://blog.csdn.net/jingcheng345413/article/details/54967739

一、概念

NSURLProtocol也是苹果众多黑魔法中的一种,使用它可以轻松地重定义整个URL Loading System。当你注册自定义NSURLProtocol后,就有机会对所有的请求进行统一的处理,基于这一点它可以让你:
1.自定义请求和响应
2.提供自定义的全局缓存支持
3.重定向网络请求
4.提供HTTP Mocking (方便前期测试)
5.其他一些全局的网络请求修改需求

二、使用方法

1.继承NSURLPorotocl,并注册你的NSURLProtocol

  1. [NSURLProtocol registerClass:[MyURLProtocol class]];

当NSURLConnection准备发起请求时,它会遍历所有已注册的NSURLProtocol,询问它们能否处理当前请求。所以你需要尽早注册这个Protocol。

2.对于NSURLSession的请求,注册NSURLProtocol的方式稍有不同,是通过NSURLSessionConfiguration注册的:

  1. NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
  2. NSArray *protocolArray = @[ [MyURLProtocol class] ];
  3. configuration.protocolClasses = protocolArray;
  4. NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
  5. NSURLSessionTask *task = [session dataTaskWithRequest:request];
  6. [task resume];
 

3. 请求结束后注销NSURLProtocol

  1. [NSURLProtocol unregisterClass:[MyURLProtocol class]];
 

4.实现NSURLProtocol的相关方法

(1)当遍历到我们自定义的NSURLProtocol时,系统先会调用canInitWithRequest:这个方法。顾名思义,这是整个流程的入口,只有这个方法返回YES我们才能够继续后续的处理。我们可以在这个方法的实现里面进行请求的过滤,筛选出需要进行处理的请求。

  1. + (BOOL)canInitWithRequest:(NSURLRequest *)request
  2. {
  3. if ([NSURLProtocol propertyForKey:MyURLProtocolHandled inRequest:request])
  4. {
  5. return NO;
  6. }
  7. if (![scheme hasPrefix:@"http"])
  8. {
  9. return NO;
  10. }
  11. return YES;
  12. }
 

(2)当筛选出需要处理的请求后,就可以进行后续的处理,需要至少实现如下4个方法

  1. + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
  2. {
  3. return request;
  4. }
  5. + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
  6. {
  7. return [super requestIsCacheEquivalent:a toRequest:b];
  8. }
  9. - (void)startLoading
  10. {
  11. NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
  12. [NSURLProtocol setProperty:@(YES) forKey:MyURLProtocolHandled inRequest:mutableReqeust];
  13. self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
  14. }
  15. - (void)stopLoading
  16. {
  17. [self.connection cancel];
  18. self.connection = nil;
  19. }

说明:
(1)canonicalRequestForRequest: 返回规范化后的request,一般就只是返回当前request即可。
(2)requestIsCacheEquivalent:toRequest: 用于判断你的自定义reqeust是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。
(3)startLoading和stopLoading 实现请求和取消流程。

5.实现NSURLConnectionDelegate和NSURLConnectionDataDelegate

因为在第二步中我们接管了整个请求过程,所以需要实现相应的协议并使用NSURLProtocolClient将消息回传给URL Loading System。在我们的场景中推荐实现所有协议。

  1. - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
  2. {
  3. [self.client URLProtocol:self didFailWithError:error];
  4. }
  5. - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
  6. {
  7. if (response != nil)
  8. {
  9. [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
  10. }
  11. return request;
  12. }
  13. - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
  14. {
  15. return YES;
  16. }
  17. - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
  18. {
  19. [self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
  20. }
  21. - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
  22. {
  23. [self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
  24. }
  25. - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
  26. {
  27. [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:[[self request] cachePolicy]];
  28. }
  29. - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
  30. {
  31. [self.client URLProtocol:self didLoadData:data];
  32. }
  33. - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
  34. {
  35. return cachedResponse;
  36. }
  37. - (void)connectionDidFinishLoading:(NSURLConnection *)connection
  38. {
  39. [self.client URLProtocolDidFinishLoading:self];
  40. }

三、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.崩溃报错:

  1. 0   libobjc.A.dylib objc_msgSend + 16
  2. 1   CFNetwork       CFURLProtocol_NS::forgetProtocolClient() + 124

有一点苹果说明的不是很清楚,苹果自己实现CustomHTTPProtocol源码中很好的体现了这一点:
NSURLProtocolClient回调动作必须跟请求的托管发送保持在一个线程、相同的Runloop,具体实现逻辑如下:
(1)在start方法中记录当前线程和Runloop模式;
(2)所有对于NSURLProtocolClient的回调,都在记录的线程、以相同的Runloop模式触发,使用如下方法:

  1. [self performSelector:onThread:withObject:waitUntilDone:modes:];

坑4:httpBody
NSURLProtocol在拦截NSURLSession的POST请求时不能获取到Request中的HTTPBody。苹果官方的解释是Body是NSData类型,而且还没有大小限制。为了性能考虑,拦截时就没有拷贝。

5.如果还有什么其它问题,建议仔细看看苹果的CustomHTTPProtocol源码,应该会发现一些问题。

CustomHTTPProtocol的更多相关文章

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

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

  2. iOS苹果官方Demo合集

    Mirror of Apple’s iOS samples This repository mirrors Apple’s iOS samples. Name Topic Framework Desc ...

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

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

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

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

  5. Sanic框架

    Sanic框架 1. 入门 Sanic 是一款类似Flask的Web服务器,它运行在Python 3.5+上. 除了与Flask功能类似之外,它还支持异步请求处理,这意味着你可以使用Python3.5 ...

  6. sanic官方文档解析之Custom Protocols(自定义协议)和Socket(网络套接字)

    1,Custom Protocol:自定义协议 温馨提示:自定义协议是一个高级用法,大多数的读者不需要用到此功能 通过特殊的自定义协议,你可以改变sanic的协议,自定义协议需要继承子类asyncio ...

随机推荐

  1. linux -------- 使用xshell ,winscp 连接linux 以及一些问题解决

    1. 安装主要就是默认确定 2.连接VM上的centos 系统 设置连接模式 3.通过 ping 虚拟机上的linux系统的IP地址 4.使用xshell 进行连接 5.输入linux 的用户名和密码 ...

  2. springmvc接收参数为日期类型

    用单个Date类型接收日期类型时,会出现报错,加上initBinder的方法 意思是将所有传入的参数都通过此方法,如果过是日期通过日期格式化器进行格式化 如果是接收类型为对象内的属性为Date类型时 ...

  3. Django 模板语言 条件判断

    Django 模板语言 条件判断 ############### 条件 ################## # view 文件 def func(request): ... return rende ...

  4. 常用Java API之Ramdom--用代码模拟猜数小游戏

    常用Java API之Ramdom Ramdom类用来生成随机数字.使用起来也是三个步骤: 1.导包 import java.util.Random; 2.创建 Random r = new Rand ...

  5. php for循环a到z

    首先先介绍2个php内置函数 ord(string):函数返回字符串的首个字符的 ASCII 值.//string:必需.要从中获得 ASCII 值的字符串. chr(ascll): 函数从指定的 A ...

  6. drf--视图家族

    目录 drf 视图家族 前期准备 总路由 urls.py 基表:utils/models.py 模型层 api/models.py 序列化器 api/serializers.py 基本视图(views ...

  7. arm树莓派Raspbian 下安装selenium+chrome

    arm树莓派Raspbian 下安装selenium+chrome 安装selenium pip3 install selenium 安装chromedriver sudo apt-get insta ...

  8. English--不完全及物动词与授予动词

    English|不完全及物动词与授予动词 动词在整个语法中都是占据着十分重要的角色.在五大句型中将动词分为五种:系动词.不及物动词.及物动词.不完全及物动词.授予动词.这篇主讲不完全及物动词和授予动词 ...

  9. .Net Core 遇到 “'windows-1252' is not a supported encoding name.”

    最近用 iTextSharp 拆分 Pdf 文档 加水印的时候遇到错误: 'windows-1252' is not a supported encoding name. For informatio ...

  10. Redux 中间件和异步操作

    回顾一下Redux的数据流转,用户点击按钮发送了一个action,  reducer 就根据action 和以前的state 计算出了新的state, store.subscribe 方法的回调函数中 ...