dimanche 26 juin 2016

NSURLProtocol delegation


I wrote a custom NSURLProtocol as follows:

//
//  CustomConnectionProtocol.swift
//  XIO
//
//  Created by Brandon T on 2016-06-25.
//  Copyright © 2016 XIO. All rights reserved.
//

import Foundation

@objc
protocol CustomConnectionProtocolDelegate : class {
    optional func connection(connection: NSURLConnection, willSendRequest request: NSURLRequest, redirectResponse response: NSURLResponse?) -> NSURLRequest?

    optional func connection(connection: NSURLConnection, request: NSURLRequest, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void)
}

class CustomConnectionProtocol : NSURLProtocol, NSURLConnectionDataDelegate {
    var connection: NSURLConnection?
    weak var requestDelegate: CustomConnectionProtocolDelegate?

    override class func canInitWithRequest(request: NSURLRequest) -> Bool {
        return (NSURLProtocol.propertyForKey("didHandleRequest", inRequest: request) as? Bool == nil) ? true : false
    }

    override func startLoading() {
        print("Starting ", self.request.URL!)

        self.requestDelegate = (NSURLProtocol.propertyForKey("requestDelegate", inRequest: self.request) as? WeakWrapper<CustomConnectionProtocolDelegate>)?.value

        let request = self.request.mutableCopy()
        NSURLProtocol.removePropertyForKey("requestDelegate", inRequest: request)
        NSURLProtocol.setProperty(true, forKey: "didHandleRequest", inRequest: request)
        self.connection = NSURLConnection(request: request, delegate: self)
    }

    override func stopLoading() {
        self.connection?.cancel()
        self.connection = nil
        self.requestDelegate = nil
    }

    override class func canonicalRequestForRequest(request: NSURLRequest) -> NSURLRequest {
        return request
    }

    override class func requestIsCacheEquivalent(aRequest: NSURLRequest, toRequest bRequest: NSURLRequest) -> Bool {
        return super.requestIsCacheEquivalent(aRequest, toRequest:bRequest)
    }

    func connection(connection: NSURLConnection, willSendRequest request: NSURLRequest, redirectResponse response: NSURLResponse?) -> NSURLRequest? {

        if self.requestDelegate?.connection(_:willSendRequest:redirectResponse:) != nil {
            if let request = self.requestDelegate!.connection!(connection, willSendRequest: request, redirectResponse: response) {
                if response != nil {
                    let redirect = request.mutable()
                    NSURLProtocol.removePropertyForKey("didHandleRequest", inRequest: redirect)

                    self.client?.URLProtocol(self, wasRedirectedToRequest: redirect, redirectResponse: response!)
                    return redirect;
                }
                return request
            }
            return nil
        }

        if response != nil {
            let redirect = request.mutable()
            NSURLProtocol.removePropertyForKey("didHandleRequest", inRequest: redirect)

            self.client?.URLProtocol(self, wasRedirectedToRequest: redirect, redirectResponse: response!)
            return redirect;
        }

        return request
    }

    func connection(connection: NSURLConnection, willCacheResponse cachedResponse: NSCachedURLResponse) -> NSCachedURLResponse? {
        return cachedResponse
    }

    func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
        self.client?.URLProtocol(self, didReceiveResponse: response, cacheStoragePolicy: .Allowed)
    }

    func connection(connection: NSURLConnection, didReceiveData data: NSData) {
        self.client?.URLProtocol(self, didLoadData: data)
    }

    func connectionDidFinishLoading(connection: NSURLConnection) {
        self.client?.URLProtocolDidFinishLoading(self)
    }

    func connection(connection: NSURLConnection, didFailWithError error: NSError) {
        self.client?.URLProtocol(self, didFailWithError: error)
    }

    func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {

        if self.requestDelegate?.connection(_:request:didReceiveChallenge:completionHandler:) != nil {
            self.requestDelegate?.connection!(connection, request: connection.currentRequest, didReceiveChallenge: challenge, completionHandler: { (disposition, credentials) in
                switch disposition {
                case .UseCredential:
                    if let credentials = credentials {
                        challenge.sender?.useCredential(credentials, forAuthenticationChallenge: challenge)
                    }
                    else {
                        //Possibly insecure..
                        challenge.sender?.continueWithoutCredentialForAuthenticationChallenge(challenge)
                    }

                case .PerformDefaultHandling:
                    challenge.sender?.performDefaultHandlingForAuthenticationChallenge!(challenge)

                case .CancelAuthenticationChallenge:
                    challenge.sender?.cancelAuthenticationChallenge(challenge)

                case .RejectProtectionSpace:
                    challenge.sender?.rejectProtectionSpaceAndContinueWithChallenge!(challenge)
                }
            })
            return
        }

        challenge.sender?.performDefaultHandlingForAuthenticationChallenge!(challenge)
    }
}

I've added a "Delegate" to the request that is being sent in a UIWebView:

NSURLProtocol.setProperty(WeakWrapper<CustomSessionProtocolDelegate>(self), forKey: "requestDelegate", inRequest: request)

then I call webView.loadRequest(request)

For the very first call, the delegate is NOT nil and it works perfectly fine. However, for subsequent calls, it is always nil (some of the requests have Referrer: OriginalRequestURLHere header. How can I get ALL the sub requests to use the same delegate as the original request?

I'm trying to let my UIWebView subclass instance handle the information.

Reasoning: I want to control resources and authentication of a UIWebView but per instance instead of globally. I have tried creating an NSURLSessionDataTask and in the completion block I would call: UIWebView.loadHTMLFromString(html, baseURL: request.URL).

The problem with that, is that it doesn't load the images.. Loading https://google.ca for example.


Aucun commentaire:

Enregistrer un commentaire