Posted at

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

More than 1 year has passed since last update.

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の設定は設定は以下の様になります。


/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]


/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 する配列の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]


/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で制御することができます。


/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があるので、それを使用して、アクセス制限を行うことができます。


/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ほど簡潔に表現できるわけではありませんが、人の目で見て

理解しやすい記述ができるのではないかと思います。