30
NSURLConnectionの デリゲートメソッドと認証 Cocoa勉強会 #47 2011/9/3 Masayuki Nii 1

Cocoa勉強会#47-NSURLConnectionのデリゲートメソッドと認証

Embed Size (px)

DESCRIPTION

Cocoa勉強会#47 2011/9/3 NSURLConnectionのデリゲートメソッドと認証 新居雅行

Citation preview

NSURLConnectionのデリゲートメソッドと認証

Cocoa勉強会 #472011/9/3Masayuki Nii

1

Agenda

NSURLConnectionを利用した通信処理

状況別のメソッド呼び出し順序

**6月の浦和での勉強会と同じネタ**

2

NSURLConnection+デリゲート

通信のためのクラスNSURLConnection• 同期通信は1行でできるが、低速なiOSでは使うことは少ないデリゲートされるメソッドを使って組み込み• メインスレッドで動かせなくもない• 別スレッドを作りそちらで動作させるのがいいかも

3

NSURLConnectionの最低限の使用法

NSURLRequest等からインスタンス化

受信を始めると、以下のメドッドが呼び出される• - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data

受信が完了すると、以下のメソッドが呼び出される• - (void)connectionDidFinishLoading:(NSURLConnection *)connection

いちおうエラー処理くらいしよう• - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

4

しかしながら…

これであらゆる場合に対処できるのか?

あらゆるエラーを取得できるのか?

SSLや認証はこれでいいのか?

全メソッドをインプリメントしていろいろな状況で動かしてみる• プロジェクト:ConnectionTest• テスト:iOS 4.3 (シミュレータ)、Mac OS X Server 10.6.x

5

すべてのデリゲートメソッド(1)

通信前• - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse

通信中• - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data• - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

6

すべてのデリゲートメソッド(2)

通信後• - (void)connectionDidFinishLoading:(NSURLConnection *)connection• - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error• - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite

7

すべてのデリゲートメソッド(3)

認証• - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection• - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace• - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge• - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

8

- (void)downloadData: (NSString *)urlString{ NSURL *url = [NSURL URLWithString: urlString]; NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL: url]; NSLog( @"urlString = %@", urlString ); self.receivedData = [NSMutableData data]; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest: urlRequest delegate: self]; if ( connection == nil ) { NSLog( @"ERROR: NSURLConnection is nil" ); }}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{ NSLog( @"Calling: connection:didReceiveData:" ); [self.receivedData appendData: data];}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{ NSLog( @"Calling: connection:didFailWithError: %@", error ); self.receivedData = nil; }

- (void)connectionDidFinishLoading:(NSURLConnection *)connection{ NSLog( @"Calling: connectionDidFinishLoading:" ); [connection release]; NSLog( @"receivedData = %@", [[[NSString alloc] initWithData: self.receivedData encoding: NSUTF8StringEncoding] autorelease] ); self.receivedData = nil;}

基本3メソッド

9

- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{ NSHTTPURLResponse *httpRes = (NSHTTPURLResponse *)response; NSLog( @"Calling: connection:didReceiveResponse: status code=%d", [httpRes statusCode] );}

- (void) connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{ NSLog( @"Calling: connection:didCancelAuthenticationChallenge: %@", challenge ); }

- (void) connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite{ NSLog( @"Calling: connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:" );}

- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse{ NSLog( @"Calling: connection:willSendRequest:redirectResponse: %@", redirectResponse ); return request;}

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection{ NSLog( @"Calling: connectionShouldUseCredentialStorage:" ); return NO;}

10

ネットワーク関連エラー

11

普通にうまくいった場合

didReceiveResponseが先に呼ばれる

urlString = http://msyk.dyndns.org/test.phpCalling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:didReceiveResponse: status code=200Calling: connection:didReceiveData::Calling: connection:didReceiveData:Calling: connectionDidFinishLoading:receivedData = 012345678901234567890…

12

存在しないファイルにアクセスした場合

didFailWithError:は呼ばれない

didReceiveResponse:でのステータスコードのチェックが必要

urlString = http://msyk.dyndns.org/test1.phpCalling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:didReceiveResponse: status code=404Calling: connection:didReceiveData:Calling: connectionDidFinishLoading:receivedData = <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"><html><head><title>404 Not Found</title>

13

存在しないURLに接続しようとした

didFailWithError:が呼び出される

エラーは「サーバが見つからない」

urlString = http://msyk1234.dyndns.org/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:didFailWithError: Error Domain=NSURLErrorDomain Code=-1003 "A server with the specified hostname could not be found." UserInfo=0x7013250 {NSErrorFailingURLStringKey=http://msyk1234.dyndns.org/, NSErrorFailingURLKey=http://msyk1234.dyndns.org/, NSLocalizedDescription=A server with the specified hostname could not be found., NSUnderlyingError=0x7013190 "A server with the specified hostname could not be found."}

14

DNSの応答がない場合

didFailWithError:が呼び出される

エラーは「タイムアウト」

urlString = http://msyk.dyndns.org/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:didFailWithError: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo=0x8b14b50 {NSErrorFailingURLStringKey=http://msyk.dyndns.org/, NSErrorFailingURLKey=http://msyk.dyndns.org/, NSLocalizedDescription=The request timed out., NSUnderlyingError=0x8b132d0 "The request timed out."}

15

到達しないIPアドレスを指定した場合

didFailWithError:が呼び出されるエラーは「タイムアウト」

urlString = http://192.168.1.98/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:didFailWithError: Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo=0x4b25350 {NSErrorFailingURLStringKey=http://192.168.1.98/, NSErrorFailingURLKey=http://192.168.1.98/, NSLocalizedDescription=The request timed out., NSUnderlyingError=0x4b22330 "The request timed out."}

16

すべてのネットワーク接続がオフ(1)

didFailWithError:が呼び出される

エラーは「インターネットがオフライン」

urlString = http://msyk.dyndns.org/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:didFailWithError: Error Domain=NSURLErrorDomain Code=-1009 "The Internet connection appears to be offline." UserInfo=0x4b34070 {NSErrorFailingURLStringKey=http://msyk.dyndns.org/, NSErrorFailingURLKey=http://msyk.dyndns.org/, NSLocalizedDescription=The Internet connection appears to be offline., NSUnderlyingError=0x4b0dbe0 "The Internet connection appears to be offline."}

17

すべてのネットワーク接続がオフ(2)

URLにIPアドレスを指定した場合

didFailWithError:が呼び出される

エラーは「サーバに接続できない」

urlString = http://10.0.1.1/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:didFailWithError: Error Domain=NSURLErrorDomain Code=-1004 "Could not connect to the server." UserInfo=0x4e2fa70 {NSErrorFailingURLStringKey=http://10.0.1.1/, NSErrorFailingURLKey=http://10.0.1.1/, NSLocalizedDescription=Could not connect to the server., NSUnderlyingError=0x4e0c4c0 "Could not connect to the server."}

18

リダイレクト

基本3メソッドだけの場合、何もしなくてもOK

willSendRequest:redirectResponse:の呼び出し2回

urlString = http://msyk.dyndns.org/test.phpCalling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:willSendRequest:redirectResponse: <NSHTTPURLResponse: 0x6834f60>Calling: connection:didReceiveResponse: status code=200Calling: connection:didReceiveData::Calling: connection:didReceiveData:Calling: connectionDidFinishLoading:receivedData = <html lang="ja"> <head>

19

SSL

20

正しい証明書のサイト(1)

基本3メソッドだけの場合、何もしなくても接続可能

認証関連のメソッドを単に組み込んだだけの場合だと、以下のように通信は正しくできない

urlString = https://msyk.net/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:canAuthenticateAgainstProtectionSpace: <NSURLProtectionSpace: 0x6502000>Calling: connection:didReceiveAuthenticationChallenge: <NSURLAuthenticationChallenge: 0x65020c0>****何も受信できていない

21

- (BOOL) connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace{ NSString *authMethod = [protectionSpace authenticationMethod]; NSLog( @"Calling: connection:canAuthenticateAgainstProtectionSpace: " " auth method=%@/host=%@", authMethod, [protectionSpace host] ); if ( [authMethod isEqualToString: NSURLAuthenticationMethodServerTrust] ) { secTrustRef = [protectionSpace serverTrust]; if (secTrustRef != NULL) { SecTrustResultType result; OSErr er = SecTrustEvaluate( secTrustRef, &result ); if ( er != noErr) { return NO; } if ( result == kSecTrustResultRecoverableTrustFailure ) { NSLog( @"---SecTrustResultRecoverableTrustFailure" ); } NSLog( @"---Return YES" ); return YES; } } if ( [authMethod isEqualToString: NSURLAuthenticationMethodDefault] ) { NSLog( @"---Return YES" ); return YES; } return NO;}

Security.frameworkも参照しておく必要がある22

正しい証明書のサイト(2)

認証関連メソッドに対応することで接続できる

確認できない証明書にも対応urlString = https://msyk.net/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:canAuthenticateAgainstProtectionSpace: auth method=NSURLAuthenticationMethodServerTrust/host=msyk.net---Return YESCalling: connection:didReceiveAuthenticationChallenge: <NSURLAuthenticationChallenge: 0x4d08180>Calling: connection:didReceiveResponse: status code=200Calling: connection:didReceiveData:Calling: connectionDidFinishLoading:receivedData = <html lang="ja">

- (void) connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{ NSLog( @"Calling: connection:didReceiveAuthenticationChallenge: %@", challenge ); NSURLCredential *credential = [NSURLCredential credentialForTrust: secTrustRef]; [[challenge sender] useCredential: credential forAuthenticationChallenge:challenge];}

23

確認できない証明書(エラーにする場合)

SecTrustEvaluate関数の戻り値• kSecTrustResultRecoverableTrustFailureの場合にNOを返す基本3メソッドではこれと同じ状態

urlString = https://coolnotify.com/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:canAuthenticateAgainstProtectionSpace: auth method=NSURLAuthenticationMethodServerTrust/host=coolnotify.comCalling: connection:didFailWithError: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “coolnotify.com” which could put your confidential information at risk." UserInfo=0x4b232e0 {NSErrorFailingURLStringKey=https://coolnotify.com/, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSErrorFailingURLKey=https://coolnotify.com/, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “coolnotify.com” which could put your confidential information at risk., NSUnderlyingError=0x4b22c10 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “coolnotify.com” which could put your confidential information at risk.", NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x4b1f320>}

24

Authentication

25

- (void) connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{ NSLog( @"Calling: connection:didReceiveAuthenticationChallenge: %@", challenge ); if ( [challenge previousFailureCount] == 0 ) { NSURLCredential *credential = [NSURLCredential credentialWithUser: @"te" password: @"te" persistence: NSURLCredentialPersistenceNone]; [[challenge sender] useCredential: credential forAuthenticationChallenge:challenge]; } else if ( [challenge previousFailureCount] == 1 ) { NSURLCredential *credential = [NSURLCredential credentialWithUser: @"msyk" password: @"12345678" persistence: NSURLCredentialPersistenceNone]; [[challenge sender] useCredential: credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] cancelAuthenticationChallenge: challenge]; }

}

26

認証に失敗する場合

urlString = https://msyk.net/iphone/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:canAuthenticateAgainstProtectionSpace: auth method=NSURLAuthenticationMethodServerTrust/host=msyk.net---Return YESCalling: connection:didReceiveAuthenticationChallenge: <NSURLAuthenticationChallenge: 0x4b25fd0>Calling: connection:canAuthenticateAgainstProtectionSpace: auth method=NSURLAuthenticationMethodDefault/host=msyk.net---Return YESCalling: connection:didReceiveAuthenticationChallenge: <NSURLAuthenticationChallenge: 0x4b24130>Calling: connection:canAuthenticateAgainstProtectionSpace: auth method=NSURLAuthenticationMethodDefault/host=msyk.net---Return YESCalling: connection:didReceiveAuthenticationChallenge: <NSURLAuthenticationChallenge: 0x9b00b20>Calling: connection:didFailWithError: Error Domain=NSURLErrorDomain Code=-1012 "The operation couldn’t be completed. (NSURLErrorDomain error -1012.)" UserInfo=0x890d7e0 {NSErrorFailingURLKey=https://msyk.net/iphone/, NSErrorFailingURLStringKey=https://msyk.net/iphone/}

27

認証に1度失敗し、2度目に成功する場合

urlString = https://msyk.net/iphone/Calling: connection:willSendRequest:redirectResponse: (null)Calling: connectionShouldUseCredentialStorage:Calling: connection:canAuthenticateAgainstProtectionSpace: auth method=NSURLAuthenticationMethodServerTrust/host=msyk.net---Return YESCalling: connection:didReceiveAuthenticationChallenge: <NSURLAuthenticationChallenge: 0x9a030e0>Calling: connection:canAuthenticateAgainstProtectionSpace: auth method=NSURLAuthenticationMethodDefault/host=msyk.net---Return YESCalling: connection:didReceiveAuthenticationChallenge: <NSURLAuthenticationChallenge: 0x700ae90>Calling: connection:canAuthenticateAgainstProtectionSpace: auth method=NSURLAuthenticationMethodDefault/host=msyk.net---Return YESCalling: connection:didReceiveAuthenticationChallenge: <NSURLAuthenticationChallenge: 0x4e0a6f0>Calling: connection:didReceiveResponse: status code=200Calling: connection:didReceiveData:Calling: connectionDidFinishLoading:receivedData = <?xml version="1.0" encoding="UTF-8"?>…

28

その他

なぜかdidCancelAuthenticationChallenge:はコールされなかった

connectionShouldUseCredentialStorageの返り値による違いないとしか思えない

29

まとめ

NSURLConnectionはネットワークに関係なく生成

didReceiveResponse:メソッドでステータスコード

didFailWithError:が呼び出されれば通信エラー

認証への対応はメソッドへの応答として記述する

認証とSSLの両方があるときには要注意

30