HHVM に組み込みの Proxygen で SSL/TLS や HTTP/2 を利用する



概要
---

HHVM には HTTP サーバーライブラリの Proxygen が組み込まれており、コマンドラインから起動できます。Proxygen は TLS/SSL および HTTP/2 に対応しています。

PHP のビルトインサーバーは SSL/TLS、HTTP/2 に対応していないので、運用環境でHHVM を使わなくてもテスト環境での選択肢の1つとして考えることができます。HHVM のプロジェクトが配布している Docker イメージを利用することで、導入の敷居が下がります。

テスト環境のために SSL/TLS を利用するほかの選択肢として、Swoole や Event エクステンションを挙げます。コードの例は[こちら](http://qiita.com/masakielastic/items/4adf156763fee6a861f2)の記事をご参照ください。


コマンドツールで Proxygen を起動させる
----------------------------------

HHVM のコマンドツールから Proxygen を直接させる場合、オプションに `-v Server.EnableH2C=true` を指定すれば HTTP/2 が利用できます。

```bash
hhvm -m server -v Server.Type=proxygen -v Server.EnableH2C=true -p 8080
```

このオプションは SSL/TSL なしで HTTP/2 を利用するためにあります。主要なブラウザーは SSL/TLS なしでの HTTP/2 通信を許可しないため、curl などのコマンドツールで確認する必要があります。

SSL/TLS もサポートしてみましょう。項目が多いので、ini 設定ファイルを用意して読み込むことにします。

```bash
hhvm -m server -c hhvm.prod.ini
```

```ini:hhvm.prod.ini
hhvm.server.type = proxygen
hhvm.server.port = 8080

; http/2
hhvm.server.ssl_next_protocols[] = h2
hhvm.server.ssl_next_protocols[] = http/1.1

; http/2 without ssl/tls
hhvm.server.enable_h2c = true

hhvm.server.enable_ssl = true
hhvm.server.ssl_certificate_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
hhvm.server.ssl_certificate_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
```

Ubuntu の場合、テスト用の SSL/TLS の証明書は ssl-cert パッケージから入手できます。

Docker の公式イメージを利用する
---------------------------

今度は Docker Hub で配布されている Docker の公式イメージを利用してみましょう。


`docker-compose.yml`、`Dockerfile`、`hhvm.prod.ini`、`public/index.php` を用意します。

```yml:docker-compose.yml
version: '2'

services:
  hhvm:
    build: ./
    volumes:
      - ./public:/var/www/public
    ports:
      - "80:80"
      - "443:443"
```

```bash:Dockerfile
FROM hhvm/hhvm-proxygen:latest
ENV DEBIAN_FRONTEND noninteractive

RUN apt-get update && apt-get install -y ssl-cert \
  --no-install-recommends && rm -r /var/lib/apt/lists/*

ADD hhvm.prod.ini /etc/hhvm/site.ini

EXPOSE 80 443
```

```ini:hhvm.prod.ini
; https://github.com/hhvm/hhvm-docker/blob/master/hhvm-latest-proxygen/server.ini
; php options

pid = /var/run/hhvm/pid

; php options

pid = /var/run/hhvm/pid

; hhvm specific 

hhvm.server.port = 80
hhvm.server.type = proxygen 
hhvm.server.default_document = index.php
hhvm.server.error_document404 = index.php
hhvm.repo.central.path = /var/run/hhvm/hhvm.hhbc
hhvm.server.source_root=/var/www/public

; default log location is stdout/err, which is good for docker
hhvm.log.use_log_file = true

; http/2
hhvm.server.ssl_next_protocols[] = h2
hhvm.server.ssl_next_protocols[] = http/1.1

; http/2 without ssl/tls
hhvm.server.enable_h2c = true

; ssl/tls
hhvm.server.enable_ssl = true
hhvm.server.ssl_certificate_file = /etc/ssl/certs/ssl-cert-snakeoil.pem
hhvm.server.ssl_certificate_key_file = /etc/ssl/private/ssl-cert-snakeoil.key
```

```php:public/index.php
<?php
phpinfo();
```

Docker イメージを起動させる
------------------------

Docker イメージをもとにサービスを起動させます。

```bash
docker-compose up --build -d
```

サービスを停止させるには次のコマンドを実行します。

```bash
docker-compose stop
```


phpinfo 関数で ini 設定を確認する
------------------------------

ブラウザーから `hhvml.server` でページ内を検索して、`ini` ファイルが反映されていることを確認します。じっくり眺めていると、公式マニュアルに掲載されていない項目があるかもしれません。

curl で確かめる
--------------

curl で HEAD リクエストを送信して HTTP/2 通信ができるか確かめてみましょう。

```bash
curl --http2 -I -v http://localhost:8080
```

レスポンスに `Upgrade: h2c`、`HTTP/2 200` が含まれていることを確認します。

```bash
> HEAD / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.50.3
> Accept: */*
> Connection: Upgrade, HTTP2-Settings
> Upgrade: h2c
> HTTP2-Settings: AAMAAABkAAQAAP__
> 
< HTTP/1.1 101 Switching Protocols
HTTP/1.1 101 Switching Protocols
< Upgrade: h2c
Upgrade: h2c
< Date: Mon, 26 Sep 2016 21:41:14 GMT
Date: Mon, 26 Sep 2016 21:41:14 GMT
< Connection: upgrade
Connection: upgrade
* Received 101
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=52
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!

< HTTP/2 200 
HTTP/2 200 
< vary: Accept-Encoding
vary: Accept-Encoding
< content-type: text/html
content-type: text/html
< x-powered-by: HHVM/3.15.0
x-powered-by: HHVM/3.15.0
< content-length: 50947
content-length: 50947
< date: Mon, 26 Sep 2016 21:41:14 GMT
date: Mon, 26 Sep 2016 21:41:14 GMT
```

今度は SSL/TLS で通信してみましょう。

```bash
curl --http2 -I -v https://localhost:8443
```

レスポンスに `ALPN, offering h2`、`HTTP/2 200 ` が含まれていることを確認します。

```bash
* Connected to localhost (127.0.0.1) port 8443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /usr/local/etc/openssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* NPN, negotiated HTTP2 (h2)
* 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, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Unknown (67):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=11687faae091
*  start date: Sep 26 19:28:39 2016 GMT
*  expire date: Sep 24 19:28:39 2026 GMT
*  issuer: CN=11687faae091
*  SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fbf7f008800)
> HEAD / HTTP/1.1
> Host: localhost:8443
> User-Agent: curl/7.50.3
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 200 
HTTP/2 200 
< vary: Accept-Encoding
vary: Accept-Encoding
< content-type: text/html
content-type: text/html
< x-powered-by: HHVM/3.15.0
x-powered-by: HHVM/3.15.0
< content-length: 50508
content-length: 50508
< date: Mon, 26 Sep 2016 21:44:20 GMT
date: Mon, 26 Sep 2016 21:44:20 GMT
```