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 shell脚本单例模式实现

    一.说明 关于单例模式,最开始的是一些小工具,运行起来后再点击运行时会提示已经运行了一个实例,觉得挺有意思但也没有很在意. 前段时间看了前领导的一段代码不太懂是做什么用的,同事查了下资料说是为了实现单 ...

  2. word 转 pfd

    转自: https://www.cnblogs.com/qiwu1314/p/6101400.html demo: public class Doc2Pdf { public static boole ...

  3. SQL Server 中获取所有的用户表、用户视图的信息

    直接贴代码了: 用户表: SELECT s.Name,Convert(varchar(max),tbp.value) as Description FROM sysobjects s AND (tbp ...

  4. linux 1-常用命令

    文件处理命令: 命令格式:命令 [-选项] [参数] 例如:ls -la /etc   多个选项可以写在一起,不区分前后关系,例如 -l 和 -a 一起写成 -la 目录处理命令:ls (就是list ...

  5. 『正睿OI 2019SC Day6』

    动态规划 \(dp\)早就已经是经常用到的算法了,于是老师上课主要都在讲题.今天讲的主要是三类\(dp\):树形\(dp\),计数\(dp\),\(dp\)套\(dp\).其中计数\(dp\)是我很不 ...

  6. 上传文件大小与时间 Web.Config文件 httpRuntime 限制

    httpRuntime  <httpRuntime executionTimeout="90" maxRequestLength="40960" useF ...

  7. winform+CefSharp 实现和js交互

    1:窗体加载的时候添加 webBrowser.RegisterJsObject("getuserName", new _Event()); 2:注册C#方法为js方法 /// // ...

  8. C#采集UVC摄像头画面并支持旋转和分辨率切换

    在项目中,我们会需要控制uvc摄像头,采集其实时画面,或者对其进行旋转.目前市面上大多数USB摄像头都支持UVC协议.那么如何采集呢?当然是采用SharpCamera!因为SharpCamera支持对 ...

  9. TinyXPath 对于xpath标准的支持测试

    xpath是一种基于xml的查询标准,一般的xml解析工具都具有,有的因为卓越的xpath性能而出名,其匹配查询算法牛逼而又高效,和正则有的一拼.虽然我现在大部分从事前端工作了,但是对于原理性的东西还 ...

  10. Django--模型层进阶

    目录 QuerySet对象 可切片 可迭代 惰性查询 缓存机制 何时查询集不会被缓存? exists()与iterator()方法 exists() iterator() 中介模型 查询优化 表数据 ...