一個朋友問了我一個問題,需求是這樣的:他要用本地的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