swift WKWebView 实现 URLProtocol 网络拦截 修改 Referer添加

在我们的日常开发过程中有时需要我们对网络做拦截操作,诸如对request 的header 做修改 添加等。

对于有效的url(http 或者 https) 只需要对 request 做添加头文件即可

//这里是对资源加入的防盗链
 var request = URLRequest(url:url as URL);
 request.setValue(kReferer, forHTTPHeaderField: "Referer")
 theWebView?.load(request)

 

但是有时加载只是一些本地的静态html文件,这时我们就需要对 网络做拦截

import UIKit

class WKURLProtocol: URLProtocol , URLSessionDataDelegate, URLSessionTaskDelegate{

    //URLSession数据请求任务
    var dataTask:URLSessionDataTask?
    //url请求响应
    var urlResponse: URLResponse?
    //url请求获取到的数据
    var receivedData: NSMutableData?


    //判断这个 protocol 是否可以处理传入的 request
    override class func canInit(with request: URLRequest) -> Bool {
        //对于已处理过的请求则跳过,避免无限循环标签问题
        if URLProtocol.property(forKey: "WKURLProtocolHandledKey", in: request) != nil {
            return false
        }
        return true
    }

    //回规范化的请求(通常只要返回原来的请求就可以)
    override class func canonicalRequest(for request: URLRequest) -> URLRequest {
        guard let m_url = request.mainDocumentURL else{
            return request
        }

        if m_url.absoluteString.contains("XXX"){
            var mrequest  = request
            mrequest.setValue("aaaa", forHTTPHeaderField: "ssdf")
            return mrequest
        }

        return request
    }

    //判断两个请求是否为同一个请求,如果为同一个请求那么就会使用缓存数据。
    //通常都是调用父类的该方法。我们也不许要特殊处理。
    override class func requestIsCacheEquivalent(_ aRequest: URLRequest,
                                                 to bRequest: URLRequest) -> Bool {
        return super.requestIsCacheEquivalent(aRequest, to:bRequest)
    }

    //开始处理这个请求
    override func startLoading() {

        let newRequest = (self.request as NSURLRequest).mutableCopy() as! NSMutableURLRequest
        //NSURLProtocol接口的setProperty()方法可以给URL请求添加自定义属性。
        //(这样把处理过的请求做个标记,下一次就不再处理了,避免无限循环请求)

        URLProtocol.setProperty(true, forKey: "WKURLProtocolHandledKey",
                                in: newRequest)

        //使用URLSession从网络获取数据
        let defaultConfigObj = URLSessionConfiguration.default
        let defaultSession = Foundation.URLSession(configuration: defaultConfigObj,
                                                   delegate: self, delegateQueue: nil)
        self.dataTask = defaultSession.dataTask(with: self.request)
        self.dataTask!.resume()

    }

    //结束处理这个请求
    override func stopLoading() {
        self.dataTask?.cancel()
        self.dataTask       = nil
        self.receivedData   = nil
        self.urlResponse    = nil
    }

    //URLSessionDataDelegate相关的代理方法
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
                    didReceive response: URLResponse,
                    completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {

        self.client?.urlProtocol(self, didReceive: response,
                                 cacheStoragePolicy: .notAllowed)
        self.urlResponse = response
        self.receivedData = NSMutableData()
        completionHandler(.allow)
    }

    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask,
                    didReceive data: Data) {
        self.client?.urlProtocol(self, didLoad: data)
        self.receivedData?.append(data)
    }

    //URLSessionTaskDelegate相关的代理方法
    func urlSession(_ session: URLSession, task: URLSessionTask
        , didCompleteWithError error: Error?) {
        if error != nil {
            self.client?.urlProtocol(self, didFailWithError: error!)
        } else {
            //保存获取到的请求响应数据

            self.client?.urlProtocolDidFinishLoading(self)
        }
    }


}
//使用
 //注册URL Loading System协议,让每一个请求都会经过WKURLProtocol处理
        URLProtocol.registerClass(WKURLProtocol.self)

但是我们如果使用的是 WKWebView 仅仅执行以上的操作还不行 

因为在 UIWebView 时代,按照下面的方式注册一个自定义的 NSURLProtocol 和 CustomURLCache 的子类,

// 注册url拦截
[NSURLProtocol registerClass:[CustomURLProtocol class]];
// 注册缓存加载
[NSURLCache setSharedURLCache:[CustomURLCache new]];

然后就可以在其实现中对 app 内所有的网络请求进行拦截,并加载本地的离线资源(很多 Hybird 框架都是基于此原理)。但在 WKWebView 中的请求却完全不遵从这一规则,网络上文章一般都解释说 WKWebView 的请求是在单独的进程里,所以不走 NSURLProtocol。

 

这里我们对 wkwebview 做拓展处理

//swift 

extension WKWebView{


    func supportURLProtocol(){

        let selector =  Selector(("registerSchemeForCustomProtocol:"))

        let vc = WKWebView().value(forKey: "browsingContextController") as AnyObject

        let cls  = type(of: vc) as AnyObject

        _ = cls.perform(selector, with: "http")
        _ = cls.perform(selector, with: "https")


    }


}

//oc

#import "WKWebView+NSURLProtocol.h"

@implementation WKWebView (NSURLProtocol)

- (void)supportURLProtocol {

    Class cls = NSClassFromString(@"WKBrowsingContextController");
    SEL selector = NSSelectorFromString(@"registerSchemeForCustomProtocol:");
    if ([cls respondsToSelector:selector]) {
        // 通过http和https的请求,同理可通过其他的Scheme 但是要满足ULR Loading System
        // 以下方法类似:performSelector:withObject:
        IMP (*func)(id, SEL, id) = (void *)[cls methodForSelector:selector];
        func(cls, selector, @"http");       // 注册http
        func(cls, selector, @"https");      // 注册https
    }
}

@end


//使用
webview.supportURLProtocol()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章