nginxでHTTP2接続(not spdy3.1)の検証

今年5月にHTTP2のRFCが公開されて、今後サービス導入が加速しそうな雰囲気になってきていますね。
nginx, H2O, apache等々対応がささやかれている昨今ですが、2015/8/5にnginx が[Early Alpha Patch for HTTP/2](https://www.nginx.com/blog/early-alpha-patch-http2/)を出したので、ちょっと触ってみた。

# httpからhttp2への進化
　今までのhttpは1本のTCP接続の中で、次のリクエストを送るには、前のリクエストのレスポンスを待つ必要があった。（Head of Line Blocking）
更に、ブラウザから同時接続できるTCP接続数は最大6本であるため、
非常にリソースの無駄と考え、googleが2009年にspdyというhttpベースの新たなプロトコルを考え、これが後のhttp2の元になる。
　http2は、streamという仮想的な通信チャンネルを用意し、1本のTCP接続の中に複数のstreamを作ることでHead of Line Blocking問題を解決した。

![スクリーンショット 2015-09-17 11.15.05.png](https://qiita-image-store.s3.amazonaws.com/0/62386/6217ebd8-9dea-7640-24d2-115b2cafdaec.png)
※一本のTCP接続の簡単な概念図

![SnapCrab_NoName_2015-9-17_15-14-10_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/62386/86615900-9a9a-a9cd-8338-0b28ceddbf4c.png)
※HTTPのアクセス例

![SnapCrab_NoName_2015-9-17_15-12-28_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/62386/37636a48-b219-477f-6da3-802b539c9f7d.png)
※HTTP2のアクセス例

![SnapCrab_NoName_2015-9-17_14-49-45_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/62386/fe17dcd7-4660-9f2f-ad79-109f80f0888a.png)
※streamの概念図

またhttp2はstreamによる同時接続により接続のPriorityの制御もを導入している。
例えば、index.htmlがダウンロードされる前にcssやjsがダウンロードされるとまずいからである。
プライオリティ制御に関してはJxck_さんの記事を参考にしてもらうといいかも。
[HTTP2 のプライオリティ制御](http://qiita.com/Jxck_/items/16a5a9e9983e9ea1129f)

さらにhttp2にはServer Pushができるようになっている。
これに関しては次回以降チャレンジしてみようかな。

# 各種ブラウザの対応状況
http2の仕様上、httpでもhttpsでも利用できるのだが、Chrome, FireFox, IEは基本的にはhttpsの通信しか許可していないようだ。
[ブラウザ対応状況](http://caniuse.com/#search=http2)

APPLEもios9からhttps通信を推奨していたり、無料のSSL証明局サービスの開始がささやかれるなど、業界全体としてhttpsへシフトしているようだな。
[新しいSSL証明局:Let's Encrypt](https://letsencrypt.org/)

# http2を導入しているサービスってどこがあるんだろう
chromeのextensionでHTTP/2 and SPDY indicatorというのがあるので、入れて確認してみる。
対象サイトはalexaの世界ランキングのTOP10に関してみてみる。

|ランク|サイト|対応状況|
|:-:|:-:|:-:|
|1|Google.com|http2|
|2|Facebook.com|spdy3.1|
|3|Youtube.com|http2|
|4|Baidu.com|http|
|5|Yahoo.com|spdy3.1|
|6|Amazon.com|http|
|7|Wikipedia.org|spdy3.1|
|8|Qq.com|http|
|9|Twitter.com|http2|
|10|Google.co.in|http2|

![SnapCrab_NoName_2015-9-17_15-0-33_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/62386/719706a8-9384-e739-eb44-d3ecffe7ebef.png)

※google.com
雷アイコン青色：http2
雷アイコン黄色：spdy
雷アイコン灰色：非対応


googleはさすがgoogleといった感じで対応済み。
世界のTOP10を見るだけでも、大体http2かspdyに対応しているようだ。
[Alexa:世界Webサイト TOP500](http://www.alexa.com/topsites)


# 検証環境
お名前.com のVPSを調達。

```lang:Spec
OS: CentOS 6.5
CPU: Intel(R) Core(TM)2 Duo CPU     T7700  @ 2.40GHz
MEMORY: 1GB
```

# http2対応のnginxの導入
必要な物をyumで導入。

```lang:CentOS6.5
yum groupinstall -y "Development Tools"
yum install -y pcre pcre-devel
yum install -y zlib zlib-devel
yum install -y openssl openssl-devel
```

nginxのダウンロード

```lang:CentOS6.5
cd /usr/local/src
curl -O http://nginx.org/download/nginx-1.9.4.tar.gz
tar xvfz nginx-1.9.4.tar.gz
```

pacheを当てる

```lang:CentOS6.5
cd nginx-1.9.4/
curl http://nginx.org/patches/http2/patch.http2.txt > patch.http2.txt
patch -p1 < patch.http2.txt 
```

ビルド

```lang:CentOS6.5
./configure --prefix=/usr/local/nginx-1.9.4 --pid-path=/var/run/nginx.pid --lock-path=/var/lock/nginx.lock --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_addition_module --without-http_userid_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --with-http_v2_module

make
make install
```

nginxの起動

```lang:CentOS6.5
/usr/local/nginx-1.9.4/sbin/nginx
ps aux | grep nginx
```

```lang:CentOS6.5
root      1350  0.0  0.2  55496  3012 ?        Ss   Sep12   0:00 nginx: master process sbin/nginx
nobody    6585  0.0  0.3  56248  3676 ?        S    Sep15   0:50 nginx: worker process
root      7702  0.0  0.0 103252   832 pts/0    S+   18:59   0:00 grep nginx
```
なんか立ちあがったっぽい。
firewallの許可

```lang:iptables
# Generated by iptables-save v1.4.7 on Sat Sep 12 14:14:19 2015
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [21:2684]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22  -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80  -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 53  -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG NONE -j DROP
-A INPUT -p tcp -m tcp ! --tcp-flags FIN,SYN,RST,ACK SYN -m state --state NEW -j DROP
-A INPUT -p tcp -m tcp --tcp-flags FIN,SYN,RST,PSH,ACK,URG FIN,SYN,RST,PSH,ACK,URG -j DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
-A INPUT -d 255.255.255.255 -j DROP
-A INPUT -d 224.0.0.1 -j DROP
-A INPUT -p udp -m multiport --dport 137,138,139,445 -j DROP
-A INPUT -p udp --dport 17500 -j DROP
-A INPUT -m limit --limit 1/s -j LOG --log-prefix "[iptables firewall] : " --log-level=info

COMMIT
# Completed on Sat Sep 12 14:14:19 2015
```

オレオレ証明書の作成とssl session ticketの作成

```lang:CentOS6.5
mkdir /usr/local/etc/cert/
cd    /usr/local/etc/cert/

openssl genrsa 2048 > server.key
openssl req -new -key server.key > server.csr
openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt
openssl rand 48 > sslsessionticket_`date +’%Y%m%d’`.key

cd    /usr/local/nginx-1.9.4/
ln -s /usr/local/etc/cert cert
```

表示するページの作成

```lang:CentOS6.5
mkdir /var/www/vhost/xxx.xxx.xxx.co.jp/htdocs/
cd    /usr/local/nginx-1.9.4
ln -s /var/www/vhost/xxx.xxx.xxx.co.jp/htdocs/ vhost
```

nginxの設定ファイルの修正

```nginx:nginx.conf
#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;


        #access_log  logs/host.access.log  main;

        location ~ .*\.(jpg|JPG|gif|GIF|png|PNG|swf|SWF|css|CSS|js|JS|inc|INC|ico|ICO) {
            root    /usr/local/nginx-1.9.4/vhost/xxx.xxx.xxx.co.jp/htdocs;
            index   index.html;
            ssi     on;
            break;
        }


        location / {
            root    /usr/local/nginx-1.9.4/vhost/xxx.xxx.xxx.co.jp/htdocs;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # proxy the PHP scripts to Apache listening on 127.0.0.1:80
        #
        #location ~ \.php$ {
        #    proxy_pass   http://127.0.0.1;
        #}

        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
        #
        #location ~ \.php$ {
        #    root           html;
        #    fastcgi_pass   127.0.0.1:9000;
        #    fastcgi_index  index.php;
        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        #    include        fastcgi_params;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #    deny  all;
        #}
    }

    # HTTPS server
    #
    server {

        #listen  443 ssl;
        listen  443 ssl http2 ;
        server_name localhost_ssl;
        ssl_certificate     /usr/local/nginx-1.9.4/cert/server.crt;
        ssl_certificate_key /usr/local/nginx-1.9.4/cert/server.key;

    #    ssl_session_cache    shared:SSL:1m;
    #    ssl_session_timeout  5m;

    #    ssl_ciphers  HIGH:!aNULL:!MD5;
    #    ssl_prefer_server_ciphers  on;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location ~ .*\.(jpg|JPG|gif|GIF|png|PNG|swf|SWF|css|CSS|js|JS|inc|INC|ico|ICO) {
            root    /usr/local/nginx-1.9.4/vhost/xxx.xxx.xxx.co.jp/htdocs;
            index   index.html;
            ssi     on;
            break;
        }

        location / {
            root    /usr/local/nginx-1.9.4/vhost/xxx.xxx.xxx.co.jp/htdocs;
            index  index.html index.htm;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

}
```

設定のreload

```lang:CentOS6.5
/usr/local/nginx-1.9.4/sbin/nginx -t
/usr/local/nginx-1.9.4/sbin/nginx -s reload
```

http2の確認
さっきも出てきた、HTTP/2 and SPDY indicatorで見てみる。

![SnapCrab_NoName_2015-9-17_14-57-53_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/62386/5e1fc1d0-09ee-db25-36a0-306f6a18e0c5.png)

おぉ。確かにhttp2通信になってる。
一応nginxのログも確認する。

```lang:access.log
210.174.39.237 - - [12/Sep/2015:16:13:42 +0900] "GET / HTTP/2.0" 200 755 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36"
```
ちゃんとHTTP/2できていることが確認できました!!
ちなみにchromeのchrome://net-internals にアクセスし、Events を確認すると、http2のストリームの情報も見れるので、チューニングする際とかは抑えておきたいところかも。

#httpとhttp2の比較
・色んなサイズのimageを129個
・同じサイズのimageを360個
・同じサイズのjsを360個
ダウンロードしてくるindex.htmlを用意して比較してみる。

#####サイズの違う画像ダウンロード (sec)
|              | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 | 誤差 |
|--------------|-------|-------|-------|-------|-------|------|------|
| http         | 14.76 | 14.8  | 15.04 | 15.73 | 14.74 | 15.0 | 0.2  |
| http + TLS   | 15.46 | 16.48 | 15.06 | 14.73 | 14.77 | 15.3 | 0.3  |
| http 2 + TLS | 14.76 | 15.52 | 14.62 | 14.75 | 14.79 | 14.9 | 0.2  |

#####サイズの同じ画像ダウンロード(sec):18.3kB * 360枚
|              | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均  | 誤差 |
|--------------|-------|-------|-------|-------|-------|------|-----|
| http         | 12.18 | 11.77 | 11.83 | 12.05 | 13.57 | 12.3 | 0.3 |
| http + TLS   | 11.77 | 11.68 | 11.44 | 44.79 | 35.34 | 23.0 | 7.1 |
| http 2 + TLS | 11.4  | 11.25 | 11.25 | 13.36 | 11.44 | 11.7 | 0.4 |

#####jsダウンロード(sec): 33.1kB * 360枚
|              | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均  | 誤差 |
|--------------|-------|-------|-------|-------|-------|------|-----|
| http         | 102   | 564   | 102   | 102   | 102   | 194  | 92  |
| http + TLS   | 150   | 156   | 138   | 102   | 132   | 136  | 9   |
| http 2 + TLS | 66    | 60    | 59.57 | 57.47 | 57.64 | 60.1 | 1.6 |

どれをとってもhttp2の方が優秀なデータが出た！

#nginxのdirectiveを色々設定してみる
以下のように設定してみた。directiveの解説は今回はしない。
ファイルはgithubに置いているので、参考までにどうぞ。
[nginx-http2-setting-sample](https://github.com/takapan/nginx-http2-setting-sample/blob/master/nginx.conf)

| directive                    | value                                                                                                     |
|------------------------------|-----------------------------------------------------------------------------------------------------------|
| worker_rlimit_nofile         | 2048                                                                                                      |
| accept_mutex_delay           | 100ms                                                                                                     |
| sendfile                     | on                                                                                                        |
| tcp_nopush                   | on                                                                                                        |
| tcp_nodelay                  | on                                                                                                        |
| open_file_cache              | max=1000 inactive=20s                                                                                     |
| gzip                         | on                                                                                                        |
| gzip_comp_level              | 9                                                                                                         |
| gzip_types                   | text/css text/plain text/js text/javascript application/javascript application/jron-rpc                   |
| ssl                          | on                                                                                                        |
| ssl_prefer_server_ciphers    | on                                                                                                        |
| ssl_protocols                | TLSv1 TLSv1.1 TLSv1.2                                                                                     |
| ssl_ciphers                  | ECDHE+RSAGCM:ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:!EXPORT:!DES:!3DES:!MD5:!DSS; |
| ssl_session_tickets          | on                                                                                                        |
| ssl_session_ticket_key       | /usr/local/nginx-1.9.4/cert/sslsessionticket_20150915.key                                                 |
| ssl_session_cache            | shared:SSL:10m                                                                                            |
| ssl_session_timeout          | 10m                                                                                                       |
| http2_max_concurrent_streams | 64                                                                                                        |
| http2_streams_index_size     | 64                                                                                                        |
| http2_idle_timeout           | 30s                                                                                                       |
| http2_recv_timeout           | 3m                                                                                                        |
| http2_chunk_size             | 200k                                                                                                      |
| http2_max_field_size         | 4096                                                                                                      |
| http2_max_header_size        | 16384                                                                                                     |
| http2_pool_size              | 8192  

#再度検証してみる
#####サイズの違う画像ダウンロード(sec)
|              | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均  | 誤差  |
|--------------|-------|-------|-------|-------|-------|-------|------|
| http         | 14.97 | 14.69 | 15.53 | 14.73 | 14.81 | 14.9  | 0.2  |
| http + TLS   | 18.9  | 15.12 | 14.48 | 15.81 | 16.15 | 16.1  | 0.8  |
| http 2 + TLS | 14.84 | 14.89 | 14.67 | 14.67 | 14.66 | 14.75 | 0.05 |

#####サイズの同じ画像ダウンロード(sec):18.3kB * 360枚
|              | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均  | 誤差  |
|--------------|-------|-------|-------|-------|-------|-------|------|
| http         | 11.67 | 11.54 | 11.56 | 11.58 | 11.56 | 11.58 | 0.02 |
| http + TLS   | 13.13 | 28.69 | 14.26 | 15.99 | 17.48 | 18    | 3    |
| http 2 + TLS | 11.34 | 11.22 | 11.34 | 11.23 | 11.27 | 11.28 | 0.03 |

#####jsダウンロード(sec): 33.1kB * 360枚
|              | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均  | 誤差 |
|--------------|-------|-------|-------|-------|-------|------|-----|
| http         | 54.59 | 55.52 | 54.37 | 54.28 | 59.37 | 55.6 | 1.0 |
| http + TLS   | 60    | 49.47 | 51.22 | 57.5  | 60    | 56   | 2   |
| http 2 + TLS | 49.35 | 48.21 | 50.16 | 35.13 | 25.4  | 42   | 5   |

どれをとってもhttp2がよかった。
画像に関しては、オレオレ証明書の関係でsslの設定の恩恵はほとんど得られなかったが、
js等の静的コンテンツのダウンロードは、gzipの恩恵としてはhttp+TLSのデータの通りだが、http2+TLSの方で大分良くなった感じが見受けられる。

結論はhttp2のほうがやっぱ優れてるので、Webサイトへの導入はかなりいいかもしれない。
とりあえず、Let's Encryptがリリースされたら、SSL証明書を取得し、再度色々検証していきたいと思う。