最近在項(xiàng)目里由于電信那邊發(fā)生dns發(fā)生域名劫持,因此需要手動(dòng)將URL請(qǐng)求的域名重定向到指定的IP地址,但是由于請(qǐng)求可能是通過NSURLConnection,NSURLSession或者AFNetworking等方式北专,因此要想統(tǒng)一進(jìn)行處理鳖悠,一開始是想通過Method Swizzling去hook cfnetworking底層方法肺樟,后來發(fā)現(xiàn)其實(shí)有個(gè)更好的方法--NSURLProtocol枝誊。
NSURLProtocol
NSURLProtocol能夠讓你去重新定義蘋果的URL加載系統(tǒng) (URL Loading System)的行為,URL Loading System里有許多類用于處理URL請(qǐng)求唠椭,比如NSURL跳纳,NSURLRequest,NSURLConnection和NSURLSession等贪嫂,當(dāng)URL Loading System使用NSURLRequest去獲取資源的時(shí)候寺庄,它會(huì)創(chuàng)建一個(gè)NSURLProtocol子類的實(shí)例,你不應(yīng)該直接實(shí)例化一個(gè)NSURLProtocol力崇,NSURLProtocol看起來像是一個(gè)協(xié)議斗塘,但其實(shí)這是一個(gè)類,而且必須使用該類的子類餐曹,并且需要被注冊(cè)逛拱。
使用場(chǎng)景
不管你是通過UIWebView, NSURLConnection 或者第三方庫 (AFNetworking, MKNetworkKit等)台猴,他們都是基于NSURLConnection或者 NSURLSession實(shí)現(xiàn)的俱两,因此你可以通過NSURLProtocol做自定義的操作饱狂。
- 重定向網(wǎng)絡(luò)請(qǐng)求
- 忽略網(wǎng)絡(luò)請(qǐng)求宪彩,使用本地緩存
- 自定義網(wǎng)絡(luò)請(qǐng)求的返回結(jié)果
- 一些全局的網(wǎng)絡(luò)請(qǐng)求設(shè)置
攔截網(wǎng)絡(luò)請(qǐng)求
子類化NSURLProtocol并注冊(cè)
@interface CustomURLProtocol : NSURLProtocol
@end
然后在application:didFinishLaunchingWithOptions:方法中注冊(cè)該CustomURLProtocol休讳,一旦注冊(cè)完畢后,它就有機(jī)會(huì)來處理所有交付給URL Loading system的網(wǎng)絡(luò)請(qǐng)求尿孔。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
//注冊(cè)protocol
[NSURLProtocol registerClass:[CustomURLProtocol class]];
return YES;
}
實(shí)現(xiàn)CustomURLProtocol
注冊(cè)好了之后筹麸,現(xiàn)在可以開始實(shí)現(xiàn)NSURLProtocol的一些方法:
- +canInitWithRequest:
這個(gè)方法主要是說明你是否打算處理對(duì)應(yīng)的request雏婶,如果不打算處理物赶,返回NO,URL Loading System會(huì)使用系統(tǒng)默認(rèn)的行為去處理留晚;如果打算處理酵紫,返回YES错维,然后你就需要處理該請(qǐng)求的所有東西,包括獲取請(qǐng)求數(shù)據(jù)并返回給 URL Loading System赋焕。網(wǎng)絡(luò)數(shù)據(jù)可以簡(jiǎn)單的通過NSURLConnection去獲取,而且每個(gè)NSURLProtocol對(duì)象都有一個(gè)NSURLProtocolClient實(shí)例泽示,可以通過該client將獲取到的數(shù)據(jù)返回給URL Loading System蜜氨。
這里有個(gè)需要注意的地方,想象一下飒炎,當(dāng)你去加載一個(gè)URL資源的時(shí)候,URL Loading System會(huì)詢問CustomURLProtocol是否能處理該請(qǐng)求赤赊,你返回YES,然后URL Loading System會(huì)創(chuàng)建一個(gè)CustomURLProtocol實(shí)例然后調(diào)用NSURLConnection去獲取數(shù)據(jù)抛计,然而這也會(huì)調(diào)用URL Loading System照筑,而你在+canInitWithRequest:中又總是返回YES,這樣URL Loading System又會(huì)創(chuàng)建一個(gè)CustomURLProtocol實(shí)例導(dǎo)致無限循環(huán)凝危。我們應(yīng)該保證每個(gè)request只被處理一次,可以通過+setProperty:forKey:inRequest:標(biāo)示那些已經(jīng)處理過的request懦铺,然后在+canInitWithRequest:中查詢?cè)搑equest是否已經(jīng)處理過了支鸡,如果是則返回NO趁窃。
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
//只處理http和https請(qǐng)求
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame))
{
//看看是否已經(jīng)處理過了急前,防止無限循環(huán)
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}
return YES;
}
return NO;
}
- +canonicalRequestForRequest:
通常該方法你可以簡(jiǎn)單的直接返回request,但也可以在這里修改request叔汁,比如添加header,修改host等码邻,并返回一個(gè)新的request另假,這是一個(gè)抽象方法,子類必須實(shí)現(xiàn)边篮。
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
mutableReqeust = [self redirectHostInRequset:mutableReqeust];
return mutableReqeust;
}
+(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";
// 替換域名
NSString *urlString = [originUrlString stringByReplacingCharactersInRange:hostRange withString:ip];
NSURL *url = [NSURL URLWithString:urlString];
request.URL = url;
return request;
}
- +requestIsCacheEquivalent:toRequest:
主要判斷兩個(gè)request是否相同戈轿,如果相同的話可以使用緩存數(shù)據(jù),通常只需要調(diào)用父類的實(shí)現(xiàn)思杯。
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
- -startLoading -stopLoading
這兩個(gè)方法主要是開始和取消相應(yīng)的request,而且需要標(biāo)示那些已經(jīng)處理過的request誊册。
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//標(biāo)示改request已經(jīng)處理過了暖璧,防止無限循環(huán)
[NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
- (void)stopLoading
{
[self.connection cancel];
}
- NSURLConnectionDataDelegate方法
在處理網(wǎng)絡(luò)請(qǐng)求的時(shí)候會(huì)調(diào)用到該代理方法,我們需要將收到的消息通過client返回給URL Loading System澎办。
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
- (void) connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
現(xiàn)在你已經(jīng)可以截取request并做你想做的事了浮驳,這里有個(gè)demo可以參考一下,截取request并重新定向到新的地址至会,具體dns解析方法可以參看DNS解析) 谱俭,如有不對(duì)宵蛀,歡迎指正县貌,哈~(有遇到iOS8 hook sdwebimage會(huì)發(fā)起多次請(qǐng)求,可以看下底下的評(píng)論)