論文の休憩がてら、mod_mrubyとredisを使ってファイル単位のアクセス制御をしてみました。重たいメディアファイル等、同一ファイルに対する不特定多数クライアントからの同時接続数を制限したい場合など多々あると思います。それをmod_mrubyで簡単に実現しようという話です。
ファイルアクセス時にインクリメント・デクリメントするスクリプトを準備
以下のようなスクリプトを2つ用意するのと、redisにアクセスできるようにしておきます。githubにも上げていますので自由にお使いください。
まずは、あるファイルにアクセスがあったら、そのファイルのアクセスカウンター(redis上にある)をインクリメントします。Apacheのアクセスチェッカーフェイズでフックするようにしましょう。
下記のlimit変数にファイルに対するアクセスカウンタの制限値を書いておきます。下記の場合は1ですので、2以上同じファイルに同時接続すると503を返します。
[program lang=’ruby’ escaped=’true’]
# file limit control # # mrubyAccessCheckerMiddle /var/www/html/file_limit_inc_by_redis.rb # mrubyLogTransactionMiddle /var/www/html/file_limit_dec_by_redis.rb redis = Apache::Redis.new("127.0.0.1", 6379) req = Apache::Request.new limit = 1 fcounter = redis.get req.filename if fcounter.nil? or fcounter.to_i < 0 fcounter = 0.to_s end fcounter = (fcounter.to_i + 1).to_s Apache.errlogger(4, "fcounter inc: #{req.filename}: #{fcounter.to_s}") if fcounter.to_i > limit redis.set req.filename, fcounter Apache.return(503) else redis.set req.filename, fcounter end
[/program]
で、このアクセスチェックが問題なく通れば、レスポンスを返してロギングのフェイズで以下のようなカウンターをデクリメントするRubyスクリプトをフックします。
[program lang=’ruby’ escaped=’true’]
# file limit control # # mrubyAccessCheckerMiddle /var/www/html/file_limit_inc_by_redis.rb # mrubyLogTransactionMiddle /var/www/html/file_limit_dec_by_redis.rb redis = Apache::Redis.new("127.0.0.1", 6379) req = Apache::Request.new fcounter = redis.get req.filename if fcounter.nil? or fcounter.to_i <= 0 Apache.errlogger(4, "unexpect error: fcounter is nil or <= 0 at dec phase") redis.set req.filename, fcounter else fcounter = (fcounter.to_i - 1).to_s Apache.errlogger(4, "fcounter dec: #{req.filename}: #{fcounter.to_s}") redis.set req.filename, fcounter end
[/program]
mod_mrubyでApacheにスクリプトを登録
上記のスクリプトをApacheから任意のフェイズでフックさせるために、以下のようにApacheのconfに設定しておきます。
[program lang=’apache’ escaped=’true’]
mrubyAccessCheckerMiddle /var/www/html/file_limit_inc_by_redis.mrb mrubyLogTransactionMiddle /var/www/html/file_limit_dec_by_redis.mrb
[/program]
このように、mod_mrubyとRedisを使えば、このように簡単に上記のようなアクセスコントロールができてしまいますね。Apacheモジュールで実装しようとすると、mutexやら共有メモリやらを駆使して、例えばpreforkだと、複数のプロセス間でカウンターを同期しないといけなくて面倒ですが、mod_mrubyだと休憩がてらできちゃいます。
適当にsleep.rbとかを作って、2同時接続とかするときちんと503を返すでしょう。(Chromeは賢いので、同一ファイルへのアクセスは一つのタブが終わるまで待つので、IE等を使うと良いです。)
エラーログに以下のような出力をするようにしているので、簡単に確認できると思います。
[program lang=’apache’ escaped=’true’]
[Wed Dec 05 18:48:37 2012] [warn] fcounter inc: /var/www/html/sleep.rb: 1 [Wed Dec 05 18:48:39 2012] [warn] fcounter inc: /var/www/html/sleep.rb: 2 [Wed Dec 05 18:48:39 2012] [warn] fcounter dec: /var/www/html/sleep.rb: 1 [Wed Dec 05 18:48:47 2012] [warn] fcounter dec: /var/www/html/sleep.rb: 0
[/program]
これでApacheの内部を弄るのは難しいとは言わせない!mod_mrubyで簡単にApacheをコントロールしてやりましょう!
さて、現実逃避せずに論文書きます…