2016-10-28

H2OでHTTP/2を体感する

HTTP/2対応のWebサーバソフトウェアであるh2oを試してみました。

OS Xでインストール&セットアップ

インストール
$ brew install h2o

launchctlで操作できるようにplistのリンクを作成しておきます。

$ ln -sfv /usr/local/opt/h2o/*.plist ~/Library/LaunchAgents

リンクを貼ったplistはlaunchctlでstart/stopできるように以下のように修正します

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>Label</key>
    <string>homebrew.mxcl.h2o</string>
    <key>RunAtLoad</key>
    <false/>
    <key>KeepAlive</key>
    <false/>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/local/opt/h2o/bin/h2o</string>
        <string>-c</string>
        <string>/usr/local/etc/h2o/h2o.conf</string>
    </array>
  </dict>
</plist>

launchdに読み込ませます

$ launchctl load ~/Library/LaunchAgents/homebrew.mxcl.h2o.plist

あとは以下のコマンドでstart/stopできます

$ launchctl start homebrew.mxcl.h2o
$ launchctl stop homebrew.mxcl.h2o

HTTP/2を試したい場合はTLS接続が前提となっています。/usr/local/opt/h2o/share/doc/h2o/examplesにサンプルのconfigや秘密鍵、証明書が入っているので、それらを使ってHTTP/2をすぐに試せます。(もちろんオレオレ証明書ですが)

$ cd /usr/local/opt/h2o/share/doc/h2o
$ h2o -c ./examples/h2o/h2o.conf

Ubuntuでインストール

ソースからインストール(mruby対応版)
$ sudo apt-get install locate git cmake build-essential \
checkinstall autoconf pkg-config libtool python-sphinx wget \
libcunit1-dev nettle-dev libyaml-dev libuv-dev bison ruby-dev -y
$ git clone git@github.com:h2o/h2o.git
$ cd h2o
$ cmake -DWITH_BUNDLED_SSL=on -DWITH_MRUBY=on .
$ make
$ sudo make install

サンプルの設定で回す場合は以下のようにして実行すればOK。

$ cd /usr/local/share/doc/h2o
$ sudo /usr/local/bin/h2o -c examples/h2o/h2o.conf

ChromeでHTTP/2を体感する

以下のHTMLにアクセスしてHTTP/2を体感してみます。
<!DOCTYPE html>
<html>
  <body>
    <img src="hoge1.png" />
    <img src="hoge2.png" />
    <img src="hoge3.png" />
    <img src="hoge4.png" />
    <img src="hoge5.png" />
    <img src="hoge6.png" />
    <img src="hoge7.png" />
    <img src="hoge8.png" />
    <img src="hoge9.png" />
    <img src="hoge10.png" />
  </body>
</html>

HTTP/1.1バージョン(http://{Host Name}:8080)

h2o_http1_timeline

HTTP/2バージョン(https://{Host Name}:8081)

h2o_http2_timeline

HTTP/1の場合は同一ドメインに対して同時接続数6の制限が効いていますが、HTTP/2の場合は1つのTCP接続を使うことになるので、同時にリクエストが行われます。

mruby拡張

404 Not Foundエラーをmrubyから返す場合
paths:
  /:
    file.dir: /usr/local/share/doc/h2o/examples/doc_root
    mruby.handler: |
      Proc.new do |env|
        [404, {'content-type' => 'text/plain'}, ["File Not Found By MRuby\n"]]
      end

IPアドレス制限

paths:
  "/":
    mruby.handler: |
      Proc.new do |env|
        if /xxx\.xxx\.xxx\.xxx/.match(env["REMOTE_ADDR"])
          [399, {}, []]
        else
          [403, {'content-type' => 'text/plain'}, ["access forbidden\n"]]
        end
      end

標準で用意されているディレクティブによらず柔軟な処理が可能なので便利

HTTP/2のサーバPushを体感する

H2Oのmruby拡張を使ってサーバPushを体感してみます。

設定ファイルのpaths以下は以下のように記載します。mruby.handlerだと設定ファイルに直接Rubyコードを書けますが、色々と不便なので、mruby.handler-fileで外部ファイルを読み込ませています。

paths:
  /:
    mruby.handler-file: /path/to/handler.rb
    file.dir: /usr/local/share/doc/h2o/examples/doc_root

handler.rbはこんな感じで

Proc.new do |env|
    push_paths = []
    if /(\/|\.php)\z/.match(env["PATH_INFO"])
        10.times do |i|
            push_paths << "/hoge#{i+1}.png"
        end
    end
    [399, push_paths.empty? ? {} : {
      "link" => push_paths.map{|p| "<#{p}>; rel=preload; as=image"}.join("\n")
    }, []]
end

リクエストしてみるとこんな感じでドキュメント読み込みが完了する前には既に画像取得が完了しているため、トータルのリクエスト時間は短くなります(注・以下のリクエストはsleepを意図的に入れているため、上記例とのタイムライン上の比較は出来ません)

h2o_http2_serverpush

サンプルなのでlinkが固定値(hoge*.rb)になっていますが、実際には各ドキュメントに対して静的なファイルをマッピングして、handler側でマッピングされた静的ファイルのリストをlinkヘッダで返すような形になります。実運用的にはクローラのようなHTMLパーサでマッピングを自動生成するような感じになるんですかね。

PHPと連携してみる

php-fpmを使ってphpを利用できるようにします。php5-fpmがUnixドメインソケットを利用している場合は以下のように記述します。
user: www-data

listen: 8080
listen:
  port: 8081
  ssl:
    certificate-file: examples/h2o/server.crt
    key-file: examples/h2o/server.key
file.custom-handler:
  extension: .php
  fastcgi.connect:
    port: /var/run/php5-fpm.sock
    type: unix
hosts:
  "127.0.0.1.xip.io:8080":
    paths:
      /:
        file.dir: /usr/local/share/doc/h2o/examples/doc_root
    access-log: /dev/stdout
  "alternate.127.0.0.1.xip.io:8081":
    listen:
      port: 8081
      ssl:
        certificate-file: examples/h2o/alternate.crt
        key-file: examples/h2o/alternate.key
    paths:
      /:
        file.dir: /usr/local/share/doc/h2o/examples/doc_root.alternate
    access-log: /dev/stdout

file.dirは絶対パスにしないとFastCGI側で以下のエラーになります。

[lib/handler/fastcgi.c] in request:/index.php:Primary script unknown

userが未指定の場合はh2oのプログラムを起動したユーザが実行ユーザになります。実行ユーザがphp5-fpmのソケットに対して適切なパーミッションが無い場合、以下のエラーになります。

[lib/handler/fastcgi.c] in request:/index.php:
connection failed:failed to connect to host

今回試した環境では以下のようなパーミッションになっていたので、www-dataユーザを実行ユーザにする必要がありました。

$ ls -la /var/run/php5-fpm.sock
srw-rw---- 1 www-data www-data 0 10月 22 02:32 php5-fpm.sock

参考URL

このエントリーをはてなブックマークに追加