NSURLSession Life Cycle and Basic Resolution

I cannot read the response from the server sequentially if I use the code below.

Title:

#import <Foundation/Foundation.h> @interface TestHttpClient : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate> -(void)POST:(NSString*) relativePath payLoad:(NSData*)payLoad; @end 

Implementation:

 #import "TestHttpClient.h" @implementation TestHttpClient -(void)POST:(NSString*)relativePath payLoad:(NSData*)payLoad { NSURL* url = [NSURL URLWithString:@"http://apps01.ditat.net/mobile/batch"]; // Set URL credentials and save to storage NSURLCredential *credential = [NSURLCredential credentialWithUser:@"BadUser" password:@"BadPassword" persistence: NSURLCredentialPersistencePermanent]; NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:[url host] port:443 protocol:[url scheme] realm:@"Ditat mobile services endpoint" authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace]; // Configure session NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration ephemeralSessionConfiguration]; sessionConfig.timeoutIntervalForRequest = 30.0; sessionConfig.timeoutIntervalForResource = 60.0; sessionConfig.HTTPMaximumConnectionsPerHost = 1; sessionConfig.URLCredentialStorage = [NSURLCredentialStorage sharedCredentialStorage]; // Should this line be here?? NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]]; // Create request object with parameters NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60.0]; // Set header data [request setHTTPMethod:@"POST"]; [request setValue:@"application/x-protobuf" forHTTPHeaderField:@"Content-Type"]; [request setValue:@"Version 1.0" forHTTPHeaderField:@"User-Agent"]; [request setValue:@"Demo" forHTTPHeaderField:@"AccountId"]; [request setValue:@"1234-5678" forHTTPHeaderField:@"DeviceSerialNumber"]; [request setValue:@"iOS 7.1" forHTTPHeaderField:@"OSVersion"]; [request setHTTPBody:payLoad]; // Call session to post data to server?? NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:request]; [downloadTask resume]; } -(void)invokeDelegateWithResponse:(NSHTTPURLResponse *)response fileLocation:(NSURL*)location { NSLog(@"HttpClient.invokeDelegateWithResponse - code %ld", (long)[response statusCode]); } #pragma mark - NSURLSessionDownloadDelegate - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSLog(@"NSURLSessionDownloadDelegate.didFinishDownloadingToURL"); [self invokeDelegateWithResponse:(NSHTTPURLResponse*)[downloadTask response] fileLocation:location]; [session invalidateAndCancel]; } // Implemented as blank to avoid compiler warning -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { } // Implemented as blank to avoid compiler warning -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { } 

It can be called from any VC (for example, a place code under the action of a button)

 -(IBAction)buttonTouchUp:(UIButton *)sender { TestHttpClient *client = [[TestHttpClient alloc] init]; [client POST:@"" payLoad:nil]; return; } 

If you run the program and call this code, it will be shown at the end of NSLog with 401. The second attempt will not work. Or it may work if you wait a bit. But it will not send server requests when a button is clicked.

Does NSURLSession somehow "remember" failed attempts and return nothing? Why is this behavior? I want to see 2 NSLog messages every time I click a button.

+6
source share
1 answer

TL DR; . In your example, you are processing authentication incorrectly.

This is what happens when an iOS or MacOS client encounters an authentication URL:

  • The client requests a resource from the GET www.example.com/protected server GET www.example.com/protected

  • The server response for this request has a status code of 401 and includes a WWW-Authenticate header. This tells the client that it is a secure resource, and indicates which authentication method to use to access the resource. On iOS and MacOS, this is the "authentication" that the delegate responds to. The WWW-Authenticate header is specifically mentioned in the documentation to highlight this .

    • Typically on iOS and MacOS, if a delegate is not provided or does not handle authentication problems, the URL loading system will try to find the appropriate credentials for this resource and type of authentication by looking at NSURLCredentialStorage. It searches for the appropriate credentials that have been saved as the default.

    • If a delegate providing authentication is provided, then before that delegate provide credentials for this resource.

  • When the system has credentials for authentication, the request again attempts to execute the credentials.

    GET www.example.com/protected Authorization: Basic blablahaala

This explains the behavior you see in Charles, and is the correct behavior according to various HTTP specifications.

Obviously, if you do not want to deploy a delegate for your connection, you have the option to enter credentials for the resource that you are accessing in NSURLCredentialStorage. The system will use this and will not require you to run a delegate for credentials.

Create an NSURLCredential:

 credential = [NSURLCredential credentialWithUser:@"some user" password:@"clever password" persistence: NSURLCredentialPersistencePermanent]; 

NSURLCredentialPersistencePermanent will tell NSURLCredentialStorage to keep this forever in the keychain. There are other possible values, such as NSURLCredentialPersistenceForSession . They are described in the documentation. . You should avoid using NSURLCredentialPersistencePermanent with credentials that have not been verified, use the session or not until the credentials are verified. You have probably seen projects using KeychainWrapper or direct access to the Keychain API to store names and passwords on the Internet - this is not the most preferable way to do this, NSURLCredentialStorage is.

Create an NSURLProtectionSpace with the correct host, port, protocol, scope, and authentication method:

 protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:[url host] port:443 protocol:[url scheme] realm:@"Protected Area" authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; 

Note that [[url port] integerValue] will not provide default values ​​for HTTP (80) or HTTPS (443). You must provide them. The area MUST match what the server provides.

Finally, put it in NSURLCredentialStorage :

 [[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace]; 

This will allow the URL loading system to use these credentials from now on. In fact, the same process can also be used to reference the SSL / TLS server trust.

In your question, you are handling server trust, but not NSURLAuthenticationMethodHTTPBasic . When your application receives an authentication request for HTTP Basic Auth, you are not responding, but things are going down. In your case, you probably won’t need to implement URLSession:didReceiveChallenge:completionHandler: if you follow the steps above to set the default basic authentication credentials for this security space. The system will handle NSURLAuthenticationMethodServerTrust , performing a default trust assessment. The system will then find the default credentials that you set for this security location for basic authentication, and use this.

UPDATE

Based on the new information in the comments and the launch of the modified version of the code, the OP actually receives this error in response to its request: NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813) This error can be found in SecureTransport.h. The root certificate for the server credentials does not exist or is not supported by the system. This is very rare, but it can happen. Technote 2232 explains how to configure client trust verification of a client to allow this certificate .

+26
source

Source: https://habr.com/ru/post/972596/


All Articles