美文网首页
iOS app秒开H5 NSURLProtocol 拦截方式

iOS app秒开H5 NSURLProtocol 拦截方式

作者: lumic000 | 来源:发表于2019-08-21 21:30 被阅读0次

    拦截并加载本地资源包

    NSURLProtocol

    公司的项目从 UIWebView 迁移到了 WKWebView。WKWebView性能更优,占用内存更少。

    对H5请求进行拦截并加载本地资源,自然想到NSURLProtocol这个神器了。

    NSURLProtocol能拦截所有当前app下的网络请求,并且能自定义地进行处理。使用时要创建一个继承NSURLProtocol的子类,不应该直接实例化一个NSURLProtocol。

    核心方法

    + (BOOL)canInitWithRequest:(NSURLRequest *)request

    判断当前protocol是否要对这个request进行处理(所有的网络请求都会走到这里)。

    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request

    可选方法,对于需要修改请求头的请求在该方法中修改,一般直接返回request即可。

    - (void)startLoading

    重点是这个方法,拦截请求后在此处理加载本地的资源并返回给webview。

    - (void)startLoading
    {
        //标示该request已经处理过了,防止无限循环
        [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:self.request];
    
        NSData *data = [NSData dataWithContentsOfFile:filePath];
        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:self.request.URL
                                                                MIMEType:mimeType
                                                   expectedContentLength:data.length
                                                        textEncodingName:nil];
    
        //硬编码 开始嵌入本地资源到web中
        [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
        [[self client] URLProtocol:self didLoadData:data];
        [[self client] URLProtocolDidFinishLoading:self];
    }
    
    复制代码
    

    - (void)stopLoading

    对于拦截的请求,NSURLProtocol对象在停止加载时调用该方法。

    注册

    [NSURLProtocol registerClass:[NSURLProtocolCustom class]];

    其中NSURLProtocolCustom就是继承NSURLProtocol的子类。

    但是开发时发现NSURLProtocol核心的几个方法并不执行,难道WKWebview不支持NSURLProtocol?

    原来由于网络请求是在非主进程里发起,所以 NSURLProtocol 无法拦截到网络请求。除非使用私有API来实现。使用WKBrowsingContextController和registerSchemeForCustomProtocol。 通过反射的方式拿到了私有的 class/selector。通过把注册把 http 和 https 请求交给 NSURLProtocol 处理。

    Class cls = NSClassFromString(@"WKBrowsingContextController");
    SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
    if ([(id)cls respondsToSelector:sel]) {
        // 把 http 和 https 请求交给 NSURLProtocol 处理
        [(id)cls performSelector:sel withObject:@"http"];
        [(id)cls performSelector:sel withObject:@"https"];
    }
    
    // 这下 NSURLProtocolCustom 就可以用啦
    [NSURLProtocol registerClass:[NSURLProtocolCustom class]];
    复制代码
    

    毕竟使用苹果私有api,这是在玩火呀。这篇文章《让 WKWebView 支持 NSURLProtocol》有很好的说明。比如我使用私有api字串拆分,运行时在组合,绕过审核。还可以对字符串加解密等等。。。

    实际问题

    通过以上处理,可以正常拦截处理,但是又发现拦截不了post请求(拦截到的post请求body体为空),即使在canInitWithRequest:方法中设置对于POST请求的request不处理也不能解决问题。内流。。。

    经了解,算是 WebKit 的一个缺陷吧。首先 WebKit 进程是独立于 app 进程之外的,两个进程之间使用消息队列的方式进行进程间通信。比如 app 想使用 WKWebView 加载一个请求,就要把请求的参数打包成一个 Message,然后通过 IPC 把 Message 交给 WebKit 去加载,反过来 WebKit 的请求想传到 app 进程的话(比如 URLProtocol ),也要打包成 Message 走 IPC。出于性能的原因,打包的时候 HTTPBody 和 HTTPBodyStream 这两个字段被丢弃掉了,这个可以参考 WebKit 的源码,这就导致 -[WKWebView loadRequest:] 传出的 HTTPBody 和 NSURLProtocol 传回的 HTTPBody 全都被丢弃掉了。 所以如果通过 NSURLProtocol 注册拦截 http scheme,那么由 WebKit 发起的所有 http POST 请求就全都无效了,这个从原理上就是无解的。

    当然网上也出现一些解决方案,但结果都不太理想。同时拦截后对ATS支持不好。再结合又使用了苹果私有API有被拒风险,最终决定弃用NSURLProtocol拦截的方案。

    技术支持:
    https://juejin.im/post/5c9c664ff265da611624764d
    https://www.cnblogs.com/zhuchenglin/p/7528250.html
    https://segmentfault.com/p/1210000016879239/read
    https://blog.csdn.net/thelittleboy/article/details/84027881
    https://www.jianshu.com/p/05616e9a1c7f

    相关文章

      网友评论

          本文标题:iOS app秒开H5 NSURLProtocol 拦截方式

          本文链接:https://www.haomeiwen.com/subject/bepssctx.html