Jetty 9.3でHTTP/2対応Webサーバを作ってみよう

[Jetty](http://www.eclipse.org/jetty/)が[9.3からHTTP/2に対応](https://webtide.com/introduction-to-http2-in-jetty/)したので、公開から少々時間が経ってしまいましたが、実際に試してみました。...が、「とりあえず動いたけど」から先で色々手こずってしまったので、その辺りをここに覚書として残したいと思います。

Jetty自体のインストールや設定はこれまでと特に変わらないので、ここでは割愛します。また、基本的にはTLS接続を使うことになることと思われますが、これに関しては、[[Java] Jetty 9 で HTTPS サーバ (SSL/TLS)](https://hirooka.pro/?p=5266)等が参考になります。

# とりあえずHTTP/2が動くようにしてみる

まずとにかくHTTP/2が使えるようにするには、まず`http2`と`http2c`の設定を追加し、JettyのALPNライブラリをインストールしてからJettyを起動します。(`$JETTY_HOME`と`$JETTY_BASE`を分けている場合は、`$JETTY_BASE`ディレクトリで作業を行います。)

```bash
$ java -jar $JETTY_HOME/start.jar --add-to-startd=http2,http2c
$ java -jar $JETTY_HOME/start.jar --create-files
$ java -jar $JETTY_HOME/start.jar
```

`start.d`ディレクトリを使わず、`etc`ディレクトリ内のXML設定ファイルの内容を反映させたい場合は、`${JETTY_HOME}/start.ini`の末尾辺りに次のような内容を追加することで対応できます。

```text:${JETTY_HOME}/start.ini
--module=alpn,http2,http2c
etc/jetty-alpn.xml
etc/jetty-http2.xml
etc/jetty-http2c.xml
```

Jettyが起動したら、Chromeで接続してみると、HTTP/2でWebページを読み込んで表示するのが確認できます。なお、Jetty側が起動しているサーバ側で確認するには、リクエストログを有効にしておくと便利です。具体的には、`${JETTY_HOME}/start.ini`に、

```text:${JETTY_HOME}/start.ini
etc/jetty-requestlog.xml
```

を追加してからJettyを起動します。

## `--create-files`でエラーになる場合は (2015/9/1追記)

上記の手順のうち`--create-files`の段階で、次のようなエラーが出ることがあります。

```text
org.eclipse.jetty.start.graph.GraphException: Missing referenced dependency: alpn-impl/alpn-1.8.0_60
```

このエラーは、JettyのALPNモジュールと実行に用いられているJavaのバージョンに不整合がある場合に発生します。上記の例は、Java 8 SEのバージョンが`1.8.0_60`で、かつ、ALPNモジュールがこのバージョンのJava向けに用意されていない場合を表しています。

この現象を解決するには、`${JETTY_HOME}/modules/alpn-impl`にJavaのバージョンに対応した適切なモジュールを追加します。具体的には、[Jettyのサイトから該当するモジュール](http://git.eclipse.org/c/jetty/org.eclipse.jetty.project.git/plain/jetty-start/src/test/resources/dist-home/modules/alpn-impl/)をダウンロードして、前記のディレクトリに置きます。JavaのバージョンとALPNモジュールのバージョンの対応関係は、[JettyのDocumentationのページ](http://www.eclipse.org/jetty/documentation/current/alpn-chapter.html#alpn-versions)に記載されています。

なお、このファイル自体はALPNモジュールのjarライブラリ本体の場所をテキストで記述しているだけですので、古いモジュールファイルをJavaのバージョンに合わせたファイル名でコピーした上で、ライブラリのURLを手動で適切なバージョンのものに書き換えることでも対処できます。

適切なバージョンのファイルが用意できれば、再びディレクトリ`${JETTY_HOME}`に戻り、`--create-files`の手順をやり直せば上手く行くはずです。

# ところが、いくつか困ったことに...

さて、このようにして、**ChromeではHTTP/2接続が確認できるものの、SafariやFirefoxなど、他のブラウザではなぜか通信が中断**してしまいます。以下、こちらで確認できた解決手順を示します。

## Safari 8以前など、HTTP/2非対応ブラウザへの対処

HTTP/2では、TLS接続時にプロトコルネゴシエーションを行うため際に用いられる拡張機能の[Application-Layer Protocol Negotiation](https://ja.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation) (ALPN)を利用してどのプロトコルで通信するかを自動的に調整するようになっています。ところが、上記の設定だけでは、ブラウザ側がALPNに対応していない場合、TLSのセッション開始後に通信が止まってしまう、という現象が発生するようです。

これに対処するには、**ALPN非対応時のデフォルトプロトコルとしてHTTP/1.1を指定**するよう設定してからJettyを起動します。具体的には、start.dを用いる場合は`${JETTY_HOME}/start.d/alpn.ini`を次のように修正してから(該当行のコメントを外してから)Jettyを起動します。

```text:${JETTY_HOME}/start.d/alpn.ini
(前略)
## Specifies what protocol to use when negotiation fails.
jetty.alpn.defaultProtocol=http/1.1
(後略)
```

XML設定ファイルで設定する場合は、`${JETTY_HOME}/etc/jetty-alpn.xml`を次のように修正します。

```xml:${JETTY_HOME}/etc/jetty-alpn.xml
        (前略)
        <Set name="defaultProtocol">
          <Property name="jetty.alpn.defaultProtocol" deprecated="alpn.defaultProtocol" default="http/1.1" />
        </Set>
        (後略)
```

ところで、それならばSafari 8向けにはNPN (Next Protocol Negotiation)を使ってSPDY/3.0等で通信するように誘導すれば...と考えそうになりますが、*Jetty 9.3ではSPDYとNPNの対応が廃止されています*ので、残念ながらHTTP/1.1にフォールバックするくらいしか方法はなさそうです。

## FirefoxでHTTP/2通信ができない現象への対処

[Firefox 36以降ではHTTP/2に対応しているはずですが、それでも通信できない場合がある](https://lepidum.co.jp/blog/2014-12-11/HTTP2-922/)ようです。これは、[JettyをOpenJDKで動作させると、一部の暗号化方式に対応していないため、Firefoxでは通信できなくなる](https://bugs.eclipse.org/bugs/show_bug.cgi?id=464144)、ということによるもののようです。

これに対処するには、**OpenJDKではなく、Oracleの純正JDKを利用する必要があります**。例えばCentOSのyum等、パッケージマネージャ経由では入手することができないため、[OracleのJavaダウンロードサイト](http://www.oracle.com/technetwork/java/javase/downloads/index.html)から直接.tar.gzアーカイブもしくはRPMパッケージを入手してインストールする必要があります。