HTTP/2.0 を使って MC のパフォーマンスを底上げしよう！

:::note warn
2023年の夏に書いた記事をそのまま塩漬けにしてました。
ゆえに、既に古い情報ですが、勿体ないので公開します。（今後、アップデートがあれば更新します。）
:::

## はじめに

2023年半ばの現在において、ホットなHTTPといえば HTTP3 とか QUIC でしょう。なので、いまさら `HTTP/2.0` に真新しさはないですし、ほとんどのユーザーは `MC` を社内ネットワーク上で運用しているから 「サーバ証明書も使ってないよ。（ドメインの運用も面倒）」という感じかもしれません。

とはいうものの現代のWebにおいては HTTPS が当たり前になっているし、これを機に社内も `HTTP/2.0` に切り替えようかな。ということで手順をまとめてみました。

https://www.kagoya.jp/howto/it-glossary/network/http3/

:::note
本記事は、Tomcat 9.0.75 の利用を前提に記述します。
:::

:::note warn
本ブログの内容を実際の運用環境へ適用する場合には、自己証明書ではなく正式な認証局により認証されたサーバ証明書を利用することを強くお勧めします。[^1]
:::

[^1]: `DAS`と`MC`の通信（リポジトリの登録）において自己証明書を利用した場合、以下の様に `DAログ` にエラーが出力され、`MC` との接続を拒否されてしまうため、DASを使ったロボットの実行ができません。この現象は利用した自己証明書の `セキュリティ証明書` をDAS端末へ登録することで解消しますが、本番環境として利用する環境においては認証局により発行されたサーバ証明書を利用する方がいいでしょう。
    ```
    2023-08-02... [21] ERROR MCConn - SSL host certificate rejected: CA unknown or self-signed certificate.
    2023-08-02... [17] ERROR MCClient - Pinging management console: 1 つ以上のエラーが発生しました。
    2023-08-02... [17] ERROR MCClient - ... この要求の送信中にエラーが発生しました。
    2023-08-02... [17] ERROR MCClient - ... 接続が切断されました: SSL/TLS のセキュリティで保護されているチャネルに対する信頼関係を確立できませんでした。
    2023-08-02... [17] ERROR MCClient - ... 検証プロシージャによると、リモート証明書は無効です。
    2023-08-02... [21] ERROR MCConn - SSL host certificate rejected: CA unknown or self-signed certificate.
    2023-08-02... [21] ERROR MCConn - SSL host certificate rejected: CA unknown or self-signed certificate.
    :
    ```

## `HTTP/2.0` とは

`HTTP/2.0` については以下の記事がわかり易いと思うので解説はそちらにお願いします。

https://www.kagoya.jp/howto/it-glossary/security/http-2/


## MC が `HTTP/2.0` で通信しているかどうかを確認する

`HTTP/2.0` では HTTPS を使った通信が前提となりますが、一方で HTTPS で通信しているからと言って `HTTP/2.0` を使っているとは限りません。

どのバージョンの HTTP を使っているか確認するために `開発者ツール` を開きます。Chrome、Edge、FireFox では　`Ctrl`+`Shift`+`I` を押下することで表示されます。

以下の図のように `Network` のタブを選択してページを再読み込みすると、`Protocol` のカラムに通信毎の利用プロトコルが表示されます。
- `HTTP/2.0` での通信は「h2」と表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/6e8cc3c4-2a82-3cee-1f2c-5f210ee755a2.png)
- 既存の通信は「http/1.1」と表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/46d7c98e-d61e-014a-90aa-f4876db0e08f.png)

:::note
`開発者ツール` 内に `Protocol` カラムが表示されていない場合はテーブルの見出しを右クリックし、表示されるコンテキストメニューから選択しましょう。 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/926eb77a-4de6-5c3c-6c21-150fb2f94c22.png)
:::

## Tomcat9 で `HTTP/2.0` を有効化する

Tomcat9 で `HTTP/2.0` を利用するには以下の3ステップを実施します。

1. HTTPS 通信用のサーバ証明書を用意する
1. server.xml で `HTTP/2.0` の有効化設定をする
1. Tomcat9 を再起動する

それでは順に見ていきましょう。

### HTTPS 通信用のサーバ証明書を用意する

`HTTP/2.0` の通信は HTTPS が前提となるので、サーバ証明書を用意する必要があります。

外部に公開するサーバの場合には `rpa-technologies.com` のようにドメインを登録し、かつ認証されたサーバ証明書を購入しますが、社内利用の非公開サービス用にサーバ証明書を購入するケースは少ないかもしれません。

今回は後続の手順も考慮し、`keytool` を使って `自己証明書` を発行します。

:::note
keytool は JDK に同梱された「鍵と証明書を管理するためのユーティリティー」です。Tomcat9 のセットアップ時にインストールしているJDK（Java11）に同梱されているはずなので、以下の操作は Tomcat9 をセットアップした環境で実施ください。
:::

PowerShell で以下のコマンドを実行してサーバ証明書を生成します。**パラメータに設定した値は適宜実施される環境の内容に読み替えてください。**[^2]

[^2]: 言うまでもないですが、`パスワード`や`キーストア名`はともかく、`ホスト名`や`IP`は実態に合った値を設定する必要があります。下記そのままコピペしても実態との整合性がずれてしまうのでお気を付けください。

後続の手順で `server.xml` から生成したサーバ証明書のパスを設定するので、Tomcat9 のインストールフォルダ配下 `conf` ディレクトリに移動してから実行することをお勧めします。

```powershell:キーストア生成コマンド
keytool -genkey `
        -dname "cn=rpa-pc22-xxxxx, ou=ope, o=RPAT, l=Minato, st=Tokyo, c=JP" `
        -alias mc113 -keystore mc113.jks -storepass mypassword -keypass mypassword `
        -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -validity 3650 `
        -ext SAN=dns:localhost,dns:rpa-pc22-xxxxx,ip:192.168.3.4
```

パラメータに関する詳しい解説は下記のリンクに譲りますが、ポイントとしては以下の通りです。

| パラメータ | ポイント |
|:-:|:-|
| -dname  | 証明書の所有者情報を指定します。 自己証明書なので適当でいいと思いますが、`cn` については`ホスト名` や `IP` を設定しておくのがいいでしょう。`cn`は証明書のサブジェクトの識別名なので、`cn` を設定することで証明書の所有者を特定することができます。|
| -keystore  | 出力されるファイル名です。  |
| -storepass  | キーストアのパスワードです。(任意の文字列)  |
| -keypass | キーストアに含まれる秘密鍵のパスワードです。 （任意の文字列） |
| -validity  |  証明書の有効期間を指定します。（未設定時は90日間） |
| -ext | 拡張機能フィールドです。<br/>SAN（Subject Alternative Name）拡張機能フィールドに、DNS名とIPアドレスが含まれます。**自己証明書ゆえの警告解消のために重要。** <br/>`MC`を設定してあるサーバの `ホスト名` と `IP` は設定しましょう。|


https://docs.oracle.com/en/java/javase/11/tools/keytool.html

### server.xml で `HTTP/2.0` の有効化設定をする

Tomcat9 インストールフォルダ配下の `server.xml` を編集して保存します。
```
📁 apache-tomcat-9.0.x
 ├─ 📁 conf        # Tomcatの設定ファイルが格納されています。
 │  ├─ 📄 server.xml
 │  └─ 📄 mc113.jsk
```
#### `HTTP/2.0` 設定

文字列 `org.apache.coyote.http11.Http11NioProtocol` を検索し、コメントアウトされている `Connector` の設定を以下の通り書き換えます。
```xml:HTTP/2.0 の設定
    <Connector port="443"  protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true"
               maxParameterCount="1000"
               >
        <SSLHostConfig>
             <Certificate certificateKeystoreFile="conf/mc113.jks"
                          certificateKeystorePassword="mypassword"
                          certificateKeyAlias="mc113"
                          type="RSA" />
        </SSLHostConfig>
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
    </Connector>
```
`server.xml` の中身を見てみると、HTTP/2 の設定例として `org.apache.coyote.http11.Http11AprProtocol` プロトコルを使った設定を例示していますが、以下の理由から `org.apache.coyote.http11.Http11NioProtocol`プロトコルへ設定を行っています。

> Tomcat9の `server.xml` 内の `org.apache.coyote.http11.Http11NioProtocol` と `org.apache.coyote.http11.Http11AprProtocol` の違いは、使用するI/O処理系です。
> 
> - **org.apache.coyote.http11.Http11NioProtocol** は、NIO（New I/O）を使用します。NIOは、非同期I/O処理系であり、複数のスレッドを使用してI/O処理を並行して実行することができます。これにより、パフォーマンスが向上します。
> - **org.apache.coyote.http11.Http11AprProtocol** は、APR（Apache Portable Runtime）を使用します。APRは、ApacheのC言語拡張ライブラリであり、高速なI/O処理を実現することができます。
>
> Tomcat9をHTTP/2化する場合に適切なI/O処理系は、`org.apache.coyote.http11.Http11NioProtocol`です。その理由は、HTTP/2プロトコルは、複数のHTTPリクエストを同時に送受信することにより、パフォーマンスを向上させるためです。NIOは、非同期I/O処理系であり、複数のスレッドを使用してI/O処理を並行して実行することができます。これにより、HTTP/2プロトコルのパフォーマンスを向上させることができます。
> 
> 一方、`org.apache.coyote.http11.Http11AprProtocol` は、ApacheのC言語拡張ライブラリを使用しています。これは、Java言語に比べてパフォーマンスが向上する可能性がありますが、C言語の知識が必要になります。

上記は `Bard` と `Bing` の回答をまとめたものです、その他 Google で検索した実例の内容としても圧倒的に `NIO` プロトコルを使用した例が多かったので、こちらを採用しました。

#### `HTTP/1.1` の取り扱い

新規構築の `MC` であれば考慮する必要もないかもしれませんが、既存環境（特に既にロボットが動いている場合）に対して設定変更を行う場合は現行の稼働に影響を与えてはいけないので、既存の `Connector` 設定は現行状態のまま手を付けず残しておきます。

こちらの設定をコメントアウトすると `HTTP/2.0` でしか接続できなくなります。

```xml:HTTP/1.1 の設定
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               maxParameterCount="1000"
               />
```



#### `server.xml` の俯瞰

編集前後の状態は以下のような感じです。

```diff_xml:server.xml
<?xml version="1.0" encoding="UTF-8"?>
:
<!-- Note:  A "Server" is not itself a "Container", so you may not
     define subcomponents such as "Valves" at this level.
     Documentation at /docs/config/server.html
 -->
<Server port="8005" shutdown="SHUTDOWN">
  :
  <!-- A "Service" is a collection of one or more "Connectors" that share
       a single "Container" Note:  A "Service" is not itself a "Container",
       so you may not define subcomponents such as "Valves" at this level.
       Documentation at /docs/config/service.html
   -->
  <Service name="Catalina">

    <!--The connectors can use a shared executor, you can define one or more
        named thread pools-->
    :
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               maxParameterCount="1000"
               />
    :
    <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443
         This connector uses the NIO implementation. The default
         SSLImplementation will depend on the presence of the APR/native
         library and the useOpenSSL attribute of the AprLifecycleListener.
         Either JSSE or OpenSSL style configuration may be used regardless of
         the SSLImplementation selected. JSSE style configuration is used below.
    -->
-   < !--
-    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
+    <Connector port="443"  protocol="org.apache.coyote.http11.Http11NioProtocol"
               maxThreads="150" SSLEnabled="true"
               maxParameterCount="1000"
               >
        <SSLHostConfig>
-            <Certificate certificateKeystoreFile="conf/localhost-rsa.jks"
+            <Certificate certificateKeystoreFile="conf/mc113.jks"
+                         certificateKeystorePassword="mypassword"
+                         certificateKeyAlias="mc113"
                          type="RSA" />
        </SSLHostConfig>
+       <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
    </Connector>
-    -->
    <!-- Define an SSL/TLS HTTP/1.1 Connector on port 8443 with HTTP/2
         This connector uses the APR/native implementation which always uses
         OpenSSL for TLS.
         Either JSSE or OpenSSL style configuration may be used. OpenSSL style
         configuration is used below.
    -->
    <!--
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol"
               maxThreads="150" SSLEnabled="true"
               maxParameterCount="1000"
               >
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
        <SSLHostConfig>
            <Certificate certificateKeyFile="conf/localhost-rsa-key.pem"
                         certificateFile="conf/localhost-rsa-cert.pem"
                         certificateChainFile="conf/localhost-rsa-chain.pem"
                         type="RSA" />
        </SSLHostConfig>
    </Connector>
    -->
    :
  </Service>
</Server>
```

https://tomcat.apache.org/tomcat-9.0-doc/config/http2.html


### Tomcat9 を再起動する

設定を保存したら Tomcat9 を再起動し、`MC` にアクセスしてみましょう。

以下の通り `HTTP/2.0` で通信ができていることでしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/6e8cc3c4-2a82-3cee-1f2c-5f210ee755a2.png)

## 自己証明書利用による警告を解消する。

色んなサイトを参考にさせていただきましたが、その中で一番簡潔にポイントを共有できると思ったページリンクを貼らせていただきます。

https://qiita.com/nnishimura/items/c748f24b65bb77f61c7b

結論から言うと、端末ごとにブラウザーに対して `セキュリティ証明書` を登録していく必要があります。

手順は3つ。

1. 前段で生成した `キーストア` をインプットとした `セキュリティ証明書` の生成
1. ブラウザへの `セキュリティ証明書` 登録（証明書ストア：`信頼されたルート証明機関`）
1. ブラウザの再起動

### `セキュリティ証明書` の生成

先ほどと同じく、`conf` ディレクトリ上で PowerShell から以下のコマンドを実行します。

```powershell
keytool -exportcert -alias mc113 -file mc113.crt `
        -keystore mc113.jks -storepass mypassword -keypass mypassword
```

`-storepass`、`-keypass` ともに `キーストア` 生成時に指定したものを設定します。

### ブラウザへの `セキュリティ証明書` 登録

生成された `mc113.crt` ファイルをダブルクリックし、表示されたダイアログに従って証明書の登録を行います。
<details><summary>セキュリティ証明書登録手順</summary>

1. `証明書のインストール` をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/c2a3eba1-3fbc-a26b-e1a4-543ad2aef70f.png)
1. `次へ` をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/3c600288-6969-628a-f576-7a2b97e90b44.png)
1. `参照` をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/c51b18fd-85f4-33ab-238c-7cb207f554db.png)
1. `信頼されたルート証明機関` を選択し `OK` をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/2f6261bc-6d50-6c6c-d849-c91182d4d4f1.png)
1. `次へ` をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/de0cdd14-acf8-f643-51f7-dd9c9b96e0b3.png)
1. `完了` をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/8ab8126b-f3ff-672e-0f4f-16b835d5ab99.png)
1. `はい` をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/03531d06-32bc-188c-10b7-4759dedf7529.png)
1. `OK` をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/e04b47aa-4961-7862-d310-0e6e5e50607c.png)
</details>

### ブラウザの再起動
一度ブラウザをすべて閉じ、再度起動します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3090491/2bd0d1aa-580b-e69d-d0da-0fe2cb75c3c2.png)
`MC` のURLを指定すると、自己証明書による警告（赤いマーク）は消え、🔓 鍵マークに変わります。


## その他

### デジタル証明書について

今回自己証明書を認証させるための試行錯誤の中で、わかり易く仕組みが解説されていたのでリンクしておきます。

https://youtu.be/p7oVTL0BrYs

### `HTTP/2.0` でレスポンスが向上したか

ちゃんとした検証をしてから結論を出すことなのでしょうが、`DS` からの認証については疑似的に負荷をかけてレスポンスを計測してみたものの、HTTP/1.1 と `HTTP/2.0` の間で有意差と言えるような違いは見られませんでした。:expressionless:

ただ `DS` の認証ってAPI的なやり取りで、Webページの表示みたいに一度のアクセスで画像やCSS・スクリプトなど多くのリソースを取得するタイプのものではないので、当たり前なのかもしれませんが。。

一方でアクセス数も多く負荷状況の高い `MC` や、画面から利用する `Kapplets` であれば目に見える改善も見られるかもしれませんね。

## まとめ

今回の手順は `MC` に限った手順ではなく、Tomcat9 上で動作する `Kapplets` や `RFS` でも同様に実施可能です。

また、既存の HTTP/1.1 の設定はそのまま残してあるので、既存のロボット（例えばRESTでロボットを呼び出すような仕組み）には影響を与えず切り替えることも可能です。