如果我使用下面的代码,我无法始终从服务器读取响应。
部首:
#import <Foundation/Foundation.h>
@interface TestHttpClient : NSObject<NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate>
-(void)POST:(NSString*) relativePath payLoad:(NSData*)payLoad;
@end
实现:
#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
{
}
可以从任何VC调用(例如按钮操作下的位置代码)
-(IBAction)buttonTouchUp:(UIButton *)sender
{
TestHttpClient *client = [[TestHttpClient alloc] init];
[client POST:@"" payLoad:nil];
return;
}
如果您启动程序并调用此代码 - 它将在NSLog完成时显示401.第二次尝试 - 将无效。或者等一下可能会有效。但是当你按下按钮时它不会发送服务器请求。
NSURLSession以某种方式“记住”尝试失败并且不会返回任何内容?为什么会这样?我希望每次按下按钮时都能看到2条NSLog消息。
答案 0 :(得分:26)
TL; DR; 您未在示例中正确处理身份验证。
当iOS或MacOS客户端遇到需要身份验证的URL时会发生这种情况:
客户端从服务器请求资源
GET www.example.com/protected
该请求的服务器响应的状态代码为401,并包含WWW-Authenticate标头。这告诉客户端这是受保护的资源,并指定用于访问资源的身份验证方法。在iOS和MacOS中,这是&#34;身份验证挑战&#34;代表回应。 The WWW-Authenticate header is specifically mentioned in the documentation to highlight this
通常在iOS和MacOS上,如果未提供代理或未处理身份验证质询,则URL加载系统将尝试通过查看NSURLCredentialStorage来查找此资源和身份验证类型的相应凭据。它会查找已保存为默认值的匹配凭据。
如果提供了实施身份验证的代理人,则由该代理人提供该资源的凭据。
当系统具有身份验证质询的凭据时,将使用凭据再次尝试请求。
获取www.example.com/protected 授权:基本blablahaala
这解释了您在Charles中看到的行为,并且根据各种HTTP规范是正确的行为。
显然,如果您不想为您的连接实现委托,您可以选择为您在NSURLCredentialStorage中访问的资源提供凭据。系统将使用此功能,并且不需要您为凭证实现委托。
创建NSURLCredential:
credential = [NSURLCredential credentialWithUser:@"some user" password:@"clever password" persistence: NSURLCredentialPersistencePermanent];
NSURLCredentialPersistencePermanent
会告诉NSURLCredentialStorage
永久存储在钥匙串中。您可以使用其他可能的值,例如NSURLCredentialPersistenceForSession
。 These are covered in the documentation.。在验证凭据之前,应避免将NSURLCredentialPersistencePermanent
与未经验证的凭据一起使用,使用会话或无。您可能已经看过使用&#39; KeychainWrapper&#39;或直接访问Keychain API以保存互联网用户名和密码 - 这不是首选的方法,NSURLCredentialStorage
是。
使用正确的主机,端口,协议,领域和身份验证方法创建NSURLProtectionSpace
:
protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:[url host] port:443 protocol:[url scheme] realm:@"Protected Area" authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
请注意,[[url port] integerValue]
不会为HTTP(80)或HTTPS(443)提供默认值。你必须提供这些。领域必须与服务器提供的内容相匹配。
最后,将其放入NSURLCredentialStorage
:
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];
这将允许URL加载系统从此时开始使用此凭据。基本上相同的过程也可以用于SSL / TLS服务器信任引用。
在您的问题中,您正在处理服务器信任,但不是NSURLAuthenticationMethodHTTPBasic
。 当您的应用程序收到HTTP基本身份验证的身份验证质询时,您没有对此做出响应,而且事情从那里开始走下坡路。在您的情况下,您可能不需要实施URLSession:didReceiveChallenge:completionHandler:
完全按照上述步骤设置此保护空间的默认基本身份验证凭据。系统将通过执行默认信任评估来处理NSURLAuthenticationMethodServerTrust
。然后,系统将找到您为此保护空间设置的默认凭据,以进行基本身份验证并使用该凭据。
<强>更新强>
基于评论中的新信息并运行代码的修改版本,OP实际上是在响应他的请求时收到此错误:
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
可以在SecureTransport.h中找到此错误。服务器凭据上的根证书不存在或不受系统信任。这种情况非常罕见,但可能会发生。 Technote 2232解释了如何在客户端中将服务器信任评估自定义为allow this certificate。