Apacheで動いていたwebサービスをH2Oに移行する方法

apache+mod_phpで動いていたサービスをH2Oに置き換えたときに行ったことに
ついて書きます。

# インストール
H2Oは以下の手順でインストールしました。OSはAmazon Linux 2016.09を使っています。
CentOS 6系のサーバーでも同じ様な手順になると思います。
まずは必要なライブラリ等をyumでインストールします。

```
$ yum -y gcc gcc-c++ curl curl-devel libarchive libarchive-devel expat expat-devel libyaml-devel zlib zlib-devel openssl-devel libyaml-devel bison 
```

CMakeをダウンロードしてきてインストールを行います。

```
    cd /usr/local/src
    wget http://www.cmake.org/files/v3.0/cmake-3.6.2.tar.gz
    tar -xvf cmake-3.6.2.tar.gz
    cd cmake-3.6
            ./bootstrap --prefix=/usr \
            --system-libs       \
            --mandir=/share/man \
            --no-system-jsoncpp \
            --docdir=/share/doc/cmake-3.6.2
    make
    make install
```

H2Oをgithubからcloneしてきて、ビルドします。今回はv2.1.0-beta4をビルドしました。

```
    cd /usr/local/src
    git clone https://github.com/h2o/h2o.git
    git checkout refs/tags/v2.1.0-beta4
 　 git submodule update --init --recursive
    cmake -DWITH_BUNDLED_SSL=on .
    make
    sudo make install
```

# PHPの設定について

apacheではmod_phpでPHPを実行することが多いと思います。H2OにもFastCGIの機能は
ありますが、今回はphp-fpmを使って、unixsocketでH2Oと通信することにしました。
例えばのwordpressの設定は設定は以下の様になります。

```yaml:/etc/h2o/conf/h2o.conf
pid-file: /var/run/h2o.pid
user: nobody

error-log: /var/log/h2o/error.log

# PHPの設定
file.custom-handler:
  extension: .php
  access-log:
    path: /var/www/logs/foo/fpm-access.log
    format: "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{X-Forwarded-For}i\" %{process-time}x %{duration}x"
  fastcgi.connect:
    port: /var/run/php-fpm/php-fpm.sock
    type: unix

# ドメイン設定
hosts:
  "foo.example.com:80":
    listen:
      port: 80
    access-log:
      path: /var/www/logs/foo/access.log
      format: "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{X-Forwarded-For}i\" %{process-time}x %{duration}x"
    paths:
      "/":
        file.dir: /var/www/html/foo/
        redirect:
          url: /index.php/
          internal: YES
          status: 307
```

# Rewrite等の設定について

phpを動かすまでは上記の設定でいいのですが、移行作業となると今までApacheが行っていた
処理をH2Oに移す必要があります。その中で最も代表的なのが、apacheのmod_rewriteなの
ではないかと思います。簡単な設定なら、H2Oのconfにも書くことが出来ますが、正規表現
でマッチした内容をもとにrewriteをしている場合はh2oのmruby handlerを使って実現できます。

例えばapacheで以下のようなリダイレクト設定はH2Oでは以下のように書けます。

```
RewriteRule   ^/aaa/test/                                https://foo.example.com/aaa/                       [R=301,L]
RewriteRule   ^/bbb/test/                                https://foo.example.com/bbb/                       [R=301,L]

```

```ruby:/etc/h2o/hook/rerirect_rule.rb
class RedirectRule

  def call(env)
    if !/^(aaaa|bbb)\/test/.match(env['PATH_INFO'])
      redirect_path = $1
      return [301, {'Location' => "http://foo.example.com#{redirect_path}"}, []]
    else
      [399, {}, []]
    end
  end
end

RedirectRule.new
```

mrubyでのハンドラーの記述はrackのインターフェースに沿ったものになります。
return する配列の１つ目がステータスコード、2つ目がレスポンスヘッダー、3つ目が
Bodyになります。ステータス399はH2Oで使える特別なステータスで、次のハンドラーに
処理を委譲します。

また引数のenvはhashオブジェクトでキーは以下の内容になります。
```
["REQUEST_METHOD", "SCRIPT_NAME", "PATH_INFO", "QUERY_STRING", "SERVER_NAME", "SERVER_ADDR", "SERVER_PORT", "HTTP_HOST", "REMOTE_ADDR", "REMOTE_PORT", "USER_NAME", "ENV_NAME", "HTTP_ACCEPT", "HTTP_COOKIE", "HTTP_USER_AGENT", "HTTP_CACHE_CONTROL", "HTTP_ACCEPT_ENCODING", "HTTP_ACCEPT_LANGUAGE", "HTTP_UPGRADE_INSECURE_REQUESTS", "rack.url_scheme", "rack.multithread", "rack.multiprocess", "rack.run_once", "rack.hijack?", "rack.errors", "SERVER_SOFTWARE"]
```

H2Oの前段にRPがある場合はもっとヘッダー
が増えると思いますが、rewrite等の作業で良く使うのは、"PATH_INFO"と"HTTP_USER_AGENT"だと思います。

他のサーバーの対してmod_rewriteでproxyをmrubyで実現する場合はhttp_request関数を使用します。


```
RewriteRule ^/contact(.*)$  http://foo.example.net/contact$1                 [QSA,NE,P,L]
```

```ruby:/etc/h2o/hook/proxy_rule.rb
class ProxyRule
  def call(env)
    if /^\/contact(\.*)/.match(env["PATH_INFO"])
      rewrite_path = $1
      headers = {}
      env.each do |key, value|
        if /^HTTP_/.match(key)
          headers[$'] = value
        end
      end
      headers['Content-Type'] = env['CONTENT_TYPE']
      headers['X-Forwarded-Host'] = env['HTTP_HOST']
      headers['X-Forwarded-For'] = env['REMOTE_ADDR']
      headers['User-Agent'] = env['HTTP_USER_AGENT']
      return  http_request(
                "http://foo.example.net/contact#{rewrite_path}?#{env["QUERY_STRING"]}",
                method:  env["REQUEST_METHOD"],
                headers: headers,
                body:    env["rack.input"]
              ).join
    end
  end
end

ProxyRule.new
```

envに入ってきているヘッダー情報はクライアントから送られているヘッダーの情報と違ってきていますので、
proxy先のアプリケーションによっては、必要なヘッダー情報を合わせる必要があります。上記の例ではContent-Type
などのヘッダーを書き換えています。

# ヘッダー情報の追加について
ブラウザがページを回遊している際に同じ画像やCSSを読み込まないようにexpireヘッダーを付加することが
あります。Apacheの場合では以下のように記述します。

```
    ExpiresActive On
    <FilesMatch "\.(gif|jpg|png|js)$">
        ExpiresDefault "access plus 1 month"
        Header set Cache-Control "max-age=604800"
    </FilesMatch>
```

H2Oでは先ほどのrewriteと同じようにmruby handlerで制御することができます。

```ruby:/etc/h2o/hook/add_expire_header.rb
class AddExpireHeader
  def call(env)
    headers = {}
    if /\.(css|js|png|jpg|gif)/.match(env["PATH_INFO"])
      headers["cache-control"] = "max-age=86400"
      headers["Expires"] = "30d"
    end
    [399, headers, []]
  end
end

AddExpireHeader.new
```
# アクセス制限について
管理画面など、特定のIP以外は閲覧させたいなく場合も出てきます。Apacheでは以下の方法で制御しています。

```
    <Location /wp_login.php>
        Require ip 169.254.0.0/24
        Require ip 10.1.0.0/16
    </Location>

    <Location /wp_admin>
        Require ip 169.254.0.0/24
        Require ip 10.1.0.0/16
    </Location>
```

H2Oでは、ACLを行うDSLがあるので、それを使用して、アクセス制限を行うことができます。

```ruby:/etc/h2o/hook/acl.rb
ALLOW_HOSTS = %w(
169.254.0.0/24 10.1.0.0/16
)

require "trie_addr.rb"
trie = TrieAddr.new.add(ALLOW_HOSTS)

acl {
  deny { !trie.match?(addr) && (path.start_with?("/wp-login") ||  path =~ /wp-admin/) }
}

```

# まとめ

apacheのconfの一部をH2Oで実現する方法を見てきました。特に当初はPHPファイルをfast-cgiで動かして
いたようなサービスだと、機能実現のために、apacheのrewriteのボリュームが大きくなる傾向になるのでは
ないかと思います。
EOLを迎えたPHPをPHP7にして、ついでにwebサーバーを変更したいような場合では、
mrubyによるrewriteやproxyはapacheのconfほど簡潔に表現できるわけではありませんが、人の目で見て
理解しやすい記述ができるのではないかと思います。