剛剛開始寫博客,可能描述的思路不是很清晰,還請各位看官擔待,寫的不好,如有紕漏,望請斧正,感覺不盡.
當我們做混合APP 或者是 純網頁的APP(就是只加一個UIWebView的套殼APP)時,經常會遇到討厭的廣告劫持!!!
特別是那些 營運商 的 DNS劫持,植入廣告,真的是太惡心了.
來現在過濾DNS廣告好像有2種:
-
HTTPS協議 --- 這個安全,可靠,推薦!!! 但是如果都能用這個那就沒這篇文章什么事了.
2.設置過濾規則 ---這個也是Adblock (最強的廣告攔截插件,沒用的,我也不知道說什么了) 的攔截原理.
現在我們是要模仿它,為我們的UIWebView 加載的網頁也加上攔截機制,過濾這些廣告.
這個方法缺點也是很大的,需要維護過濾規則 比較麻煩.
- 如果你需要加載的網頁都是從外網上拉下來的,那這個目前好像沒有什么解決辦法.(如果讀者還有什么高招,還請留言告知!)只能 保持最新的過濾規則,哈哈哈~~
- 如果你加載的網頁全部都是在你自己的服務器上,那就好辦多了,直接不是你域名和你合作的域名內的請求都不通過就行了. 注意!! 像第三方登錄之類的都會被攔截的,因為所有的系統請求都會經過你的過濾規則.(筆者就被坑過,哈哈哈哈)
眾所周知,我們原生iOS的UIWebView 要于html網頁進行交互 有兩個途徑
1: 通過UIWebView 的 stringByEvaluatingJavaScriptFromString: 方法實現與HTML網頁的交互.
注意: 這個方法必須在網頁加載完成之后才會有效,也就是再 delegate 中的 webViewDidFinishLoad: 方法執行過之后
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
//獲取某個id的標簽中的內容
NSString *content = [webView stringByEvaluatingJavaScriptFromString:
@"document.getElementById('你的某個標簽的id').innerHTML"];
}
2: ios7 以上有了這個 神器!!<JavaScriptCore/JavaScriptCore.h> 框架.
使用這個框架進行交互的網上有這相當多的教程,我這就放幾個傳送門就好了.
這個有Swift 版 和 OC版的,協議模型注入 (JSExport )
這個是使用 stringByEvaluatingJavaScriptFromString 方法的
NSURLProtocol
概念
NSURLProtocol :它可以輕松地重定義整個URL Loading System。當你注冊自定義NSURLProtocol后,就有機會對所有的請求進行統一的處理,基于這一點它可以讓你實現以下的功能
·自定義請求和響應
·提供自定義的全局緩存支持
·重定向網絡請求
·提供HTTP Mocking (方便前期測試)
·其他一些全局的網絡請求修改需求
使用方法
繼承NSURLPorotocl,并注冊你的NSURLProtocol
完整源代碼最后附上
[NSURLProtocol registerClass:[CCURLProtocol class]];
實現NSURLProtocol的相關方法
當遍歷到我們自定義的NSURLProtocol時,系統先會調用canInitWithRequest:這個方法。顧名思義,這是整個流程的入口,只有這個方法返回YES我們才能夠繼續后續的處理。我們可以在這個方法的實現里面進行請求的過濾,篩選出需要進行處理的請求。
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
//只處理http和https請求
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame )||
([scheme caseInsensitiveCompare:@"https"] == NSOrderedSame ) )
{
//看看是否已經處理過了,防止無限循環
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}
return YES;//處理
}
return NO;
}
當篩選出需要處理的請求后,就可以進行后續的處理,需要至少實現如下4個方法
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
//在這里網頁出現任何變動(加載JS ,CSS 什么的都能攔截得到) ,發送個通知 do something
//[[NSNotificationCenter defaultCenter] postNotificationName:PageChangeNotification object:self userInfo:nil];
NSLog(@"canonicalRequestForRequest:%@",request.URL.absoluteString);
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
#warning --- 攔截URL 進行規則過濾
//在這個方法 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];
//打標簽,防止無限循環
[NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
//
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
//
- (void)stopLoading
{
[self.connection cancel];
}
canonicalRequestForRequest: 進行過濾 ,返回規范化后的request
**requestIsCacheEquivalent:toRequest: **用于判斷你的自定義reqeust是否相同,這里返回默認實現即可。它的主要應用場景是某些直接使用緩存而非再次請求網絡的地方。
startLoading和stopLoading 實現請求和取消流程。
redirectHostInRequset: 方法的實現
+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request
{
//沒有域名的URL請求就原路返回,不能返回nil ,不然在跳轉APP的時候會被攔截返回空出錯(或者其他情況).
//eg: mqq://im/chat?chat_type=wpa&uin=1299101858&version=1&src_type=web 跳轉到指定QQ用戶的聊天窗口
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;
}
if (originUrlString != nil) {
//獲取攔截的黑白名單數據(過濾名單)
//這個是自定義方法,你們自己隨意發揮,哈哈哈.
#warning --- 思路實現
/*
這里的匹配黑白名單一般只是**匹配域名**
思路 1:匹配白名單->匹配黑名單-> 如果兩個都沒有,就向服務器打印日志. (拉外網)
思路 2:匹配白名單
以下代碼運用思路1 實現
eg: 這個是過濾的規則的例子格式
.*(.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將正則表達式設置為OC規則
if (![whiteList isEqualToString:@""])
{
NSRegularExpression *regular1 = [[NSRegularExpression alloc] initWithPattern:whiteList options:NSRegularExpressionCaseInsensitive error:nil];
//2.利用規則測試字符串獲取匹配結果
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將正則表達式設置為OC規則
NSRegularExpression *regular2 = [[NSRegularExpression alloc] initWithPattern:blackList options:NSRegularExpressionCaseInsensitive error:nil];
//2.利用規則匹配字符串獲取匹配結果
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 - 發送到服務端打印日志
//do something
}
}
return request;
}
實現NSURLConnectionDelegate和NSURLConnectionDataDelegate
#warning 筆者聲明:這里是有大坑的,最好是全部的代理方法都實現一遍,不然有可能會出現各種問題
- (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] 消息,那么需要重定向的網頁就會出現問題:host不對或者造成跨域調用導致資源無法加載
[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
// return request; // 這里如果返回 request 會重新請求一次
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 - 這里是獲取黑白名單的數據
/*
[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請求
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame )||([scheme caseInsensitiveCompare:@"https"] == NSOrderedSame ))
{
//看看是否已經處理過了,防止無限循環
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}
return YES;//處理
}
return NO;
}
+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
//網頁發生變動
// [[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];
//打標簽,防止無限循環
[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] 消息,那么需要重定向的網頁就會出現問題:host不對或者造成跨域調用導致資源無法加載
[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
// return request; // 這里如果返回 request 會重新請求一次
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請求就原路返回,不能返回nil ,不然在跳轉APP的時候會被攔截返回空出錯(或者其他情況).
//eg: mqq://im/chat?chat_type=wpa&uin=1299101858&version=1&src_type=web 跳轉到指定QQ用戶的聊天窗口
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;
}
if (originUrlString != nil) {
//獲取攔截的黑白名單數據(過濾名單)
//這個是自定義方法,你們自己隨意發揮,哈哈哈.
#warning --- 思路實現
/*
這里的匹配黑白名單一般只是**匹配域名**
思路 1:匹配白名單->匹配黑名單-> 如果兩個都沒有,就向服務器打印日志. (拉外網)
思路 2:匹配白名單
以下代碼運用思路1 實現
eg: 這個是過濾的規則的例子格式
.*(.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將正則表達式設置為OC規則
if (![whiteList isEqualToString:@""])
{
NSRegularExpression *regular1 = [[NSRegularExpression alloc] initWithPattern:whiteList options:NSRegularExpressionCaseInsensitive error:nil];
//2.利用規則測試字符串獲取匹配結果
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將正則表達式設置為OC規則
NSRegularExpression *regular2 = [[NSRegularExpression alloc] initWithPattern:blackList options:NSRegularExpressionCaseInsensitive error:nil];
//2.利用規則匹配字符串獲取匹配結果
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 - 發送到服務端打印日志
//do something
}
}
return request;
}
@end
demo 現在正在整理,之后再奉上.如有疑問,歡迎留言.
以上來源參考自網絡,如有侵權請私信筆者.