本章介绍UIWebView和WKWebView的网络请求拦截和请求更改-- 案例demo

注意UIWebView已经被抛弃替换为WKWebView,可以作为参考,将其翻译成WKWebView的相关逻辑

UIWebView网络请求拦截、篡改

UIWebView的拦截是通过重写NSURLProtocol类,来实现我们自定义的拦截,然后在使用WebView的时候注册URLProtocol即可

可以通过继承重写NSURLProtocol类需要重写下面几个方法:

canInitWithRequest、canonicalRequestForRequest、requestIsCacheEquivalent、startLoading、stopLoading

canInitWithRequest

该方法是判断是否拦截处理对应请求的方法,可以通过返回YES,来阻断改请求,当请求阻断后会走后面的方法

拦截方式如下所示,可以通过url、后缀名之类的屏蔽

//是否能够处理给定的请求,自行出行返回YES,否则返回NO
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    NSLog(@"%@", request.URL.absoluteString);
    //拦截百度的logo
    NSArray *blackList = @[@"jpeg", @"png", @"jpg"];
    if ([request.URL.absoluteString 
        isEqualToString:@"https://www.baidu.com/img/flexible/logo/plus_logo_web_2.png"]) {
        //屏蔽单个url
        return YES;
    }else if ([blackList containsObject:request.URL.pathExtension]) {
        //拦截掉一类数据例如图片
        return YES;
//    else if ([request.URL.absoluteString containsString:@"www.baidu.com"]) {
//        //是否包含某个url,或者其他,可以屏蔽一类网站
//        return YES;
//    }
    return NO;

canonicalRequestForRequest、requestIsCacheEquivalent

canonicalRequestForRequest为拦截请求后必须实现的方法,requestIsCacheEquivalent无需实现也行,一般不在这里做额外的操作

//必须实现,规范化URL请求,一般返回request
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
//用于检测两个请求是缓存等效的,则为YES,否则为NO
//+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b {
//    return [super requestIsCacheEquivalent:a toRequest:b];

startLoading

startLoading是拦截完毕请求后,进行篡改内容的重要一步,可以在里面根据拦截的请求,来替换篡改请求数据

通过NSURLProtocolClient的-URLProtocol:didLoadData方法来发送替换数据

如下所示,将看到的图片替换成自己想要的图片内容

- (void)startLoading {
    //拦截后加载请求,可以在这里调整发起自己的网络请求
    NSArray *blackList = @[@"jpeg", @"png", @"jpg"];
    if ([self.request.URL.absoluteString isEqualToString:
        @"https://www.baidu.com/img/flexible/logo/plus_logo_web_2.png"]) {
        NSData *imgData = [NSData dataWithContentsOfFile:
            [[NSBundle mainBundle] pathForResource:@"test1" ofType:@"jpeg"]];
        [self.client URLProtocol:self didLoadData:imgData];
    }else if ([blackList containsObject:self.request.URL.pathExtension]) {
        //将拦截的图片替换掉
        NSData *imgData = [NSData dataWithContentsOfFile:
            [[NSBundle mainBundle] pathForResource:@"test1" ofType:@"jpeg"]];
        [self.client URLProtocol:self didLoadData:imgData];
//    else if ([self.request.URL.absoluteString containsString:@"www.baidu.com"]) {
//        //是否包含某个url,或者其他,可以屏蔽一类网站
//    }

对重写的NSURLProtocol子类进行注册和移除

注意:仅仅重写是不够的的,还需要在使用前注册该重写后的子类,并且使用完毕该功能后,适当的位置及时移除协议(可以在使用该webView前注册,离开该页面后取消注册),可以避免其他网络被拦截的情况

//注册拦截协议
[NSURLProtocol registerClass:[LSURLProtocol class]];
//取消注册
[NSURLProtocol unregisterClass:[LSURLProtocol class]];

最后测试一下,发现百度的首页图片已经被拦截替换了

WKWebView的网络请求拦截、篡改

WKWebView和UIWebView不一样,其核心模块是在WebKit框架中,网络也是一样,因此其拦截不能像UIWebView一样了,需要通过WebKit框架中的新方法来实现对指定WKWebView内容的拦截和篡改

将WKWebView拦截之前,先贴出WKWebView的正常加载代码,从中可以看到WKWebViewConfiguration,这个是注册拦截用到的类

- (void)initWKWebView {
    //网络走的webkit内核,无法使用NSURLProtocol
    //不加上webView显示大小有问题
    NSString *jScript = @"var meta = document.createElement('meta'); meta.setAttribute('name', 'viewport'); meta.setAttribute('content', 'width=device-width'); document.getElementsByTagName('head')[0].appendChild(meta);";
    WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript 
        injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
    WKUserContentController *wkUController = [[WKUserContentController alloc] init];
    [wkUController addUserScript:wkUScript];
    WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
    wkWebConfig.userContentController = wkUController;
    _wkWebView = [[WKWebView alloc]initWithFrame:self.view.frame configuration:wkWebConfig];
    //手势触摸滑动
    _wkWebView.allowsBackForwardNavigationGestures = YES;
    NSURLRequest *request = [NSURLRequest requestWithURL:
        [NSURL URLWithString:@"https://www.baidu.com/"] 
        cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:15];
    [_wkWebView loadRequest:request];
    [self.view addSubview:_wkWebView];

设置WKURLSchemeHandler代理,以及注意事项

WKWebView实现网络拦截是通过的WKURLSchemeHandler协议,通过WKWebViewConfiguration对象进行注册,由于WKWebViewConfiguration是直接跟WKWebView挂钩的,所以不需要担心其移除问题(当然除非所有的WKWebView使用的都是这一个config)

设置的时候,可以设置拦截的URLScheme,拦截前会调用WKWebView的类方法handlesURLScheme,来检查设置的URLScheme是否符合条件,如果不符合直接崩溃,例如下面所示:http系列的会导致崩溃,正常url不会有问题,详细可以查看handlesURLScheme说明

其设置代理代码如下所示:

//可以点进去查看,通过webView的类方法handlesURLScheme来检查url可用性,无效的URLScheme会导致崩溃(例如:http)
//由于调用类方法继承不可以,可以通过分类+hook来处理(或者主动方法的hook和交换),避免自定义拦截标准出现出现崩溃
//标准的urlscheme是没有问题的,可以点方法进入查看参考标准,例如:www.baidu.com
//这里可以自行创建专门处理拦截业务的代理类,将self替换之,并实现WKURLSchemeHandler协议即可
[wkWebConfig setURLSchemeHandler:self forURLScheme:@"www.baidu.com"];
[wkWebConfig setURLSchemeHandler:self forURLScheme:@"https"];
[wkWebConfig setURLSchemeHandler:self forURLScheme:@"http"];

那么如何避免设置非法的URLScheme崩溃呢,前面说了通过WKWebView的类方法handlesURLScheme,因此可以hook掉该类方法,可以通过以分类或者是直接hook的方式来进行hook(这里为了看着更清晰,在分类里面进行的hook)

hook代码如下所示:

+ (void)load {
    method_exchangeImplementations(
        class_getClassMethod(self, @selector(handlesURLScheme:)), 
        class_getClassMethod(self, @selector(__handlesURLScheme:)));
+ (BOOL)__handlesURLScheme:(NSString *)urlScheme {
    if ([urlScheme isEqualToString:@"http"] || [urlScheme isEqualToString:@"https"]) {
        return NO;
    return [self __handlesURLScheme: urlScheme];

这样就不会崩溃了

实现代理拦截、篡改请求

代理主要实现startURLSchemeTask方法,这个方法相当于是URLProtocol中的canInitWithRequest与startLoading的结合体,可以通过startURLSchemeTask方法拦截并篡改数据

案例如下所示

//通知开始准备加载相应的网络任务
- (void)webView:(WKWebView *)webView startURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask {
    NSURLRequest *request = urlSchemeTask.request;
    //可以通过url拦截响应的方法
    if ([request.URL.absoluteString.pathExtension isEqualToString:@"png"] 
        || [request.URL.absoluteString.pathExtension isEqualToString:@"gif"]) {
        //一个任务完成需要返回didReceiveResponse和didReceiveData两个方法
        //最后在执行didFinish,不可重复调用,可能会导致崩溃
        [urlSchemeTask didReceiveResponse:[NSURLResponse new]];
        [urlSchemeTask didReceiveData:[NSData dataWithContentsOfFile:
            [[NSBundle mainBundle] pathForResource:@"test1" ofType:@"jpeg"]]];
        [urlSchemeTask didFinish];
        return;
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] 
        dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        //也可以通过解析data等数据,通过data等数据来确定是否拦截
        //一个任务完成需要返回didReceiveResponse和didReceiveData两个方法
        //最后在执行didFinish,不可重复调用,可能会导致崩溃
        if (!data) {
            [urlSchemeTask didReceiveResponse:[NSURLResponse new]];
            [urlSchemeTask didReceiveData:[NSData dataWithContentsOfFile:
                [[NSBundle mainBundle] pathForResource:@"test1" ofType:@"jpeg"]]];
        } else {
            [urlSchemeTask didReceiveResponse:response];
            [urlSchemeTask didReceiveData:data];
        [urlSchemeTask didFinish];
    [task resume];

通过上面代码可以看到,当一个请求被拦截时,必须要调用一系列的方法,保证请求能够正常进行,通过urlSchemeTask参数,可以检验URL相关数据,并且拦截后替换返回数据,需要分别调用didReceiveResponse、didReceiveData回调数据,并通过didFinish方法标记成功(必须在回调数据之后标记),且方法不可重复调用,不然可能会导致崩溃

当然通过上面案例也可以看出,也可以先请求网络,然后通过网络返回的结果,来判断是否对数据进行拦截和篡改,可以参考上面的案例

当通知停止加载某个url时会调用下面的stopURLSchemeTask方法,该方法一般不回调,可以用来查看失败原因,并作出相应

//通知停止加载该url,这里一般不做处理,否则可能会引起异常,暂时未发现走这里
- (void)webView:(WKWebView *)webView stopURLSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask {
    NSLog(@"%@", urlSchemeTask.request.URL.absoluteString);

测试一下,与UIWebView一样,发现百度的首页图片已经被拦截替换了

以上便是webView拦截请求的相关内容了,想要更加详细的内容,可以使用案例demo,点入方法查看系统的说明并尝试

分类:
iOS
  •