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の設定は設定は以下の様になります。
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]
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 する配列の1つ目がステータスコード、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]
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で制御することができます。
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があるので、それを使用して、アクセス制限を行うことができます。
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ほど簡潔に表現できるわけではありませんが、人の目で見て
理解しやすい記述ができるのではないかと思います。