mod_mruby でファイルダウンロード数を記録する (更新)

※ 2014-4-7 追記しました。

ModSecurity 2.5 という本に mod_security で特定のファイルが何回ダウンロードされたか記録するトリックが載ってました。

その時はおっ、凄い!と思ったけど、そんなことに ModSecurity を使わなくても、今の僕らには mod_mruby があるじゃない!ということでやってみました。mruby-sqlite3 も使います。

# count_download.rb - count file download
#
# You have to build mod_mruby with mruby-sqlite3.
#
# Run this script with mruby and chown the created DB file to apache user.
# After that, add the following line to httpd.conf:
#   SetOutputFilter   mruby
#   mrubyOutputFilter /var/www/count_download.rb

DB = "/tmp/download_count.sqlite3"
# Count downloads of files with these extensions
EXTS = %w(.zip .pdf)

@db = SQLite3::Database.new(DB)

def create_table
  begin
    @db.execute_batch "DROP TABLE download"
  rescue => e
    puts "Failed to DROP!"
    puts e.to_s
  end
  begin
    @db.execute_batch "CREATE TABLE download \
        (uri TEXT PRIMARY KEY, count INTEGER)"
  rescue => e
    puts "Failed to CREATE TABLE! Die!"
    raise e
  end
end

def increment(uri)
  # http://stackoverflow.com/questions/19337029/insert-if-not-exists-statement-in
-sqlite
  @db.execute_batch "INSERT INTO download(uri, count) SELECT ?, 0 \
      WHERE NOT EXISTS(SELECT 1 FROM download WHERE uri LIKE ?)", uri, uri
  @db.execute_batch "UPDATE download SET count = count + 1 WHERE uri LIKE ?", uri
end
  
if $0 == __FILE__
  print "Initialize #{DB}? (Y/n) "
  case gets.chomp.downcase
  when "", "y"
    create_table
    puts "Successful!"
  else
    puts "Nothing done."
  end
else
  r = Apache::Request.new
  if r.method_number == Apache::M_GET and
      EXTS.include? r.uri[-4..-1] and r.status == 200
    increment r.uri
  end
end

@db.close

こんな感じで sqlite3 ファイルに記録されます。

# echo 'select * from download;' | sqlite3 /tmp/download_count.sqlite3
/hoge.zip|2
/eicar.zip|1

上のスクリプトを使うには、まず mod_mruby に mruby-sqlite3 を組み込まないといけません。mod_mruby の build_config.rb に下の行を追加してビルドし直します:

  conf.gem :git => 'git://github.com/mattn/mruby-sqlite3.git'

それから出来上がった mruby (mod_mruby のソースディレクトリの mruby/bin/mruby) で上のスクリプトを実行します。すると /tmp/download_count.sqlite3 というファイルができるので、apache から書き込めるように権限を直します (chown apache:apache /tmp/download_count.sqlite3 とか)。

あとは httpd.conf に SetOutputFilter と mrubyOutputFilter ディレクティブを書いて (スクリプトのコメント参照) apache リスタート。これでようやくダウンロード数が記録できます。

便利だね!

便利だね、って記録したダウンロードカウントを活用する方法を書いてませんでした。
ダウンロードリンクを表示するページにダウンロード数も表示するようにしてみましょう。
まず、ダウンロード数を text/plain で表示する Apache ハンドラか CGI を用意します。ここでは mod_mruby を使ってハンドラを作りました。

# Add following lines to your httpd.conf:
# <Location /download_count>
#   mrubyHandlerMiddle /var/www/download_count.rb
# </Location>
#
# Access  /download_count?uri=foo.zip  and you'll get an integer value.
  
DB = "/tmp/download_count.sqlite3"

class Hash     
  def self.[](*kvs)
    res = {}   
    0.step kvs.length-1, 2 do |i|
      res[kvs[i]] = kvs[i+1]
    end
    res
  end
end     
        
r = Apache::Request.new
r.content_type = "text/plain"
result = 0

if r.method_number == Apache::M_GET
  args = Hash[*r.args.to_s.split(/[&;]/).map{|kv| kv.split('=')}.flatten]
  @db = SQLite3::Database.new(DB)
  begin
    @db.execute("SELECT count FROM download WHERE uri LIKE ?",
                args['uri']) do |row, fields|
      result = row[0]
    end
  rescue
    # do nothing
  end
  @db.close
end

Apache.rputs result

このスクリプトのコメントにある通りに Apache を設定してから /download_count?uri=/hoge.zip などという uri をリクエストするとダウンロード数が表示されます。なので、ダウンロード数を表示したいページに SSI でこの URI を埋め込めば OK です。

ダウンロードページの例です:

<!doctype html>
<!--#config errmsg="[値が取得できません…。]" -->
<html>
  <head>
    <title>ダウンロード</title>
    <style type="text/css">
      dt       { clear: both; width: 8em }
      dt, dd   { float: left }
      dd       { margin-left: 0 }
      em.count { font-weight: bold; font-style: normal }
    </style>
  </head>
  <body>
    <dl>
      <dt>
        <a href="/hoge.zip">hoge.zip</a>
      </dt>
      <dd>
      ダウンロード数: <em class="count"><!--#include virtual="/download_count?uri=/hoge.zip" --></em>
      </dd>
      <dt>
        <a href="/eicar.zip">eicar.zip</a>
      </dt>
      <dd>
      ダウンロード数: <em class="count"><!--#include virtual="/download_count?uri=/eicar.zip" --></em>
      </dd>
    </dl>
    <div style="clear: both"></div>
  </body>
</html>

Web ブラウザでアクセスするとこんな風に見えます。
mod_mruby dl counter

(コウヅ)

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中