Android+オレオレ証明書=通信不能
WebViewを使用したAndroidアプリを製作し、信頼できない証明書(いわゆるオレオレ証明書)を使用したWebサイトにアクセスすると、頻繁にリンクをタップしたときに通信不能になってしまった。放っておくと約10分で復活するけど・・・。
頻繁というのはリンクをタップした後、ロードの完了を待たずに別(同じでもいいかも)のリンクをタップするくらいの感じ。
ちなみに WebViewClient の onReceivedSslError() はオーバーライドして、
public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) { handler.proceed() ; }
な感じで全部もみ消すように実装している。
この現象の詳細を追ってみた。環境はFroyo。
あ、回避策はわかりませんでした。誰か教えてください。(ちゃんとした証明書使えという以外で ^^;)
andoid.net.http, android.webkit 辺りの実装概要
- HTTP通信は http0〜http3 という4本のスレッドで行われる
- スレッドクラスは android.net.http.ConnectionThread
- HTTP通信のリクエストを保持するキューとキューに溜まったリクエストを http0〜3 で処理する実装
- 最後のほうにクラス図(抜粋)載せてます
通信不能時の状態
- 通信不能な状態の時はhttp0〜3全てが、キューへの追加を待つのとは別のところでwaitしている
- キューへの追加待ちの時は android.net.http.ConnectionThread.run() の中でwait
- 通信不能時は android.net.http.HttpsConnection.openConnection() の中でwait
- (id:h13i32maru 氏が発見)
waitしている場所
android.net.http.HttpsConnection.openConnection() の抜粋
01: sslSock = (SSLSocket) getSocketFactory().createSocket(); 02: sslSock.connect(new InetSocketAddress(mHost.getHostName(), mHost.getPort())); 03: 04: SslError error = CertificateChainValidator.getInstance(). 05: doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName()); 06: 07: if (error != null) { // ←オレオレ証明書はエラーとなるのでここはtrue 08: synchronized (mSuspendLock) { 09: mSuspended = true; 10: } 11: boolean canHandle = req.getEventHandler().handleSslErrorRequest(error); 12: synchronized (mSuspendLock) { 13: if (mSuspended) { 14: mSuspendLock.wait(10 * 60 * 1000);
通信不能時のhttpxスレッドはこの14行目のwaitでwaitしている。
waitに至る流れ
mSuspneded を false に変更するのは HttpsConnection.restartConnection() のみ。このwaitから復帰させるための mSuspendLock.notify() を呼ぶのも HttpsConnection.restartConnection() 内のみ。
つまり、waitしぱなっしにならないためには、muSuspened が9行目で true に設定された後、別スレッドでrestartConnection()が呼ばれるか、11行目の req.getEventHandler().handleSslErrorRequest(error) の中で restartConnection() がよばれる必要がある。
restartConnection() を呼ぶのは HttpsConnection.closeConnection() と Request.handleSslErrorResponse(boolean) のみ。
ロードが正常に完了する時のrestartConnection()の呼び出しスタックはこんな感じ。
01: HttpsConnection.restartConnection(boolean) line: 409 02: HttpsConnection(Request).handleSslErrorResponse(boolean) line: 519 03: RequestHandle.handleSslErrorResponse(boolean) line: 119 04: LoadListener.handleSslErrorResponse(boolean) line: 881 05: SslErrorHandler.handleSslErrorResponse(LoadListener, SslError, boolean) line: 270 06: SslErrorHandler.checkSslPrefTable(LoadListener, SslError) line: 165 07: Network.checkSslPrefTable(LoadListener, SslError) line: 336 08: LoadListener.handleSslErrorRequest(SslError) line: 814 09: HttpsConnection.openConnection(Request) line: 325 10: HttpsConnection(Connection).openHttpConnection(Request) line: 407 11: HttpsConnection(Connection).processRequests(Request) line: 260 12: ConnectionThread.run() line: 134
ところが、別スレッドからロードが既にキャンセルされてしまっていた場合は5行目の SslErrorHandler.handleSslErrorResponse() の中が以下のようになっているので、これ以降のメソッドは呼び出されず、restartConnection()が呼ばれることはない。
01: synchronized void handleSslErrorResponse(・・・) { 02: if (!loader.cancelled()) { // ←ロードが既にキャンセルされていればスキップ 03: ・・・ 04: loader.handleSslErrorResponse(proceed); 05: ・・・ 06: } 07: }
では、キャンセルの処理の中でrestartConnection()が呼ばれるかというと・・・呼ばれません! キャンセル処理の中ではcloseConnection()が以下のように呼ばれる。
01: HttpsConnection.closeConnection() line: 385 02: HttpsConnection(Connection).cancel() line: 159 03: Request.cancel() line: 384 04: RequestHandle.cancel() line: 99 05: LoadListener.cancel() line: 1297 06: JWebCoreJavaBridge.sharedTimerFired() line: not available [native method] 07: JWebCoreJavaBridge.fireSharedTimer() line: 88 08: JWebCoreJavaBridge.handleMessage(Message) line: 105 09: JWebCoreJavaBridge(Handler).dispatchMessage(Message) line: 99 10: Looper.loop() line: 123 11: WebViewCore$WebCoreThread.run() line: 626 12: Thread.run() line: 1096
が、closeConnection()の処理は
01: void closeConnection() { 02: ・・・ 03: if (mSuspended) { 04: restartConnection(false); 05: ・・・ 06: }
となっていて、mSuspended が true になる前に呼ばれても restartConnection() は呼ばれず mSuspended も false にはならない。
つまり、上記HttpsConnection.openConnection() の2行目辺りで通信中にユーザーが何かリンクをタップすると、そのスレッドは10分間のwaitに入ってしまい、使用不能となる。これが、http0〜3全てのスレッドで起きるとリンクをタップしても通信しないと言う症状が発生する。