ただしロードバランサーを使っていてセッションの維持にCookieを利用している場合。
要約すると
AndroidのWebViewが接続したTCPコネクションは、Activityのインスタンスとは独立してるから注意という話。
WebView持ってるActivityは念のため以下の実装をしておくと安心かもしれない。
※現在の開発環境がXamarin.AndroidなのでC#で書くが、JavaやKotlinでもほぼ同じだと思う。
protected override void OnDestroy()
{
base.OnDestroy();
//WebViewの内部状態を破棄
_webView.Destroy();
_webView.Dispose();
}
WebViewはChomeの実装を使っている
WebViewは内部的にChromeを利用しており、そのインスタンス管理はアプリの制御外である。
.NETで言えばマネージドなコードからWin32 APIを呼び出しているイメージに近い。
そのためWebView経由で作成されたTCPコネクションを閉じるタイミングもChromeの管理範囲となる。
この仕様を理解しておかないと、困ったことになる場合がある。
困ること
サーバー側にロードバランサーを置いてラウンドロビンしているような場合、厄介なことになる場合がある。
今回自分が陥ったのは以下のようなシチュエーションだった。
- 複数台のWebサーバーをロードバランサーでラウンドロビン
- ロードバランサーのセッションの維持はCookie方式
- Cookieはクライアントとの間に新しいTCPコネクションが確立したタイミングで付与される
- Webアプリのセッション(=認証状態)は別のCookieで管理
- 上記Webアプリのセッションをリセットするに、WebView(を持つActivity)を起動するたびにCookieをクリアしている
上記の太字部分が見事にWebViewの仕様とかち合ってしまった。
つまりこのような流れだ。
1回目のWebView起動時の流れ
- TCPコネクションが確立
- ロードバランサーがCookie(負荷分散セッションID)を付与
- アプリケーションセッションが確立
- WebサーバーがCookie(アプリセッションID)を付与
2回目以降のWebView起動時の流れ
- Cookieをクリア
- TCPコネクションは初回の接続が生き残っているため再利用
- ロードバランサーは同一Socketからのアクセス時はCookie(負荷分散セッションID)を付与しない=ロードバランサーのセッションが維持できない
- アプリケーションセッションが確立
- WebサーバーがCookie(アプリセッションID)を付与
2回目以降のリクエストでロードバランサーのセッションが維持できていないので、アプリケーションセッションを確立できていないWebサーバーにリクエストが飛ぶ可能性があり、アプリセッションが一致しないためエラーが発生してしまう。
最初エラーになった時に本当に原因がわからず、Http周りのログを徹底的に洗ってようやく突き止めることができた。
まさかWebViewの仕様に原因があったとは……
対応方法
先述のとおり。
Activityが破棄されるタイミングでWebViewのDestroy
メソッドを呼び出すようにすることで、Chromeのプロセスが内部でTCPコネクションを閉じてくれるようになる。
CookieとTCPコネクションのライフサイクルが一致するようにするのがポイント。
実際に接続が破棄されているのかアプリ側は知る由もないが、この実装にしてからは上述の不具合は発生しなくなったので、おそらく問題ないと思われる。