自社のAndroidアプリがHTTP/2を利用しているか確認してみた

本稿は[KLab Engineer Advent Calendar 2023](https://qiita.com/advent-calendar/2023/klab) の25日目の記事です。

すみません、大遅刻になってしまいました。24日目までのKLabのエンジニアの皆さん無遅刻で書いていただいて本当に感謝です。

# 自社のHTTP/2対応状況、把握してますか？

モバイルアプリケーションのユーザー体験向上を考えるとき、通信の効率化は重要な要素と言えるでしょう。中でもプロトコルの選択は大きな関心事です。本稿のテーマであるHTTP/2、あるいはHTTP/3について、自身のプロジェクトでの導入を検討・議論したことのある人は珍しくないはずです。

一方で、自分たちのアプリが実際にHTTP/2を使っているかどうか確認している人は少数派ではないでしょうか。昨今ではHTTPS通信が大前提なので、通信の中身を覗き見るのは意外と面倒だったりします。自社のアプリではまだHTTP/2が導入できていないと誤解している場合、そもそも確認をしようと思わないかもしれません。

実は、KLab社内でもHTTP/2の対応状況について十分に把握している人は少ない状況でした。社内の通信ライブラリはHTTP/2対応しているにもかかわらず、ライブラリの実装者以外はHTTP/1.1で通信していると思い込んでおり、的外れな議論をしていることさえありました。

また、クラウドサービスやCDNサービスによっては、HTTP/2を利用するために明示的に設定を有効化する必要があるものがあります。しかし、CDNはデフォルトでHTTP/2対応しているはずという思い込みから確認をしておらず、HTTP/1.1で通信を続けてしまっているプロジェクトも存在しました。

今回、筆者は自社のモバイルアプリケーションとサーバ間の通信状況を覗き見る環境を手元に作り、HTTP/2の対応状況を確認してみました。本稿ではその知見を紹介します。似たような疑問や問題意識を持った人の参考になれば幸いです。

# HTTP/2の主な特徴

HTTP/2は、ネットワークの効率性と速度を改善するための特徴を持っています。いくつかの特徴について紹介していきます。

## 通信の多重化（マルチプレクシング）

HTTP/2の最大の特徴の一つは通信の多重化です。これは、同じTCP接続上で複数のリクエストとレスポンスを同時にやり取りすることが可能になる技術です。

KLabで作っているようなゲームアプリケーションでは、数十から数百の小さいアセットファイルを同時にダウンロードしたいことがあります。このように同一ホストに大量にアクセスしたい場合にHTTP/2はピッタリのプロトコルと言えるでしょう。

HTTP/1.1時代のブラウザは1つのホストに対して4から6程度のコネクションを作って複数のリクエスト・レスポンスを並行処理していました。これに対し、HTTP/2では1コネクションでより多くのリクエストを処理できるというわけです。この技術により、特にネットワーク品質が悪い環境でのユーザー体験が向上すると考えられます。

## コネクションの維持

HTTP/2は1つのホストに対するコネクションを一定時間維持する前提のプロトコルです。RFCでもコネクションの維持が推奨されています。

これはユーザー体験の観点でも重要で、同一ホストに複数回リクエストするような場合にコネクションの確立やTLSネゴシエーションのコストを払うのが1回限りで済むというわけです。

もっとも、コネクションの維持はHTTP/1.1のkeep-aliveから実現されており、HTTP/2だけの強みではありません。プロトコルがどちらであろうと、コネクションを維持して使い回せているかどうかが通信効率に大きく影響すると言えるでしょう。

## ヘッダ圧縮

HTTP/1.1では、ヘッダー情報がテキストで送信されていました。これに対し、HTTP/2ではHPACKという圧縮形式を用いてヘッダー情報を圧縮し、通信量を削減しています。

これで削減できる通信量は微々たるものかもしれませんが、やった方が良い改善なのは間違いありません。

# AndroidアプリでのHTTP/2の実装方法

KLabではUnityでモバイルゲームを作っており、ソースコードとしてはC#の割合が多くなっています。しかし通信についてはC#では全てのニーズを満たせないと判断しており、iOS・Androidそれぞれネイティブ実装を利用しています。

Androidについて言うと、[OkHttp](https://square.github.io/okhttp/)という有名なOSSを採用しています。このライブラリはHTTP/2をサポートしており、Android 7.0以降であればオプション指定も追加ライブラリもなしでHTTP/2接続ができます。

# AndroidアプリのHTTP/2通信の確認方法

前置きが長くなりましたが、ようやく本題です。

先日、社内のUnity製ゲームのAndroid版について、本当にHTTP/2通信しているのかどうか調べたい、ということがありました。

この場合、ソースコードにデバッグコードを入れて再ビルドしてもよかったのですが、私はプロキシツール[Charles](https://www.charlesproxy.com/)を使って確認してみました。

確認してみた結果がこちらです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/46856/c9b4c5d7-33ac-b049-33a9-6606c19de98f.png)

これはCDNへのアクセスだけをフィルタしたものですが、プロトコルがHTTP/2.0になっていることが確認できます。また、クライアントのIPアドレス・Port番号が変わっていないことから、コネクション1つを使い回していることがわかります。

また、本来なら暗号化されていて途中経路では見えないはずのURLのPath情報も表示されています（上記画像では黒塗りにしていますが）。これはプロクシでTLSの復号をしているからわかる情報です。

そもそもHTTP/2を使っているかどうかの確認にもTLSの復号は必須です。HTTP/2へのアップグレードは暗号化された経路で行われるので、TLSを復号しないとプロトコルの判断もできないのです。

以下、この環境を作る手順を紹介します。

## Android実機の操作手順

下記の手順で通信をCharles経由にした上でCharlesのCA証明書をインストールできます。

1. Android実機とMacを同じネットワークに接続します。
2. Mac上でCharlesを開き、プロキシ設定を行います。具体的には、メニューバーから「Proxy」「Proxy Settings」を選択します。表示されるダイアログで、ポート番号（デフォルトは8888）を確認します。さらに「Support HTTP/2」「Enable transparent HTTP proxying」両方を有効にします。
3. Android実機のWi-Fi設定から、プロキシを設定します。Wi-Fiのネットワークの詳細設定で、「プロキシ」を「手動」に設定し、サーバー名にMacのIPアドレス、ポートにCharlesで設定したポート番号を入力します。
4. HTTPS通信を復号するため、CharlesのCA証明書をAndroid実機にインストールします。3の設定をした状態でAndroid実機から http://chls.pro/ssl にアクセスすると証明書がダウンロードされます。
5. 「セキュリティ」「暗号化と認証情報」「証明書のインストール」「CA証明書」からダウンロードした証明書をイントールします。

ただし、最近のAndroidにはセキュリティ対策が施されており、この設定に加えてapkを改変しないとTLSの復号はできません。Android 7以降を使っている場合は次のセクションを読み進めてください。

## apkの書き換え・再署名

今回のようにAndroidアプリから独自のCA証明書を利用する場合、apkの設定ファイルを一部変更したものを自分で作る必要があります。

具体的には、まずapktoolをインストールして対象のapkを解凍します。

```
$ brew install apktool
$ apktool d testapp.apk
$ cd testapp
```

次に、アプリの`res/xml`ディレクトリに`network_security_config.xml`ファイルを作成し、以下のように設定します。

```xml
<network-security-config>
    <base-config>
        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </base-config>
</network-security-config>
```

この設定ファイルを`AndroidManifest.xml`から参照するため、applicationタグにandroid:networkSecurityConfig属性を追加します。

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
    <application android:networkSecurityConfig="@xml/network_security_config" ... >
        ...
    </application>
</manifest>
```

上記ファイルを取り込んで新たなapkを作ります。

```
$ apktool b . -o ../testapp.apk
```

注意点ですが、この設定を製品版バイナリに適用してはいけません。万一そんなことがあると、せっかくOSが提供してくれているセキュリティ対策が無効になってしまいます。

XMLの詳細はAndroid デベロッパー ガイドの[ネットワーク セキュリティ構成](https://developer.android.com/training/articles/security-config?hl=ja)を参照してください。

## 他のプロクシツールの利用

プロクシツールとして本稿ではCharlesを利用しましたが、HTTP/2をサポートしているプロクシツールなら何を使っても大丈夫です

Windowsユーザーの場合は[Fiddler Classic](https://www.telerik.com/fiddler/fiddler-classic)が無料で利用できるはずです。

また、社内ではLinux+[mitmproxy](https://mitmproxy.org/)で同等の環境を作っている事例もありました。

Macの場合はCharlesがおすすめです。私は個人でライセンスを購入していますが、今回のような確認目的なら試用版でも十分でしょう。試用版の制限は３０分間でCharlesが終了してしまうことだけです。

## 補足：セキュリティ対策との関係

今回紹介したapkを書き換える手法はストアに並んでいるアプリに対しては利用できないはずです。各社ともセキュリティ対策で改ざん防止の仕組みを採用しており、再署名したapkの動作を禁止しているためです。本稿の内容はセキュリティ対策を無効化した社内用デバッグビルドで実験しています。

また、アプリによっては証明書ピンニングを採用しているかもしれません。これは中間者攻撃対策の手法で、OSの証明書ストアを信用せず、特定のCA証明書だけを信用するような技術です。当然ですが、証明書ピンニングが有効だとCharlesのようなプロクシツールは利用できません。今回紹介したテクニックを社内確認で使いたい場合、デバッグビルドでは証明書ピンニングを無効化する必要があります。

# まとめ

AndroidアプリのTLS通信をプロクシで復号し、実機でHTTP/2通信をしているかどうか確認する方法を紹介しました。この仕組みを使えばクライアントとサーバのHTTP/2対応状況を確認できるので、トラブルシュートに役立つはずです。

本稿ではカバーできませんでしたが、デバッグバイナリのみ独自CA証明書を有効にすることもできるはずです。これは今後の宿題とさせてください。

ではみなさん、良いお年を！