iOS URL Loading System / HTTP 重定向 認識與學習


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM