これは mod_mruby ngx_mruby Advent Calendar 2014 の10日目(12/10)の投稿です。
今回は mod_mruby および Apache を、WEBアプリケーションのコンテンツ配信サーバとして利用する際に、便利そうなスクリプトを幾つか書いてみたので紹介します。
尚、前提となる環境は、アドベントカレンダーの3日目に書きましたので、そちらを参照ください。
→ mod_mruby を Amazon EC2、Apache2.4 へ導入する
→ Apache のバージョンは 2.4.10
、mod_mruby のバージョンは 1.9.7
です。
私の考える mod_mruby の利点
Apache の各フェーズにフックできるので、例えば PHP、Perl等での WEBアプリケーションをそのまま生かしながら、機能を拡張できる点かなと。図にしてみました。(実際にはリバースプロキシとしても使えます。)
よくある処理を共通モジュール化しておけば、各WEBアプリケーション開発のコード量を減らせますし、またWEBサーバが Apache であれば(ngx_mruby であれば Nginx)、異なる言語のプロジェクトでも横断して利用することが出来ます。
これらは元々は Apache モジュールとして C言語 で書けばいいのですが、大変なので^^; mod_mruby として Ruby のシンタックスで簡単に書ける & mgem で拡張できるというのは強みかと。
Apacheのディレクティブ設定にあたり気づいたTips
-
基本的に mod_mruby の設定ディテクティブ(例:
mrubyHandlerMiddle
)は.htaccess
には書けないので、もし書くなら各種.conf
に書きます。-
.htaccess
が煩雑になるので、もっともな仕様だと思います。 -
が、Apache の標準ディレクティブの中には
.htaccess
に書けるものがあるので、フックするフェーズによっては、それと組み合わせることも可能かと。.htaccessの例AddHandler mruby-script .rb (.conf で事前に mrubyHandlerEnable On としておく必要がある)
Apache のハンドラの説明についてはこちら
→ http://httpd.apache.org/docs/current/ja/handler.htmlフィルタについは未検証ですが、たぶん同じようなことが出来るんじゃないかな。
→ http://httpd.apache.org/docs/current/ja/filter.html
-
-
URLが○○だったら適用する、というパターンを実現するには、次の2通りの方法があると思います(後者は未検証ですが、たぶん大丈夫)。
-
Apache の
Location
Directory
Files
等でリクエストを絞った上で、mod_mruby スクリプトを適用する。例<Location /mruby> mrubyHandlerMiddle "/path/to/unified_hello.rb" </Location>
-
mod_mruby のスクリプト内で判定する。
- URL が望むものでなければ
Apache::return(Apache::OK)
で終了する。 - ちなみに
Apache::return(Apache::OK)
Apache::return(Apache::DECLINED)
は、クライアントにレスポンスを返すのではなく、Apache の次フェーズに処理を移す、という挙動のはずです。
- URL が望むものでなければ
-
-
フックするフェーズ(例:
mrubyAuthChecker***
mrubyFixups***
mrubyLogTransaction***
mrubyOutputFilter
等)によってアクセスできる mod_mruby のクラスやメソッドが異なるので(Apache の処理フェーズ上の仕様)、もし動かなければ違うフェーズを試すのが良いかと。 -
同じフェーズに複数の mod_mruby スクリプトは適用できないので、やるとしたら
***First
***Middle
***Last
でバラけさすか、もしくは スクリプトの設計(Rubyの設計)で、1スクリプトに複数機能を持たせるか、になると思います。- ファイルの分割とか出来るのかしら?
その他、Apache の概要や設定ディレクティブについては、公式ページを見ると良いかと思います。
→ http://httpd.apache.org/docs/current/ja/
スクリプト例
前提として下記の例は、URLでは絞らず、全てのHTTPリクエストに適応する例で書いています。絞る場合は、上記に書いた Tips を参考に絞っていただければ。
フックする Apache の処理フェーズは、特に理由がなければ mrubyFixupsLast
としています。
理由としては「PHP」「Perl」「HTML/JS/CSSの静的コンテンツ」そのほかの「コンテンツハンドラ」を起動する直前のフェーズであり、他の Apache モジュールの影響を受けにくいというのと、単に私が好きだからです。
ちなみに私は、過去に Apache モジュールの開発経験があったりします^^;
最後のコンテンツハンドラの例を除いて、上記の「私の考える mod_mruby の利点」に書いたとおり、他のコンテンツハンドラ(PHP等)をそのまま生かして機能を拡張する例です。
① クライアントの接続元IPによってアクセス制限する
WEBアプリケーションの開発中に接続元の IP アドレスで制限をしたくなることは多いと思いますが(古くはガラケーサイトをキャリアのゲートウェイIPアドレスで制限など)、これを mod_mruby で設定する例です。
もしくは、ネットワーク的に クローズドな API サーバの場合など。
mrubyFixupsLast /usr/local/apache2/mruby/ip_check.rb
e = Apache::Env.new
c = Apache::Connection.new
white_list = [
'111.222.333.444',
'222.333.444.555',
'333.444.555.666'
]
if e['ENV']=='development'
if white_list.include? c.remote_ip
Apache::return(Apache::OK)
else
Apache::return(Apache::HTTP_UNAUTHORIZED)
end
end
ホワイトリストに含まれない IP アドレスからの HTTP リクエストは、Apache の認証エラーにしています。
② クライアントのユーザエージェントによりコンテンツを切り替える
よくありそうな、クライアントデバイスの種類による提供コンテンツの切り替えです。動的に生成するコンテンツの場合はコンテンツハンドラ側(PHP等)で切り替えれば良いのですが、下記の例では mod_mruby により静的コンテンツに対応させています。
mrubyFixupsLast /usr/local/apache2/mruby/agent_content.rb
r = Apache::Request.new
if /iPhone/ =~ r.headers_in['User-Agent']
/^(.+)\.html$/ =~ r.filename
r.filename = $1 + '_iphone.html'
end
Apache::return(Apache::OK)
iPhone で hoge.html
にアクセスしたら hoge_iphone.html
を適用させる、という内容です。
③ HTTPレスポンスヘッダを付与する
コンテンツの配信時に、ブラウザのキャッシュを有効利用するために、HTTPレスポンスヘッダを付与する例です。
ブラウザキャッシュとHTTPレスポンスヘッダの関係はこちらを参照ください。
→ 静的リソース(HTML,JS,CSS,画像)のブラウザキャッシュを制御
クライアントのブラウザキャッシュを有効利用することで、WEBサーバに対するアクセス負荷を軽減できます。
mrubyFixupsLast /usr/local/apache2/mruby/add_res_header.rb
r = Apache::Request.new
if /\.html$/ =~ r.filename
r.headers_out['Cache-Control'] = 'max-age=0'
end
if /\.(css|js)$/ =~ r.filename
r.headers_out['Cache-Control'] = 'max-age=600'
end
if /\.(gif|jpe?g|png)$/ =~ r.filename
r.headers_out['Cache-Control'] = 'max-age=86400'
end
Apache::return(Apache::OK)
配信するコンテンツの拡張子によって、キャッシュさせる時間を切り替えています。
④ コンテンツハンドラでの出力を更に置換する
上記の①〜③では Fixups
フェーズ(コンテンツハンドラの直前)にフックしていましたが、コンテンツ生成後のフェーズにフックする例です。
「PHP」「Perl」「HTML/JS/CSSの静的コンテンツ」等の出力内容を OutputFilter
フェーズで操作します。
SetOutputFilter mruby
mrubyOutputFilter /usr/local/apache2/mruby/convert_content.rb
f = Apache::Filter.new
content = f.flatten
f.cleanup
f.insert_tail content.gsub /index/, 'hoge'
f.insert_eos
上記は単純に index
という文字列を hoge
という文字列で置換する例です。
このフェーズでの置換は、私は可能性を感じています。WEBアプリケーション開発で何かお決まりの処理を、Apache のモジュールとして搭載できるからです。
(コンテンツのサイズが大きい場合のメモリ利用量は注意かも?)
⑤ おまけ:コンテンツハンドラとして mod_mruby を適用する
簡易なものを除いて、WEBアプリケーションのコンテンツ生成で mod_mruby を利用することはツール等を除きそんなに多くはないかと思いますが、設定例です。
基本的には .conf
で mrubyHandler***
で mod_mruby のスクリプトを指定すればいいのですが、mod_mruby ではハンドラ名 mruby-script
が公開されているので、次のように mod_mruby をハンドラとして起動することが出来ます。
事前に .conf
で mod_mruby をハンドラとして起動できるようにしておき、
mrubyHandlerEnable On
.htaccess
(別に.conf
に書いてもいいんだけど)で、リクエストコンテンツの拡張子が .rb
ならハンドラとして mod_mruby を適用します。
AddHandler mruby-script .rb
こうすることで、mrubyHandler***
で mod_mruby スクリプトを指定するのに比べて 任意の(ファイル名の) mod_mruby スクリプト を動的に(.conf
の設定なしに)適用することが出来ます。
この場合、スクリプトの cache ができない気もしますが。
また応用として、mod_rewrite からハンドラ起動も出来ます。
RewriteEngine on
RewriteRule \.rb$ - [L,H=mruby-script]
この例では単純に .rb
で判定していますが、mod_rewrite なので、更にいくらでも柔軟に条件を指定できるかと。
おわりに
mod_mruby での開発については、昨日の @matsumotory さんの記事(mod_mrubyインストール後入門)が分かりやすかったので、そちらも参照いただくと良いかと思います。
それ以外には mod_mruby の wiki を見たり、mod_mruby のサンプルソース を読むと、作り方が分かるかと。
明日(12/11)は takeswim さんによる「nginxにngx_mrubyを導入する」です。よろしくお願いします
16日目はまた私のターンなので、そこでは Amazon S3 へのリバースプロキシについて書く予定です。