一个朋友问了我一个问题,需求是这样的:他要用本地的H5资源 替换 链接资源, 但是判断链接资源时候 因为一些操作请求本地化了之后 一些操作比如请求服务器使用的是http开头,然而本地资源一直是以file://开头, 这样的
然后 shouldStart 方法中 的request(post请求) body 是空的, 这样就无法到底知道是哪个链接了.于是就不能触发相应的资源方法.
我思考好一阵子,起初 我以为这个就是应该是 用 shouldStart 协议 根据 url 判断 是否需要本地处理. 最终
解决办法是 “通过URL加载系统 (URL Loading System),拦截需要的h5资源,把本地数据直接展示
原因:
(1)利用UIWebView来处理回调地址,或者做拦截操作 一般都是得请求shouldStart协议中,甚至 需要知道请求回调结果
就是HTTP重定向的问题! 之前 真的没有用过这个NSURLProtocol 协议去处理过问题.今天学习一下.
HTTP重定向: (在网上查资料 了解到的一个比较系统的解释)
Redirect(客户端重定向 我们这里研究的是)
标准意义上的“重定向”指的是HTTP重定向,它是HTTP协议规定的一种机制。这种机制是这样工作的:当client向server发送一个请求,要求获取一个资源时,在server接收到这个请求后发现请求的这个资源实际存放在另一个位置,于是server在返回的response中写入那个请求资源的正确的URL,并设置reponse的状态码为301(表示这是一个要求浏览器重定向的response),当client接受到这个response后就会根据新的URL重新发起请求。重定向有一个典型的特症,即,当一个请求被重定向以后,最终浏览器上显示的URL往往不再是开始时请求的那个URL了。这就是重定向的由来。
我们在实际开发过程中 遇到过这种 服务端重定向的情况,就是 和第三方电商合作,需要展示一个订单列表,就是我们作为客户前端 向服务端发起请求,然后服务端发现对应资源在商城里,然后回调结果是301 /也有302,然后会继续请求商城的订单列表的url 然后就正常展示了.
我们这么做的好处是: 不需要在客户端写死 订单列表的请求,服务端可以动态修改订单列表的链接.
首先是这个注册 NSURLProtocol协议方法可以解决的问题:
笼统就是 上面提到的"Redirect(客户端重定向)",对上层的 NSURLRequest 进行拦截,然后按需求响应操作.
NSURLProtocol具体可以做:
(1)如果需要,可以对html页面中的图片做本地化处理
(2)Mock假的response
(3)对请求头做规范化处理
(4)在上层应用不感知情况下,实现一套代理机制
(5)过滤请求、响应中敏感信息
(6)对已有协议做改进、补充处理
这些是网上查得到的,总得来说就是拦截请求时候 可以高度自定义请求方式, 拦截请求结果 高度自定义处理方法. 在实际开发中,根据具体需求处理.
我写了一个 实例 参考SectionDemo 的CustomUrlProtocol
// // MyURLProtocol.h // NSURLProtocolExample // // Created by HF on 2017/5/3. // Copyright © 2017年 HF. All rights reserved. // #import <Foundation/Foundation.h> @interface MyURLProtocol : NSURLProtocol @property (nonatomic, strong) NSMutableData *mutableData; @property (nonatomic, strong) NSURLResponse *response; @end
// // MyURLProtocol.m // NSURLProtocolExample // // Created by HF on 2017/5/3. // Copyright © 2017年 HF. All rights reserved. // #import "MyURLProtocol.h" #import "AppDelegate.h" #import "CachedURLResponseModel+CoreDataProperties.h" @interface MyURLProtocol () <NSURLConnectionDelegate> @property (nonatomic, strong) NSURLConnection *connection; @end @implementation MyURLProtocol /** * 是否拦截处理指定的请求 * * @param request 指定的请求 * * @return 返回YES表示要拦截处理,返回NO表示不拦截处理 */ + (BOOL)canInitWithRequest:(NSURLRequest *)request { static NSUInteger requestCount = 0; NSLog(@"Request #%lu: URL = %@", (unsigned long)requestCount++, request); //看看是否已经处理过了,防止无限循环 if ([NSURLProtocol propertyForKey:@"MyURLProtocolHandledKey" inRequest:request]) { return NO; } return YES; } #pragma mark - NSURLProtocol /** 重写这个协议 目的是按需求条件筛选出目标请求,同时对目标request进行进一步完整包装与定义 @param request request @return NSURLRequest */ + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { NSMutableURLRequest *mutableReqeust = [request mutableCopy]; mutableReqeust = [self redirectHostInRequset:mutableReqeust]; return mutableReqeust; //return request; } + (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b { return [super requestIsCacheEquivalent:a toRequest:b]; } - (void)startLoading { //如果想直接返回缓存的结果,构建一个CachedURLResponse对象 // 1. CachedURLResponseModel *cachedResponse = [self cachedResponseForCurrentRequest]; if (cachedResponse) { NSLog(@"serving response from cache"); // 2. NSData *data = cachedResponse.data; NSString *mimeType = cachedResponse.mimeType; NSString *encoding = cachedResponse.encoding; // 3. NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL MIMEType:mimeType expectedContentLength:data.length textEncodingName:encoding]; // 4. [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; [self.client URLProtocol:self didLoadData:data]; [self.client URLProtocolDidFinishLoading:self]; } else { // 5. NSLog(@"serving response from NSURLConnection"); NSMutableURLRequest *newRequest = [self.request mutableCopy]; //标记"tag",防止无限循环 [NSURLProtocol setProperty:@YES forKey:@"MyURLProtocolHandledKey" inRequest:newRequest]; self.connection = [NSURLConnection connectionWithRequest:newRequest delegate:self]; } } - (void)stopLoading { [self.connection cancel]; self.connection = nil; } #pragma mark - NSURLConnectionDelegate - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; self.response = response; self.mutableData = [[NSMutableData alloc] init]; } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [self.client URLProtocol:self didLoadData:data]; [self.mutableData appendData:data]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [self.client URLProtocolDidFinishLoading:self]; [self saveCachedResponse]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { [self.client URLProtocol:self didFailWithError:error]; } #pragma mark -- private +(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request { if ([request.URL host].length == 0) { return request; } NSString *originUrlString = [request.URL absoluteString]; NSString *originHostString = [request.URL host]; NSRange hostRange = [originUrlString rangeOfString:originHostString]; if (hostRange.location == NSNotFound) { return request; } //定向到bing搜索主页 NSString *ip = @"cn.bing.com"; // 替换host NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip]; NSURL *url = [NSURL URLWithString:urlString]; request.URL = url; return request; } - (void)saveCachedResponse { NSLog(@"saving cached response"); // if (![self.request.URL.absoluteString isEqualToString:@"cn.bing.com"]) return; // 1. AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication]delegate]; NSManagedObjectContext *context = delegate.managedObjectContext; // 2. CachedURLResponseModel *cachedResponse = [NSEntityDescription insertNewObjectForEntityForName:@"CachedURLResponseModel"inManagedObjectContext:context]; cachedResponse.data = self.mutableData; cachedResponse.url = self.request.URL.absoluteString; cachedResponse.timestamp = [NSDate date]; cachedResponse.mimeType = self.response.MIMEType; cachedResponse.encoding = self.response.textEncodingName; // 3. NSError *error; BOOL const success = [context save:&error]; if (!success) { NSLog(@"Could not cache the response."); } } - (CachedURLResponseModel *)cachedResponseForCurrentRequest { // 1. AppDelegate *delegate = (AppDelegate *)[[UIApplication sharedApplication]delegate]; NSManagedObjectContext *context = delegate.managedObjectContext; // 2. NSFetchRequest *fetchRequest = [CachedURLResponseModel fetchRequest]; // 3. NSPredicate *predicate = [NSPredicate predicateWithFormat:@"url == %@", self.request.URL.absoluteString]; [fetchRequest setPredicate:predicate]; // 4. NSError *error; NSArray *result = [context executeFetchRequest:fetchRequest error:&error]; // 5. if (result && result.count > 0) { return result[0]; } return nil; } @end
然后在需要的请求前注册这个类
[NSURLProtocol registerClass:[MyURLProtocol class]];
请求结束 注销这个类
[NSURLProtocol unregisterClass:[MyURLProtocol class]];
在MyURLProtocol 里面 针对目标请求 具体按需处理
这里 我举得例子 是:
首先 :请求 "https://www.raywenderlich.com" 使用 "MyURLProtocol" 注册 拦截 该请求 然后重定向到 "cn.bing.com"上
其次:对重定向 对象 添加缓存
注意:
这里只是模拟过程 没有特此针对判断具体链接,真正使用的时候大家一定要逻辑严谨,并且根据具体需求要做适当优化,才能灵活达到举一反三目的。
参考:
1. http://blog.csdn.net/xanxus46/article/details/51946432
2 .http://blog.csdn.net/bluishglc/article/details/7953614
3.http://www.molotang.com/
4.https://www.raywenderlich.com/59982/nsurlprotocol-tutorial