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. Cmder下ssh免密登录配置

    1.本地生成ssh-key 在本地cmder终端下运行下面的命令生成ssh的公钥和私钥文件: ssh-keygen -t rsa 其中,.ssh/id_rsa为私钥文件,留在本地使用,而.ssh/id ...

  2. 修改ssh服务器默认端口号

    1.查看当前ssh服务器端口号 在修改ssh服务器的端口号之前,首先查看ssh服务器监听的端口号,使用netstat命令: $ sudo netstat -tunlp | grep "ssh ...

  3. Nginx官方文档翻译(转)

    add by zhj: 由并发网组织翻译,赞 <Nginx官方文档>WebSocket代理 <Nginx官方文档>配置文件中的单位 <Nginx官方文档>控制ngi ...

  4. 『正睿OI 2019SC Day3』

    容斥原理 容斥原理指的是一种排重,补漏的计算思想,形式化的来说,我们有如下公式: \[\left | \bigcup_{i=1}^nS_i \right |=\sum_{i}|S_i|-\sum_{i ...

  5. k8s-Namespace(命名空间)

    k8s-Namespace(命名空间) Namespace(命名空间)是Kubernetes系统中的另一个非常重要的概念,通过将系统内部的对象“分配”到不同的Namespace中,形成逻辑上分组的不同 ...

  6. 开源规则引擎 Drools 学习笔记 之 -- 1 cannot be cast to org.drools.compiler.kie.builder.impl.InternalKieModule

    直接进入正题 我们在使用开源规则引擎 Drools 的时候, 启动的时候可能会抛出如下异常: Caused by: java.lang.ClassCastException: cn.com.cheng ...

  7. ASP.NET MVC实现单用户登录

    现在许多网站都要求登录后才能进行进一步的操作,当不允许多用户同时登录一个帐号时,就需要一种机制,当再登录一个相同的帐号时,前面登录的人被挤下线,或者禁止后面的人登录.这里实现的是前一种功能. 网上有许 ...

  8. JS实现文件自动上传

    JS引用: <script type="text/javascript" src="~/bootstrap/js/fileinput.min.js"> ...

  9. Windows 查看端口占用进程并关闭

    当我们在运行一些软件需要特定软件(如tomcat)时,有可能会碰上端口被占用的情况,这时候我们可能就需要更改端口或把占用端口的进程结束掉,因为更换端口可能会导致当前环境产生一些的问题或是需要重新配置其 ...

  10. Schnorr签名介绍

    Schnorr签名介绍 来源 https://panzhibiao.com/2019/02/28/schnorr-sigature/ https://github.com/bitcoin/bitcoi ...