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. bolt继承关系和区别

    先上个图:  具体区别: IRichBolt/IBasicBolt 区别IRichBolt和IBasicBolt IRichBolt继承自IBolt和IComponent.IBasicBolt继承自I ...

  2. [转帖]Mysql各版本介绍及下载

    Mysql各版本介绍及下载 http://blog.itpub.net/12679300/viewspace-1251661/ 原创 MySQL 作者:wzq609 时间:2014-08-15 10: ...

  3. 查看电脑已保存的wifi及密码

    1. 查看以保存的wifi名称  打开cmd(win+r) #查看已保存WiFi名称 netsh wlan show profiles 2. 查看已保存的wifi的密码 netsh wlan show ...

  4. XAML加载的四种方式

    XAML加载与编译可以分为四种: 仅使用代码进行WPF程序的生成 使用代码和未编译的标记 使用代码和编译过的BAML 1.只是用代码进行窗体的生成:优点是可以随意定制应用程序,缺点是没有可视化编辑窗口 ...

  5. C#表达式参数解析算法,N级属性调用,函数值,变量值,常量值

    public static object GetValue(Expression expression) { var names = new Stack<string>(); var ex ...

  6. person类与其子类在使用中的内存情况(含java的改写和c#的屏蔽)

    JAVA 普通person类及调用代码: public class Person { public String xm; public int nl; public void setme(String ...

  7. drf--视图家族

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

  8. 【JVM学习笔记一】Java内存区域

    1. 运行时数据区域 1) 程序计数器 | 线程私有,存储线程运行时所执行字节码的行号,实现分支.循环.跳转.异常处理.线程恢复等基础功能 | Java方法,记录正在执行的虚拟机字节码指令的行号:Na ...

  9. Microsoft SQL Server数据库语法

    目录   关于数据库的语法: 1.创建数据库 create database 数据库名on primary(主文件属性(name,filename,size等)) -用逗号隔开次要主要文件和次要文件( ...

  10. js原生Ajax(十四)

    一.XMLHttpRequest    [使用XMLHttpRequest时,必须将html部署到web服务器中]1) 指定请求1.实例化eg: var http = new XMLHttpReque ...