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.
Related
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
I have something like service API class which implements NSURLConnectionDelegate methods. I noticed that some of the methods of this are deprecated and Apple now suggests to use NSURLSession. I created this service API's own delegate which the calling class would implement and that would be executed when a response is received. I am looking for how I can do something similar while using NSURLSession.
My present stuff with NSURLConnection:
#class ServiceAPI;
#protocol ServiceAPIDelegate <NSObject>
- (void)getResponseData:(NSData *)responseData;
#end
#interface ServiceAPI : NSObject<NSURLCoDelegate>
- (void)httpServiceRequest:(NSMutableURLRequest *)serviceRequest;
#property (nonatomic, weak) id <ServiceAPIDelegate> delegate;
#end
In ServiceAPI implementation file:
- (void)httpServiceRequest:(NSMutableURLRequest *)serviceRequest {
//[[NSURLSession sharedSession] dataTaskWithRequest:serviceRequest] resume];
self.requestConnection = [NSURLConnection connectionWithRequest:serviceRequest delegate:self];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.delegate getResponseData:self.responseData sender:self];
}
Class making request and getting response:
#interface CallingController : UITableViewController<ServiceAPIDelegate>
#end
Implementation file CallingController:
- (void)getResponseData:(NSData *)responseData {
// Do something with response.
}
How do I let the calling class handle the response method just like NSURLConnection while using NSURLSession. While reading NSURLSession looks like the request and response are handled together using completion handler something like this:
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://itunes.apple.com/search?term=apple&media=software"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", json);
}];
I still want to have a service API class and my controller would just pass down the request to service API to make calls and once the response is back, it would be passed down to controller. How do I pass the response to the controller while using NSURLSession.
Something like this:
#class ServiceAPI;
#protocol ServiceAPIDelegate <NSObject>
- (void)getResponseData:(NSData *)responseData;
#end
#interface ServiceAPI : NSObject<NSURLSessionDelegate>
- (void)httpServiceRequest:(NSMutableURLRequest *)serviceRequest;
#property (nonatomic, weak) id <ServiceAPIDelegate> delegate;
#end
In Implementation
- (void)httpServiceRequest:(NSMutableURLRequest *)serviceRequest {
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *downloadTask =
[session dataTaskWithRequest:serviceRequest completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[self.delegate getResponseData:data sender:self];
}];
[downloadTask resume];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// no use
}
You can still achieve what you wish by initialising NSURLSessionDataTask without completion block. That way, it will call the delegate methods instead.
For example:
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"https://itunes.apple.com/search?term=apple&media=software"]];
[dataTask resume];
Implement NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler;
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data;
Also implement NSURLSessionTaskDelegate to know when the request is completed.
/* Sent as the last message related to a specific task. Error may be
* nil, which implies that no error occurred and this task is complete.
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error;
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.
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?
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)