ALB + httpd の組み合わせで一部のブラウザでページ表示できなかった問題の対処方法 (HTTP/2 version)

# はじめに

1年と少し前に以下の記事を書いた。

https://qiita.com/t-kigi/items/0723cd7c26472746d389

ここでは、

> ALB は HTTP2 を受け付けるが、ALB-EC2間は HTTP 1.1 で通信される。
Apache の mod_http2 を有効にしていると、HTTP 2 通信が可能なので Upgrade をレスポンスヘッダに含めてしまう。

と、ALB-EC2間の通信は HTTP/1.1 になるということを記載したが、実際には2020年10月のアップデートによって、ALB-EC2間を (条件付きではあるが) HTTP/2 で通信できるようになっている。

https://aws.amazon.com/jp/about-aws/whats-new/2020/10/application-load-balancers-enable-grpc-workloads-end-to-end-http-2-support/

そこで、その問題の解決策を記載する。

## Apache の設定

ここでは記事執筆時点の Amazon Linux 2 を利用し、yum で httpd を取ってきた。 バージョンは 2.4.46 のものを利用している。

最初に、以下の通り検証用表示設定を行う。

```bash:コンテンツの準備
$ sudo mkdir -p /var/www/example
$ sudo chmod 777 /var/www/example
$ echo OK > /var/www/example/index.html
$ echo HEALTH > /var/www/example.health.html
```

```conf:/etc/httpd/conf.d/vhost.conf
<VirtualHost *:80>
  DocumentRoot /var/www/example
  <Location />
    Require all denied
  </Location>
  <Location /health.html>
    Require all granted
  </Location>
</VirtualHost>

<VirtualHost *:80>
  DocumentRoot /var/www/example
  ServerName example.t-kigi.net
</VirtualHost>
```

最初にサーバーネームを指定しないVirtualHostを置くことで、想定しないホスト名やIPアドレスでウェブサイトにアクセスしてきた場合の通信を弾くようにしている。
一方、ALBからのヘルスチェックはホスト名なしで飛んでくるので、`/health.html` を許可している。

この状態で httpd を再起動させて検証してみる。　すると、HTTP/1.1 で通信した場合はアップグレードヘッダが含まれているが、HTTP/2 で通信した場合はそれを正常に受け入れてアップグレードヘッダを含めていないことが分かる。

```bash
# Host指定なしの通信
$ curl -I http://localhost/
HTTP/1.1 403 Forbidden
Date: Wed, 07 Apr 2021 08:52:54 GMT
Server: Apache/2.4.46 ()
Content-Type: text/html; charset=iso-8859-1
$ curl -I http://localhost/health.html
HTTP/1.1 200 OK
Date: Wed, 07 Apr 2021 08:52:59 GMT
Server: Apache/2.4.46 ()
Upgrade: h2,h2c
Connection: Upgrade
Last-Modified: Wed, 07 Apr 2021 08:38:59 GMT
ETag: "3-5bf5ddcbc2364"
Accept-Ranges: bytes
Content-Length: 3
Content-Type: text/html; charset=UTF-8

# Host指定ありの HTTP/1.1 通信
$ curl -I --http1.1 -H 'Host: example.t-kigi.net' http://localhost/
HTTP/1.1 200 OK
Date: Wed, 07 Apr 2021 08:54:07 GMT
Server: Apache/2.4.46 ()
Upgrade: h2,h2c
Connection: Upgrade
Last-Modified: Wed, 07 Apr 2021 08:25:50 GMT
ETag: "3-5bf5dadb52926"
Accept-Ranges: bytes
Content-Length: 3
Content-Type: text/html; charset=UTF-8

# Host指定ありの HTTP/2 通信
$ curl -I --http2 -H 'Host: example.t-kigi.net' http://localhost/
HTTP/1.1 200 OK
Date: Wed, 07 Apr 2021 08:54:36 GMT
Server: Apache/2.4.46 ()
Last-Modified: Wed, 07 Apr 2021 08:25:50 GMT
ETag: "3-5bf5dadb52926"
Accept-Ranges: bytes
Content-Length: 3
Content-Type: text/html; charset=UTF-8
```

なお、設定している `t-kigi.net` は私個人の所有するドメインであり、サンプルとして利用しているので、適時 `example.com` などと読み替えて欲しい。

## ALB の設定

新しくALBを作成してリスナーを設定するが、ALB-EC2間の通信を HTTP/2 にする場合 **HTTPS通信のみを受け付ける設定にする必要がある** 。これが、最初に書いた「条件付きで」の条件になる。

![SnapCrab_NoName_2021-4-7_17-58-37_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/b6fbad3c-e9f1-384c-1e97-74330a55d4b3.png)

ロードバランサーのプロトコルに HTTP などを含んでしまった場合、後のターゲットグループの指定で HTTP/2 が選べない。

![SnapCrab_NoName_2021-4-7_11-40-51_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/053bd5a3-a5ad-20f5-1b91-759b3bcfa984.png)

リスナーを HTTPS として、プロトコルバージョンを HTTP2 とすると、ALB-EC2間の通信を HTTP/2 で行ってくれるようになる。 ここでは省略するが、実際にアクセス可能な設定 (ALBの設定、ACMの設定、`example.t-kigi.net` でアクセスするための Route53 の設定など) を実施したのち、`https://example.t-kigi.net` にアクセスすると。

![SnapCrab_NoName_2021-4-7_18-10-24_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/d79b7028-4256-310c-3588-58c71cf95260.png)

と正常にページが表示される。 httpd のログを見ても確認できる。
ログと併記しているが、Windows Chrome だけでなく、前々の状態ではページが表示できなかった iPhone の Safari や curl でもきちんとページが表示できることを確認した。

```
# ALB -> EC2 のヘルスチェックが HTTP/2 で実施されている
172.31.35.226 - - [07/Apr/2021:09:10:08 +0000] "GET /health.html HTTP/2.0" 200 3 "-" "ELB-HealthChecker/2.0"

# ALB -> EC2 の通信が HTTP/2 で実施されている
172.31.35.226 - - [07/Apr/2021:09:10:12 +0000] "GET / HTTP/2.0" 200 3 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36"

# 表示問題のあった iPhone Safari からの通信でも問題なく表示できている
172.31.2.27 - - [07/Apr/2021:09:15:17 +0000] "GET / HTTP/2.0" 200 3 "-" "Mozilla/5.0 (iPhone; CPU iPhone OS 14_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1"

# 表示問題のあった curl からの通信でも問題なく表示できている
172.31.2.27 - - [07/Apr/2021:09:18:41 +0000] "GET / HTTP/2.0" 200 3 "-" "curl/7.68.0"
```

## HTTP ターゲットグループを追加する場合

何らかの理由があって HTTP 通信を受け付ける必要があるとき、HTTP2用として作成したターゲットグループは選択することができない。

![SnapCrab_NoName_2021-4-7_18-23-14_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/135aea1f-954b-c496-c689-1465f5384dd4.png)

そのため、HTTP通信をサーバーに通そうとした場合、HTTP1.1 を使う新しいターゲットグループを作ってやる必要があるのだが、同じサーバーの同じ設定で受け取る場合は[前々と同じく Upgrade ヘッダを含めてしまう問題](https://qiita.com/t-kigi/items/0723cd7c26472746d389) が再発する。 なので、VirtualHost など中で適切に振り分けてやる必要があったり、HTTP/1.1用のインスタンスを作ったりする必要がある。

しかし、一般的なユースケースでは「httpでアクセスしてきた場合、httpsにリダイレクトしたい」ぐらいだと思う。 この場合は、ターゲットグループを使うのではなく、ALB側にリダイレクトで対処してもらうのが良い。

![SnapCrab_NoName_2021-4-7_18-27-35_No-00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/502582/704e4908-468b-ccd0-cd6b-08b3caa723cd.png)

この設定を入れるだけで、ALBを経由してサーバーに到達するすべての通信は HTTPS 経由であることが保証される。


## HTTP/1.1 で接続してきた場合の挙動

この設定を行うと、**ALBはHTTP/1.1接続してきた場合、接続を許可しない** 。 実際に curl で通信を行ってみると、ALB から 464 が返ってきて通信に失敗していることが分かる。

```bash
$ curl -v --http1.1 https://example.t-kigi.net/
*   Trying 52.69.158.169:443...
* TCP_NODELAY set
* Connected to example.t-kigi.net (52.69.158.169) port 443 (#0)
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use http/1.1
* Server certificate:
*  subject: CN=*.t-kigi.net
*  start date: Jul 15 00:00:00 2020 GMT
*  expire date: Aug 15 12:00:00 2021 GMT
*  subjectAltName: host "example.t-kigi.net" matched cert's "*.t-kigi.net"
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
> GET / HTTP/1.1
> Host: example.t-kigi.net
> User-Agent: curl/7.68.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 464 
< Server: awselb/2.0
< Date: Wed, 07 Apr 2021 09:30:45 GMT
< Content-Length: 0
< Connection: keep-alive
< 
* Connection #0 to host example.t-kigi.net left intact
```

対応状況などを見るとメジャーなブラウザは対応していると思うのだが、例えばIE10以前をサポートしなくてはいけない場合だったり、フィーチャーフォン対応などがあったりする場合は、HTTP/2だけでよいかは事前に確認する必要があるだろう。

> 下記ウェブブラウザが対応している。ただし、全てHTTP/2 over TLSのみである。
> - Google Chrome (30は設定が必要[6][7]）
> - Mozilla Firefox (34から標準で有効[8])
> - Microsoft Windows 10上のInternet Explorer 11
> - Microsoft Edge[9]
> - Opera
> - Safari 9から
> 引用 - https://ja.wikipedia.org/wiki/HTTP/2 (2021/04/07閲覧)


# 参考

https://dev.classmethod.jp/articles/application-load-balancers-enable-grpc-workloads-end-to-end-http-2-support/