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