Using NSURLProtocol with NSURLSession - ios

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
Registering [NSURLProtocol registerClass:[CustomHTTPSProtocol class]]; worked fine with NSURLConnection but how to Resister for NSURLProtocol Globally with NSURLSession ?.
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.

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

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.

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?

Related

NSURLSession not downloading full data, but finnish with success

Scenario: I am downloading some big attachments (30-50 mb) from EWS API, by using NSURLSession. And saving the downloded xml data into files.
I made HTTP class which uses NSURLSession, handles delegate callbacks and has a completion handler. The HTTP class creates its own NSURLSession and start downloading the data. Here is my HTTP.m
//
// HTTP.m
// Download
//
// Created by Ankush Kushwaha on 7/6/18.
// Copyright © 2018 Ankush Kushwaha. All rights reserved.
//
#import "HTTP.h"
typedef void (^httpCompletionBlock)(NSData* result);
#interface HTTP()
#property (nonatomic) NSMutableData * data;
#property (nonatomic) NSString *fileNametoSaved;
#property (nonatomic) httpCompletionBlock completion;
#end
#implementation HTTP
- (instancetype)initWithAttachmntId:(NSString *)attachmentId
fileName:(NSString *)fileName
completion:(void (^)(NSData* result))completion
{
self = [super init];
if (self) {
self.data = [NSMutableData data];
self.completion = completion;
self.fileNametoSaved = fileName;
NSURL *requestUrl = [NSURL URLWithString:#"https://outlook.office365.com/EWS/Exchange.asmx"];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:requestUrl];
request.HTTPMethod = #"POST";
NSString *soapXmlString = [NSString stringWithFormat:#"<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
"xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\"\n"
"xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\"\n"
"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
"<soap:Body>\n"
"<m:GetAttachment>\n"
"<m:AttachmentIds>\n"
"<t:AttachmentId Id=\"%#\"/>\n"
"</m:AttachmentIds>\n"
"</m:GetAttachment>\n"
"</soap:Body>\n"
"</soap:Envelope>\n",attachmentId];
if (soapXmlString)
{
NSString *xmlLength = [NSString stringWithFormat:#"%ld", (unsigned long)soapXmlString.length];
request.HTTPBody = [soapXmlString dataUsingEncoding:NSUTF8StringEncoding];
[request addValue:#"text/xml; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
[request addValue:xmlLength forHTTPHeaderField:#"Content-Length"];
}
dispatch_async(dispatch_get_main_queue(), ^{
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfiguration
delegate:self
delegateQueue:nil];
NSURLSessionDataTask *dataTask = [defaultSession dataTaskWithRequest:request];
[dataTask resume];
});
}
return self;
}
-(void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
if (challenge.previousFailureCount == 0)
{
NSURLCredential* credential;
credential = [NSURLCredential credentialWithUser:#"MY_OUTLOOK.COM EMAIL" password:#"PASSWORD" persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
else
{
// URLSession:task:didCompleteWithError delegate would be called as we are cancelling the request, due to wrong credentials.
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
-(void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
completionHandler(NSURLSessionResponseAllow);
}
-(void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.data appendData:data];
// NSLog(#"data : %lu", (unsigned long)self.data.length);
}
-(void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
NSLog(#"didCompleteWithError: %#", error);
if (error)
{
NSLog(#"Error: %#", error);
}
else
{
NSData *data;
if (self.data)
{
data = [NSData dataWithData:self.data];
}
NSLog(#"Success : %lu", (unsigned long)self.data.length);
NSString *filePath = [NSString stringWithFormat:#"/Users/startcut/Desktop/xxx/%#",
self.fileNametoSaved];
NSString *xmlString = [[NSString alloc] initWithData:self.data encoding:NSUTF8StringEncoding];
[xmlString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
self.completion ? self.completion(self.data) : nil;
}
[session finishTasksAndInvalidate]; // We must release the session, else it holds strong referance for it's delegate (in our case EWSHTTPRequest).
// And it wont allow the delegate object to free -> cause memory leak
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler;
{
NSString *redirectLocation = request.URL.absoluteString;
if (response)
{
completionHandler(nil);
}
else
{
completionHandler(request); // new redirect request
}
}
#end
In My ViewController I am making 5 HTTP requests, to download 5 diffrent attachments.
HTTP *http = [[HTTP alloc] initWithAttachmntId:#"AAAaAGFua3VzaC5zdGFyY3V0QG91dGxvb2suY29tAEYAAAAAACd30qZd6oFAvoaMby5vOMUHAOsTbManU6VPoeQkUTl4/J0AAWUo5o0AAOsTbManU6VPoeQkUTl4/J0AAWruTj0AAAESABAAWGs6REUQc02OHF0x6uYJ+g=="
fileName:#"http1"
completion:^(NSData *result) {
NSLog(#"Completion 1");
}];
HTTP *http2 = [[HTTP alloc] initWithAttachmntId:#"AAAaAGFua3VzaC5zdGFyY3V0QG91dGxvb2suY29tAEYAAAAAACd30qZd6oFAvoaMby5vOMUHAOsTbManU6VPoeQkUTl4/J0AAWUo5o0AAOsTbManU6VPoeQkUTl4/J0AAWruTjsAAAESABAAP8zebUI1fkSiE8tQ+RtwiQ=="
fileName:#"http2"
completion:^(NSData *result) {
NSLog(#"Completion 2");
}];
HTTP *http3 = [[HTTP alloc] initWithAttachmntId:#"AAAaAGFua3VzaC5zdGFyY3V0QG91dGxvb2suY29tAEYAAAAAACd30qZd6oFAvoaMby5vOMUHAOsTbManU6VPoeQkUTl4/J0AAWUo5o0AAOsTbManU6VPoeQkUTl4/J0AAWruTjkAAAESABAAiPaJIPjp/k6iQHSMpi6aDw=="
fileName:#"http3"
completion:^(NSData *result) {
NSLog(#"Completion 3");
}];
HTTP *http4 = [[HTTP alloc] initWithAttachmntId:#"AAAaAGFua3VzaC5zdGFyY3V0QG91dGxvb2suY29tAEYAAAAAACd30qZd6oFAvoaMby5vOMUHAOsTbManU6VPoeQkUTl4/J0AAWUo5o0AAOsTbManU6VPoeQkUTl4/J0AAWruTjwAAAESABAA86vBkFlTNU2oEVq/eRtLGQ=="
fileName:#"http4"
completion:^(NSData *result) {
NSLog(#"Completion 4");
}];
HTTP *http5 = [[HTTP alloc] initWithAttachmntId:#"AAAaAGFua3VzaC5zdGFyY3V0QG91dGxvb2suY29tAEYAAAAAACd30qZd6oFAvoaMby5vOMUHAOsTbManU6VPoeQkUTl4/J0AAWUo5o0AAOsTbManU6VPoeQkUTl4/J0AAWruTjoAAAESABAAND6qbOQbnkyoyg0K17T9/Q=="
fileName:#"http5"
completion:^(NSData *result) {
NSLog(#"Completion 5");
}];
Problem: As the files or data are being downloaded parallelly with 5 separate HTTP objects, At the end when NSUrlSession session delegate gets called I save data into files in my HTTP.m's -(void)URLSession (NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
Method. Most of the times the downloaded data (files) does not contain the full data (e.g If the Size of the attachment is 30 mb, my code downloads the data 4 mb or 10 mb or 3.2 mb etc. The numbers are not consistent). It seems that NSURLSession terminates or stop the data downloading in between and close the connection successfully. If I download 1 attachment at a time (Instead of making 5 HTTP objects in my view controller, I just make 1 object at a time) in most of the cases it works and downloads full data content.
Any help is appreciated guys. I am stuck in this from 2 days.
In no particular order:
You should not be creating a new session for each request. That prevents the OS from limiting the number of simultaneous requests correctly, and will likely cause other issues down the road. Similarly, you should not be calling finishTasksAndInvalidate after each task completes.
You must retain a reference to the session until there are no more outstanding requests. If that doesn't fit easily into your app's architecture, you might consider using the default session instead of providing your own session.
Your Content-Length header value is incorrect. It should be a byte count, not a character count. Convert the string to an NSData with encoding first, and send the length of that as Content-Length. Otherwise, it will fail as soon as you get a single multi-byte character in the body.
Your didReceiveResponse: method should ideally be clearing your data storage so that it handles multipart responses correctly (with the last one winning), rather than concatenating them.
Your authentication challenge handler, as written, is likely to cause serious problems. You should be checking the protection space of the challenge to see if it is one that you care about, and if not, you should be triggering default handling. Without that your app will fail if the user is behind any sort of proxy, among other things.
Fix those issues, and if it still isn't working, ask a new question about whatever is still not working. :-)
Finally. I found the cause. Not the solution :(
It was not from iOS code. There might be some code improvement needed as #dgatwood mentioned (Thanks), but even after improvements then I was facing the same problem.
Actually, the EWS exchange is getting throttled by large data download. Due to which EWS server terminates the connection in between. Here is the blog

Auth challenge for NSURLSession not working with Custom protocol

I have implemented custom protocol with session delegate as below -
- (void)startLoading {
NSMutableURLRequest *mReq = [self.request mutableCopy];
NSURL *url = [[self request] URL];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
...
...
if(!_mySession) {
_mySession = [NSURLSession sessionWithConfiguration:config
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
}
.....
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler{
NSURLAuthenticationChallenge* challengeWrapper = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:[[CustomAuthChallengeWrappers alloc] initWithSessionCompletionHandler:completionHandler]];
if(self.client){
if([self.client respondsToSelector:#selector(URLProtocol:didReceiveAuthenticationChallenge:)]) {
debugLog("auth-challenge");
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challengeWrapper];
}
}
}
If client uses NSURLConnection, it is working fine. If I use NSURLSession on client side, its forwarding the Auth challenge but not receiving back in custom protocol.
Challenge wrapper is implemented as per this link:
NSURLProtocol Challenge wrapper
Am I missing anything for NSURLSession?
Just to update it is working fine with iOS 10 and xcode 8 beta version. Looks like there was some problem in iOS 9.3 which has been fixed in this release.

NSURLSession HTTP basic auth delegate does not work

I used NSURLConnection with challenge delegate, and it worked.
I now migrate my code to session, and challenge not called.
On server side I see 401 response, but no delegate called.
#interface MyDelegate : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate>
#end
#implementation MyDelegate
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
// Break point here NOT called
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
// Break point here NOT called
}
#end
int main(int argc, char * argv[]) {
#autoreleasepool {
//return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSURL *url = [NSURL URLWithString:#"http://127.0.0.1:8000/upload/123"];
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
req.HTTPMethod = #"PUT";
[req setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
NSError *err;
NSURLResponse *resp;
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:[MyDelegate new] delegateQueue:nil];
[[session uploadTaskWithRequest:req fromData:[NSData new]]resume];
sleep(10000);
}
}
Do I miss something?
Just in case: please do not advice me to create Basic HTTP auth header manually, I want to use delegate.
PS: Could it be my delegate released since delegate not retained in session?
I will try to make it static, then.
I think NSURLSession requests have to be running in a run loop. I'm not certain of this, however.
And of course, if you were doing this in a real app, you probably would not want to block the main thread with a sleep call. This might also be part of the problem.

Regestring NSRULProtocol for NSURLSessions Globally

I am trying to use NSURLProtocol to handle authentication challenges for every NSURLSession connection . I used
[NSURLProtocol registerClass:[CustomHTTPSProtocol class]]
for NSURLConnection .But it is not working with NSURLSession so i need to setSessionConfiguration.protocolClasses=#[[CustomHTTPSProtocol class]];.
The problem is i use this URLProtocol class to only handle authentication challenges and i get received data's in the delegates of original class.
Something like this
OriginalClass
NSURLConnection
-(void)sendURL
{
[NSURLProtocol registerClass:[CustomHTTPSProtocol class]]; //done globally in didFinishLaunching
self.URL =[NSURL URLWithString:[[NSString stringWithFormat:#"https://www.google.co.in"]stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:self.URL];
self.connection=[NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
NSlog(#"received data...%#",data);
}
NSURLSession
-(void)sendURL
{
self.URL =[NSURL URLWithString:[[NSString stringWithFormat:#"https://www.google.co.in"]stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
NSMutableURLRequest *request=[[NSMutableURLRequest alloc]initWithURL:self.URL];
sessionConfiguration =[NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.protocolClasses=#[[CustomHTTPSProtocol class]];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:nil];
NSURLSessionDataTask*task= [session dataTaskWithRequest:checkInInfoRequest];
[task resume];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
NSLog(#"data ...%# ",data); //handle data here
}
NSURLProtocol Class
NSURLConnection
-(void)startLoading
{
NSMutableURLRequest *newRequest = [self.request mutableCopy];
[NSURLProtocol setProperty:#YES forKey:CustomHTTPSProtocolHandledKey inRequest:newRequest];
self.connection=[NSURLConnection connectionWithRequest:newRequest delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
else
{
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
NSURLSession
- (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.sessionTask=[session dataTaskWithRequest:newRequest];
[self.sessionTask resume];
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
Using the above logic i was able to handle authentication for all request globally and handling response individually. But with NSURLSession if i use NSURLProtocol, authentication is done in Protocol class but i am not able to receive data, since my original class delegates are not called.
Somebody help me out.
When you implement making the request yourself (or resend it as you did), your protocol becomes the challenge sender. You are responsible for creating an NSURLAuthenticationChallenge object, setting yourself as the sender, providing all the other bits from the existing challenge, and sending it using the NSURLProtocolClient class.
Take a look at Apple's CustomHTTPProtocol Sample Code project for examples and further information.

NSURLSession + server with self signed cert

I have an app that is production along with a development server that has a self signed certificate.
I am attempting to test NSURLSession and background downloading but can't seem to get past - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
When I use NSURLConnection I am able to bypass it using:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
NSLog(#"canAuthenticateAgainstProtectionSpace %#", [protectionSpace authenticationMethod]);
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(#"didReceiveAuthenticationChallenge %# %zd", [[challenge protectionSpace] authenticationMethod], (ssize_t) [challenge previousFailureCount]);
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
But I can't figure out how to get this to work with NSURLSession >:(
This is what I have currently (that doesn't work):
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
NSLog(#"NSURLSession did receive challenge.");
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
I also tried creating a category of NSURLSession that would allow any certificate for a host:
#import "NSURLRequest+IgnoreSSL.h"
#implementation NSURLRequest (IgnoreSSL)
+ (BOOL)allowsAnyHTTPSCertificateForHost:(NSString*)host {
return YES;
}
+ (void)setAllowsAnyHTTPSCertificate:(BOOL)allow forHost:(NSString*)host {}
#end
Which also doesn't seem to help.
EDIT
I've updated this method to return:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
//Creates credentials for logged in user (username/pass)
NSURLCredential *cred = [[AuthController sharedController] userCredentials];
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
}
Which still does nothing.
For me your first example is working fine. I have tested with the following code without problems (it is of course very insecure since it allows any server certificate).
#implementation SessionTest
- (void) startSession
{
NSURL *url = [NSURL URLWithString:#"https://self-signed.server.url"];
NSURLSessionConfiguration *defaultConfigObject = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration: defaultConfigObject delegate: self delegateQueue: [NSOperationQueue mainQueue]];
NSURLSessionDataTask * dataTask = [defaultSession dataTaskWithURL:url
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error == nil)
{
NSString * text = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(#"Data: %#",text);
}
else
{
NSLog(#"Error: %#", error);
}
}];
[dataTask resume];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
#end
Update: This is the class interface, the SessionTest class is the NSURLSessionDataDelegate, to start the data download you create a SessionTest object and call the startSession method.
#interface SessionTest : NSObject <NSURLSessionDelegate>
- (void) startSession;
#end
There's not enough information to suggest a concrete solution to your problem.
Here are some principal requirements:
Since you want a background task, ensure you created a suitable NSSession object through backgroundSessionConfiguration:. Using this class factory method is mandatory for getting background sessions.
For requests running in the background in a separate process, only upload and download tasks are supported. Note that, in your original code, you are using a data task.
Ensure you have properly implemented the delegate method application:handleEventsForBackgroundURLSession:completionHandler: in your App Delegate. When your app is not running, and when the session is running in its own process and requires credentials, iOS will restart your app in the background and the background session will call this delegate method. See also https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:handleEventsForBackgroundURLSession:completionHandler:
Disabling server trust evaluation should work as you tried in your first example. Use this for development only!
See also (https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/URLLoadingSystem/Articles/UsingNSURLSession.html#//apple_ref/doc/uid/TP40013509-SW44)

Resources