mod_mrubyとredisを使ってファイル単位のアクセス制御をしてみた

論文の休憩がてら、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をコントロールしてやりましょう!

さて、現実逃避せずに論文書きます…