iOS UIWebView 广告拦截 js,css 拦截方法 (NSURLProtocol)

刚刚开始写博客,可能描述的思路不是很清晰,还请各位看官担待,写的不好,如有纰漏,望请斧正,感觉不尽.

当我们做混合APP 或者是 纯网页的APP(就是只加一个UIWebView的套壳APP)时,经常会遇到讨厌的广告劫持!!!
特别是那些 营运商 的 DNS劫持,植入广告,真的是太恶心了.

来现在过滤DNS广告好像有2种:
  1. HTTPS协议 --- 这个安全,可靠,推荐!!! 但是如果都能用这个那就没这篇文章什么事了.
    2.设置过滤规则 ---这个也是Adblock (最强的广告拦截插件,没用的,我也不知道说什么了) 的拦截原理.
    现在我们是要模仿它,为我们的UIWebView 加载的网页也加上拦截机制,过滤这些广告.
    这个方法缺点也是很大的,需要维护过滤规则 比较麻烦.
  1. 如果你需要加载的网页都是从外网上拉下来的,那这个目前好像没有什么解决办法.(如果读者还有什么高招,还请留言告知!)只能 保持最新的过滤规则,哈哈哈~~
  2. 如果你加载的网页全部都是在你自己的服务器上,那就好办多了,直接不是你域名和你合作的域名内的请求都不通过就行了. 注意!! 像第三方登录之类的都会被拦截的,因为所有的系统请求都会经过你的过滤规则.(笔者就被坑过,哈哈哈哈)

众所周知,我们原生iOS的UIWebView 要于html网页进行交互 有两个途径

1: 通过UIWebView 的 stringByEvaluatingJavaScriptFromString: 方法实现与HTML网页的交互.

注意: 这个方法必须在网页加载完成之后才会有效,也就是再 delegate 中的 webViewDidFinishLoad: 方法执行过之后

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
     //获取某个id的标签中的内容 
    NSString *content = [webView stringByEvaluatingJavaScriptFromString:
        @"document.getElementById('你的某个标签的id').innerHTML"];
}
2: ios7 以上有了这个 神器!! 框架.

使用这个框架进行交互的网上有这相当多的教程,我这就放几个传送门就好了.

这个有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是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。

startLoadingstopLoading 实现请求和取消流程。

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 

@interface CCURLProtocol : NSURLProtocol

@end

CCURLProtocol.m 文件



#import "CCURLProtocol.h"
#import "AFNetWork.h"

static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";
static NSDictionary *_holdUpDic;
@interface CCURLProtocol ()

@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 现在正在整理,之后再奉上.如有疑问,欢迎留言.

以上来源参考自网络,如有侵权请私信笔者.

你可能感兴趣的:(iOS UIWebView 广告拦截 js,css 拦截方法 (NSURLProtocol))