zoukankan      html  css  js  c++  java
  • CustomHTTPProtocol

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

    一、概念

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

    二、使用方法

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

    [objc] view plain copy
    1. [NSURLProtocol registerClass:[MyURLProtocol class]];  

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

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

    [objc] view plain copy
    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

    [objc] view plain copy
    1. [NSURLProtocol unregisterClass:[MyURLProtocol class]];    
     

    4.实现NSURLProtocol的相关方法

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

    [objc] view plain copy
    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个方法

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


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

    5.实现NSURLConnectionDelegate和NSURLConnectionDataDelegate

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

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


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

    [objc] view plain copy
    1. 0   libobjc.A.dylib objc_msgSend + 16  
    2. 1   CFNetwork       CFURLProtocol_NS::forgetProtocolClient() + 124  

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

    [objc] view plain copy
    1. [self performSelector:onThread:withObject:waitUntilDone:modes:];  


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

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

  • 相关阅读:
    flutter常用内置动画组件
    flutter中的生命周期函数
    Flutter打开第三方应用
    在windows系统搭建并运行一个Flutter项目
    在windows系统搭建Flutter开发环境
    axios的get请求无法设置Content-Type
    解决vue中使用laydate.js选择日期后再修改其他model时日期会被清空问题
    Git commit时提示错误时    解决办法
    CSS3 @font-face属性
    解决webstorm本地IP访问页面出错的问题
  • 原文地址:https://www.cnblogs.com/feng9exe/p/7207200.html
Copyright © 2011-2022 走看看