Using NSURLProtocol with NSURLSession
Asked Answered
E

3

7

My application uses NSURLConnection to communicate with server. We use https for communication. In order to handle authentication from all request in one place i used NSURLProtocol and handled authentication in delegates in that class. Now I have decided to use NSURLSession instead of NSURLConnection. I am trying do get NSURLProtocol working with NSURLSession I created a task and used NSURLProtocol by

NSMutableURLRequest *sampleRequest = [[NSMutableURLRequest alloc]initWithURL:someURL];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = @[[CustomHTTPSProtocol class]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:checkInInfoRequest];
[task resume];

CustomHTTPSProtocol which is my NSURLProtocol class looks like this

static NSString * const CustomHTTPSProtocolHandledKey = @"CustomHTTPSProtocolHandledKey";

@interface CustomHTTPSProtocol () <NSURLSessionDataDelegate,NSURLSessionTaskDelegate,NSURLSessionDelegate>

@property (nonatomic, strong) NSURLSessionDataTask *connection;
@property (nonatomic, strong) NSMutableData *mutableData;

@end

@implementation CustomHTTPSProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
    if ([NSURLProtocol propertyForKey:CustomHTTPSProtocolHandledKey inRequest:request]) {
        return NO;
    }
    return [[[[request URL]scheme]lowercaseString]isEqualToString:@"https"];
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return request;
}

- (void) startLoading {
    NSMutableURLRequest *newRequest = [self.request mutableCopy];
    [NSURLProtocol setProperty:@YES forKey:CustomHTTPSProtocolHandledKey inRequest:newRequest];

    NSURLSession*session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
    self.connection = [session dataTaskWithRequest:newRequest];
    [self.connection resume];
    self.mutableData = [[NSMutableData alloc] init];
}

- (void) stopLoading {
    [self.connection cancel];
    self.mutableData = nil;
}

-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
    NSLog(@"challenge..%@",challenge.protectionSpace.authenticationMethod);
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        [challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
    }
    else {
        [challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
    }
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    [self.client URLProtocol:self didLoadData:data];
    NSLog(@"data ...%@  ",data); //handle data here
    [self.mutableData appendData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    if (!error) {
        [self.client URLProtocolDidFinishLoading:self];
    }
    else {
        NSLog(@"error ...%@  ",error);
        [self.client URLProtocol:self didFailWithError:error];
    }
}

@end

Start loading is called and also authentication challenge is done but stop loading is called immediately after that.

Error code -999 "Cancelled" is returned after some time. didReceiveData is not called.

Note:NSURLProtocol and the Authentication Process worked fine with NSURLConnection.

What am I missing ?? My Questions are

  1. Registering [NSURLProtocol registerClass:[CustomHTTPSProtocol class]]; worked fine with NSURLConnection but how to Resister for NSURLProtocol Globally with NSURLSession ?.

  2. Why are the requests getting failed in NSURLProtocol(same URL and logic worked withURLConnection) with URLSession and how to get NSURLProtocol working with URLSession ?.

Kindly help me and let me know if you want any more details.

Erdrich answered 26/8, 2015 at 9:48 Comment(1)
As @chandra said, I only get -999 error on iOS 8.xSyllabize
R
0

I know this is old but the problem you are having is in your didReceiveChallenge method. You are ending the method by calling

[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];

or

[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

What you need to be doing instead is using the completion handler to send your results. It would look like this:

completionHandler(.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust)

or

completionHandler(.PerformDefaultHandling, nil)

These are in Swift 2.0 but should translate nicely to Obj-C.

Rothermere answered 17/8, 2016 at 19:0 Comment(0)
K
3

Registering a custom NSURLProtocol with NSUrlsession is same as the way you do with NSURLConnection.

NSURLSession Api's (NSURLSessionDataTask and other task classes) when used with custom NSUrlProtocol fails to handle HTTP Authentication Challenges properly. This was working as expected on iOS 7.x and is broken in iOS 8.x.Raised a apple radar and my radar was closed saying it was a duplicate of another radar and I Still see the original radar is still open. So I believe this issue is not yet fixed by apple as of today.

Hope I am not too late with the answer :)

Kreg answered 8/3, 2016 at 8:52 Comment(2)
Anyone know if this was fixed in iOS 9.x or 10.x?Valance
The current status of my radar is still open, So I believe its not yet fixed in iOS 10.x.Kreg
G
0

My vague recollection is that NSURLSession automatically uses any globally registered protocols. So you shouldn't need to register it again, but it also shouldn't hurt to do so.

I wonder if the URL request is getting copied, in which case your custom tagging might not work, and you might be seeing infinite recursion until it hits some internal limit. Have you tried setting a request header instead?

Gahl answered 28/10, 2015 at 19:4 Comment(0)
R
0

I know this is old but the problem you are having is in your didReceiveChallenge method. You are ending the method by calling

[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];

or

[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];

What you need to be doing instead is using the completion handler to send your results. It would look like this:

completionHandler(.UseCredential, NSURLCredential(forTrust: challenge.protectionSpace.serverTrust)

or

completionHandler(.PerformDefaultHandling, nil)

These are in Swift 2.0 but should translate nicely to Obj-C.

Rothermere answered 17/8, 2016 at 19:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.