今天分享攔截網絡請求或者替換IP的方法.
1:應用場景
通過攔截網絡請求後進行服務器地址替換。
2:實現原理
要想攔截,我們需要先弄懂IOS的網絡請求原理,看下圖
# 網絡 <--> NSURLProtocol <--> 網絡庫
大部分的網絡請求都要通過一個叫NSURLProtocol 的抽象類,既然都要通過這個抽象類,那我們是不是通過重載NSURLProtocol的方式進行網絡請求的攔截與過濾呢,答案當然是肯定的.但世界上沒有銀彈,NSURLProtocol不能解決所有的問題,爲什麼呢.因爲NSURLProtocol可以攔截的網絡請求包括NSURLSession,NSURLConnection。現在主流的iOS網絡庫,例如AFNetworking,Alamofire等網絡庫都是基於NSURLSession或NSURLConnection的,所以這些網絡庫的網絡請求都可以被NSURLProtocol所攔截。
PS:基於CFNetwork的網絡請求,以及WKWebView的請求是無法攔截的。例如ASIHTTPRequest,MKNetwokit等網路庫都是基於CFNetwork的,所以這些網絡庫的網絡請求無法被NSURLProtocol攔截。
3.實現步驟
1、創建NSURLProtocol子類
由於NSURLProtocol是一個抽象類,要使用它的時候需要創建它的一個子類。.m文件如下:
#import "ReplaceURLProtocol.h"
// 爲了避免canInitWithRequest和canonicalRequestForRequest的死循環
static NSString *const URLProtocolHandledKey = @"URLProtocolHandledKey";
// 老url網址
static NSString *const old_url = @"baidu.com";
// 新url網址
static NSString *const new_url = @"google.com";
@interface ReplaceURLProtocol()<NSURLSessionDelegate>
@property(nonatomic,strong)NSURLSession * session;
@end
@implementation ReplaceURLProtocol
+(BOOL)canInitWithRequest:(NSURLRequest *)request
{
return YES;
}
//改變請求request
+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
// 業務邏輯寫這裏
return request;
}
//開始請求
-(void)startLoading
{
//業務邏輯寫這裏
}
//停止請求
-(void)stopLoading
{
}
#pragma mark ---- NSURLSessionDelegate
/*
NSURLSessionDelegate接到數據後,通過URLProtocol傳出去
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error)
{
[self.client URLProtocol:self didFailWithError:error];
}
else
{
[self.client URLProtocolDidFinishLoading:self];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
[self.client URLProtocol:self didLoadData:data];
}
@end
2、註冊protocol
基於NSURLConnection或[NSURLSession sharedSession]創建的網絡請求,在AppDelegate的didFinishLaunchingWithOptions方法中調用registerClass方法即可。
//註冊protocol
[NSURLProtocol registerClass:[ReplaceURLProtocol class]];returnYES;}
3、攔截用戶的網絡請求
首先,在攔截到網絡請求後會先調用+(BOOL)canInitWithRequest:(NSURLRequest *)request方法。我們可以在該方法裏進行是否處理這個攔截的邏輯。如設置只對攔截到的http或https請求進行處理。
+(BOOL)canInitWithRequest:(NSURLRequest *)request
{
// 不是網絡請求,不處理
if (![request.URL.scheme isEqualToString:@"http"] &&
![request.URL.scheme isEqualToString:@"https"]) {
return NO;
}
// 指定攔截網絡請求,如:www.baidu.com
if ([request.URL.absoluteString containsString:old_url]) {
return YES;
}else {
return NO;
}
}
接着,會調用+(NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request在該方法中,我們可以對request進行處理。例如修改頭部信息等。最後返回一個處理後的request實例。也可以在該方法裏面將用戶的請求域名替換成別的域名:
/**
設置我們自己的自定義請求
可以在這裏統一加上頭之類的
@param request 應用的此次請求
@return 我們自定義的請求
*/
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
// 設置已處理標誌
[NSURLProtocol setProperty:@(YES)
forKey:kProtocolHandledKey
inRequest:mutableReqeust];
NSLog(@"************ 原始請求的url1 %@",request.URL);
if ([request.URL host].length == 0)
{
return request;
}
NSString * originUrlStr = [request.URL absoluteString];
NSString * originHostStr = [request.URL host];
NSRange hostRange = [originUrlStr rangeOfString:originHostStr];
if (hostRange.location == NSNotFound)
{
return request;
}
//指定攔截網絡請求,如:www.baidu.com
if ([request.URL.absoluteString containsString:old_url]) {
//定向到百度搜索
NSString * ip = new_url;
NSString * urlStr = [originUrlStr stringByReplacingCharactersInRange:hostRange withString:ip];
NSURL * url = [NSURL URLWithString:urlStr];
mutableReqeust.URL = url;
NSLog(@"************ 替換後的url 1 %@",mutableReqeust.URL);
return [mutableReqeust copy];
}
else{
return request;
}
4、轉發
-(void)startLoading將處理過的request重新發送出去。發送的形式,可以是基於NSURLConnection,NSURLSession甚至CFNetwork。我們也可以在該方法裏面設置網絡代理,如下我們設置一個代理後,重新創建一個NSURLSession將網絡請求發送出去:
// 重新父類的開始加載方法
- (void)startLoading {
NSMutableURLRequest * mutableRequest = [[self request] mutableCopy];
NSLog(@"************ 開始請求 %@",mutableRequest.URL);
NSURLSessionConfiguration * configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];//創建一個臨時會話配置
//網絡請求
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
// 注 這裏也可以添加代理 捕獲用戶請求數據
NSURLSessionTask * task = [self.session dataTaskWithRequest:self.request];
[task resume];//開始任務
}
5、回調
上面使用的是NSURLSession請求,所以我們通過NSURLSessionDelegate來接收網絡請求的數據(成功或失敗等信息):
#pragma mark ---- NSURLSessionDelegate
/*
NSURLSessionDelegate接到數據後,通過URLProtocol傳出去
*/
//失敗
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error)
{
[self.client URLProtocol:self didFailWithError:error]; //請求錯誤
}
else
{
[self.client URLProtocolDidFinishLoading:self]; //完成加載
}
}
//接收到響應
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; //創建一個響應(緩存策略:不緩存)
completionHandler(NSURLSessionResponseAllow);
}
//接收到數據
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
[self.client URLProtocol:self didLoadData:data]; //接收到數據
}
6、結束
在-(void)stopLoading完成網絡請求的操作
//結束請求
-(void)stopLoading
{
[self.session invalidateAndCancel];
self.session = nil;
}