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()

 

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