剛剛開始寫博客,可能描述的思路不是很清晰,還請(qǐng)各位看官擔(dān)待,寫的不好,如有紕漏,望請(qǐng)斧正,感覺不盡.
當(dāng)我們做混合APP 或者是 純網(wǎng)頁的APP(就是只加一個(gè)UIWebView的套殼APP)時(shí),經(jīng)常會(huì)遇到討厭的廣告劫持!!!
特別是那些 營運(yùn)商 的 DNS劫持,植入廣告,真的是太惡心了.
來現(xiàn)在過濾DNS廣告好像有2種:
-
HTTPS協(xié)議 --- 這個(gè)安全,可靠,推薦!!! 但是如果都能用這個(gè)那就沒這篇文章什么事了.
2.設(shè)置過濾規(guī)則 ---這個(gè)也是Adblock (最強(qiáng)的廣告攔截插件,沒用的,我也不知道說什么了) 的攔截原理.
現(xiàn)在我們是要模仿它,為我們的UIWebView 加載的網(wǎng)頁也加上攔截機(jī)制,過濾這些廣告.
這個(gè)方法缺點(diǎn)也是很大的,需要維護(hù)過濾規(guī)則 比較麻煩.
- 如果你需要加載的網(wǎng)頁都是從外網(wǎng)上拉下來的,那這個(gè)目前好像沒有什么解決辦法.(如果讀者還有什么高招,還請(qǐng)留言告知!)只能 保持最新的過濾規(guī)則,哈哈哈~~
- 如果你加載的網(wǎng)頁全部都是在你自己的服務(wù)器上,那就好辦多了,直接不是你域名和你合作的域名內(nèi)的請(qǐng)求都不通過就行了. 注意!! 像第三方登錄之類的都會(huì)被攔截的,因?yàn)樗械南到y(tǒng)請(qǐng)求都會(huì)經(jīng)過你的過濾規(guī)則.(筆者就被坑過,哈哈哈哈)
眾所周知,我們?cè)鷌OS的UIWebView 要于html網(wǎng)頁進(jìn)行交互 有兩個(gè)途徑
1: 通過UIWebView 的 stringByEvaluatingJavaScriptFromString: 方法實(shí)現(xiàn)與HTML網(wǎng)頁的交互.
注意: 這個(gè)方法必須在網(wǎng)頁加載完成之后才會(huì)有效,也就是再 delegate 中的 webViewDidFinishLoad: 方法執(zhí)行過之后
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//獲取某個(gè)id的標(biāo)簽中的內(nèi)容
NSString *content = [webView stringByEvaluatingJavaScriptFromString:
@"document.getElementById('你的某個(gè)標(biāo)簽的id').innerHTML"];
}
2: ios7 以上有了這個(gè) 神器!!<JavaScriptCore/JavaScriptCore.h> 框架.
使用這個(gè)框架進(jìn)行交互的網(wǎng)上有這相當(dāng)多的教程,我這就放幾個(gè)傳送門就好了.
這個(gè)有Swift 版 和 OC版的,協(xié)議模型注入 (JSExport )
這個(gè)是使用 stringByEvaluatingJavaScriptFromString 方法的
NSURLProtocol
概念
NSURLProtocol :它可以輕松地重定義整個(gè)URL Loading System。當(dāng)你注冊(cè)自定義NSURLProtocol后,就有機(jī)會(huì)對(duì)所有的請(qǐng)求進(jìn)行統(tǒng)一的處理,基于這一點(diǎn)它可以讓你實(shí)現(xiàn)以下的功能
·自定義請(qǐng)求和響應(yīng)
·提供自定義的全局緩存支持
·重定向網(wǎng)絡(luò)請(qǐng)求
·提供HTTP Mocking (方便前期測(cè)試)
·其他一些全局的網(wǎng)絡(luò)請(qǐng)求修改需求
使用方法
繼承NSURLPorotocl,并注冊(cè)你的NSURLProtocol
完整源代碼最后附上
[NSURLProtocol registerClass:[CCURLProtocol class]];
實(shí)現(xiàn)NSURLProtocol的相關(guān)方法
當(dāng)遍歷到我們自定義的NSURLProtocol時(shí),系統(tǒng)先會(huì)調(diào)用canInitWithRequest:這個(gè)方法。顧名思義,這是整個(gè)流程的入口,只有這個(gè)方法返回YES我們才能夠繼續(xù)后續(xù)的處理。我們可以在這個(gè)方法的實(shí)現(xiàn)里面進(jìn)行請(qǐng)求的過濾,篩選出需要進(jìn)行處理的請(qǐng)求。
+ (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;
}
當(dāng)篩選出需要處理的請(qǐng)求后,就可以進(jìn)行后續(xù)的處理,需要至少實(shí)現(xiàn)如下4個(gè)方法
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
//在這里網(wǎng)頁出現(xiàn)任何變動(dòng)(加載JS ,CSS 什么的都能攔截得到) ,發(fā)送個(gè)通知 do something
//[[NSNotificationCenter defaultCenter] postNotificationName:PageChangeNotification object:self userInfo:nil];
NSLog(@"canonicalRequestForRequest:%@",request.URL.absoluteString);
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
#warning --- 攔截URL 進(jìn)行規(guī)則過濾
//在這個(gè)方法 redirectHostInRequset 里面去過濾你要過濾的東西
mutableReqeust = [self redirectHostInRequset:mutableReqeust];
return mutableReqeust;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//打標(biāo)簽,防止無限循環(huán)
[NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
//
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
//
- (void)stopLoading
{
[self.connection cancel];
}
canonicalRequestForRequest: 進(jìn)行過濾 ,返回規(guī)范化后的request
**requestIsCacheEquivalent:toRequest: **用于判斷你的自定義reqeust是否相同,這里返回默認(rèn)實(shí)現(xiàn)即可。它的主要應(yīng)用場(chǎng)景是某些直接使用緩存而非再次請(qǐng)求網(wǎng)絡(luò)的地方。
startLoading和stopLoading 實(shí)現(xiàn)請(qǐng)求和取消流程。
redirectHostInRequset: 方法的實(shí)現(xiàn)
+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request
{
//沒有域名的URL請(qǐng)求就原路返回,不能返回nil ,不然在跳轉(zhuǎn)APP的時(shí)候會(huì)被攔截返回空出錯(cuò)(或者其他情況).
//eg: mqq://im/chat?chat_type=wpa&uin=1299101858&version=1&src_type=web 跳轉(zhuǎn)到指定QQ用戶的聊天窗口
if ([request.URL host].length == 0) {
return request;
}
NSString *originUrlString = request.URL.absoluteString;
//獲取主機(jī)名字,在這里執(zhí)行正則匹配
NSString *originHostString = [request.URL host];
NSRange hostRange = [originUrlString rangeOfString:originHostString];
//找不到主機(jī)名,返回
if (hostRange.location == NSNotFound) {
return request;
}
if (originUrlString != nil) {
//獲取攔截的黑白名單數(shù)據(jù)(過濾名單)
//這個(gè)是自定義方法,你們自己隨意發(fā)揮,哈哈哈.
#warning --- 思路實(shí)現(xiàn)
/*
這里的匹配黑白名單一般只是**匹配域名**
思路 1:匹配白名單->匹配黑名單-> 如果兩個(gè)都沒有,就向服務(wù)器打印日志. (拉外網(wǎng))
思路 2:匹配白名單
以下代碼運(yùn)用思路1 實(shí)現(xiàn)
eg: 這個(gè)是過濾的規(guī)則的例子格式
.*(.qq.com|api.weibo.com|.weibo.com|.baidu.com|.weixin.qq.com|.sina.com|.sina.cn).*
*/
NSDictionary *dic = [self getHoldUpDic];
if (!dic)//如果為空不處理黑白名單
{
return request;
}
//白名單
NSString *whiteList = dic[@"whiteList"];
//黑名單
NSString * blackList = dic[@"blackList"];
#pragma mark - 白名單匹配
//1.1將正則表達(dá)式設(shè)置為OC規(guī)則
if (![whiteList isEqualToString:@""])
{
NSRegularExpression *regular1 = [[NSRegularExpression alloc] initWithPattern:whiteList options:NSRegularExpressionCaseInsensitive error:nil];
//2.利用規(guī)則測(cè)試字符串獲取匹配結(jié)果
NSArray *results1 = [regular1 matchesInString:originUrlString options:0 range:NSMakeRange(0, originUrlString.length)];
if (results1.count > 0)//是白名單,允許訪問
{
return request;
}
}
#pragma mark - 黑名單匹配
if (![blackList isEqualToString:@""])
{
//1.1將正則表達(dá)式設(shè)置為OC規(guī)則
NSRegularExpression *regular2 = [[NSRegularExpression alloc] initWithPattern:blackList options:NSRegularExpressionCaseInsensitive error:nil];
//2.利用規(guī)則匹配字符串獲取匹配結(jié)果
NSArray *results2 = [regular2 matchesInString:originUrlString options:0 range:NSMakeRange(0, originUrlString.length)];
if (results2.count > 0 ) //黑名單,返回nil;
{
return request;
}
}
if (![whiteList isEqualToString:@""]&&![blackList isEqualToString:@""])
{
#pragma mark - 發(fā)送到服務(wù)端打印日志
//do something
}
}
return request;
}
實(shí)現(xiàn)NSURLConnectionDelegate和NSURLConnectionDataDelegate
#warning 筆者聲明:這里是有大坑的,最好是全部的代理方法都實(shí)現(xiàn)一遍,不然有可能會(huì)出現(xiàn)各種問題
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[self.client URLProtocol:self
didFailWithError:error];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
// if (response != nil)
// {
// [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
// }
#warning lanjie wen ti 這里需要回傳[self client] 消息,那么需要重定向的網(wǎng)頁就會(huì)出現(xiàn)問題:host不對(duì)或者造成跨域調(diào)用導(dǎo)致資源無法加載
[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
// return request; // 這里如果返回 request 會(huì)重新請(qǐng)求一次
return nil;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
}
- (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];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return cachedResponse;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self.client URLProtocolDidFinishLoading:self];
}
繼承的 CCURLProtocol 類的代碼
CCURLProtocol.h 文件
#import <Foundation/Foundation.h>
@interface CCURLProtocol : NSURLProtocol
@end
CCURLProtocol.m 文件
#import "CCURLProtocol.h"
#import "AFNetWork.h"
static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";
static NSDictionary *_holdUpDic;
@interface CCURLProtocol ()<NSURLConnectionDelegate>
@property (nonatomic, strong) NSURLConnection *connection;
@end
@implementation CCURLProtocol
+(NSDictionary *)getHoldUpDic
{
if (!_holdUpDic)
{
#pragma mark - 這里是獲取黑白名單的數(shù)據(jù)
/*
[AFNetWork postWithURL:@"" Params:@"" Success:^(NSURLSessionDataTask *task, id responseObject) {
//獲取廣告攔截資料
_holdUpDic = responseObject;
//寫入本地plist文件
BOOL success = [_holdUpDic writeToFile:path atomically:YES];
if (success )
{
NSLog(@"寫入成功");
}else
{
NSLog(@"寫入失敗");
}
}];
_holdUpDic = [NSDictionary dictionaryWithContentsOfFile:path];
*/
}
return _holdUpDic;
}
+ (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;
}
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
//網(wǎng)頁發(fā)生變動(dòng)
// [[NSNotificationCenter defaultCenter] postNotificationName:PageChangeNotification object:self userInfo:nil];
// NSLog(@"canonicalRequestForRequest:%@",request.URL.absoluteString);
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
mutableReqeust = [self redirectHostInRequset:mutableReqeust];
return mutableReqeust;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//打標(biāo)簽,防止無限循環(huán)
[NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
//
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
//
- (void)stopLoading
{
[self.connection cancel];
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
[self.client URLProtocol:self
didFailWithError:error];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
// if (response != nil)
// {
// [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
// }
#warning lanjie wen ti 這里需要回傳[self client] 消息,那么需要重定向的網(wǎng)頁就會(huì)出現(xiàn)問題:host不對(duì)或者造成跨域調(diào)用導(dǎo)致資源無法加載
[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
// return request; // 這里如果返回 request 會(huì)重新請(qǐng)求一次
return nil;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
}
- (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];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
return cachedResponse;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[self.client URLProtocolDidFinishLoading:self];
}
#pragma mark -- private
+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request
{
//沒有域名的URL請(qǐng)求就原路返回,不能返回nil ,不然在跳轉(zhuǎn)APP的時(shí)候會(huì)被攔截返回空出錯(cuò)(或者其他情況).
//eg: mqq://im/chat?chat_type=wpa&uin=1299101858&version=1&src_type=web 跳轉(zhuǎn)到指定QQ用戶的聊天窗口
if ([request.URL host].length == 0) {
return request;
}
NSString *originUrlString = request.URL.absoluteString;
//獲取主機(jī)名字,在這里執(zhí)行正則匹配
NSString *originHostString = [request.URL host];
NSRange hostRange = [originUrlString rangeOfString:originHostString];
//找不到主機(jī)名,返回
if (hostRange.location == NSNotFound) {
return request;
}
if (originUrlString != nil) {
//獲取攔截的黑白名單數(shù)據(jù)(過濾名單)
//這個(gè)是自定義方法,你們自己隨意發(fā)揮,哈哈哈.
#warning --- 思路實(shí)現(xiàn)
/*
這里的匹配黑白名單一般只是**匹配域名**
思路 1:匹配白名單->匹配黑名單-> 如果兩個(gè)都沒有,就向服務(wù)器打印日志. (拉外網(wǎng))
思路 2:匹配白名單
以下代碼運(yùn)用思路1 實(shí)現(xiàn)
eg: 這個(gè)是過濾的規(guī)則的例子格式
.*(.qq.com|api.weibo.com|.weibo.com|.baidu.com|.weixin.qq.com|.sina.com|.sina.cn).*
*/
NSDictionary *dic = [self getHoldUpDic];
if (!dic)//如果為空不處理黑白名單
{
return request;
}
//白名單
NSString *whiteList = dic[@"whiteList"];
//黑名單
NSString * blackList = dic[@"blackList"];
#pragma mark - 白名單匹配
//1.1將正則表達(dá)式設(shè)置為OC規(guī)則
if (![whiteList isEqualToString:@""])
{
NSRegularExpression *regular1 = [[NSRegularExpression alloc] initWithPattern:whiteList options:NSRegularExpressionCaseInsensitive error:nil];
//2.利用規(guī)則測(cè)試字符串獲取匹配結(jié)果
NSArray *results1 = [regular1 matchesInString:originUrlString options:0 range:NSMakeRange(0, originUrlString.length)];
if (results1.count > 0)//是白名單,允許訪問
{
return request;
}
}
#pragma mark - 黑名單匹配
if (![blackList isEqualToString:@""])
{
//1.1將正則表達(dá)式設(shè)置為OC規(guī)則
NSRegularExpression *regular2 = [[NSRegularExpression alloc] initWithPattern:blackList options:NSRegularExpressionCaseInsensitive error:nil];
//2.利用規(guī)則匹配字符串獲取匹配結(jié)果
NSArray *results2 = [regular2 matchesInString:originUrlString options:0 range:NSMakeRange(0, originUrlString.length)];
if (results2.count > 0 ) //黑名單,返回nil;
{
return request;
}
}
if (![whiteList isEqualToString:@""]&&![blackList isEqualToString:@""])
{
#pragma mark - 發(fā)送到服務(wù)端打印日志
//do something
}
}
return request;
}
@end
demo 現(xiàn)在正在整理,之后再奉上.如有疑問,歡迎留言.
以上來源參考自網(wǎng)絡(luò),如有侵權(quán)請(qǐng)私信筆者.