公開Web APIをプログラムから叩きたい。
けどうっかり「連打」してしまって(要するにsleep入れずにループまわしてリクエスト連投)…
サービス提供者に迷惑をかけたらどうしよう…
この種のプログラムのテストは少しだけ不安があります。
同じリクエストを繰り返し送ることもあり得るので、結果のキャッシュもしたい。
こういった理由で、リクエスト頻度を抑え、結果をKVSなどでキャッシュしつつAPIを叩くプログラムを自作しようとしたらすでに同様なことを実現するRuby Gemがありました。
APICache(api_cache)です。
READMEを読んでこれは使えそうだと思い、簡単なプログラムを書いてみました。動作はしましたが、実はかなり使いにくいものであることもわかりました。
APICacheサンプルプログラム
localhost:9292
というダミーのWebサービスにAPICacheでアクセスしてみます。このWebサービスはGETするたびランダムな文字列を返すだけの役に立たないものです。
require 'api_cache'
require 'moneta'
APICache.store = Moneta.new(:Daybreak, file: "db")
def get_random_id
# 以下で、 cache < period とする意味はないが、
# period の効果を確認するためわざとそうしている。
option = {
:cache => 5, # 5秒経ったらAPI叩いて新しいデータを取りにいく。
:valid => :forever, # キャッシュの有効期限は無期限とする。
:period => 10, # APIは10秒以上間隔を空ける。
:timeout => 5, # 5秒でタイムアウト。
:fail => "Fail Whale" # 例外発生時に返される値。
}
APICache::get('http://localhost:9292', option)
end
puts get_random_id
sleep 3
puts get_random_id # 前回と同じ文字列を得るはず。
sleep 3
puts get_random_id # APIを叩くが10秒経過していないのでエラーとなる。
sleep 5
puts get_random_id # 新しい文字列を得る。
- テスト段階で本物のWebサービスを使いたくないので、ここでは
http://localhost:9292
という ローカルで走るダミーサービスを叩いています。 - キャッシュ層の指定は
APICache.store
に対して行います。ここではDaybreakバックエンドのMonetaインスタンスを与えています。db
という名前でキャッシュファイルが生成されます。 - APICache::getは実際にリクエストを送るか、期限切れでないキャッシュがあればそれを返します。
ランダムな文字列を生成するダミーサービス
rackupで手っ取り早く適当なサーバを作ります。
# Example:
# $ rackup -s webrick -p 9292 random_id.ru
require 'rack/response'
class RandomID
def call(env)
Rack::Response.new.finish do |res|
res.write ('a'..'z').to_a.shuffle[0, 8].join
end
end
end
run RandomID.new
rackup -s webrick -p 9292 random_id.ru
などとして起動します。
実行結果
上記ダミーサーバを起動した上で、サンプルプログラムを動作させます:
$ ruby api_cache_hello.rb
sarljvch
sarljvch
I, [2013-10-22T22:33:24.738347 #18162] INFO -- : APICache http://localhost:9292: Exception raised (Cannot fetch http://localhost:9292: queried too recently - APICache::CannotFetch)
sarljvch
nbixczgt
3回目はリクエスト間隔が短すぎるというエラーログが出ています。この場合もキャッシュされた結果が得られます。
キャッシュの中身を見てみます:
$ ruby -rdaybreak -e 'Daybreak::DB.new("db").tap{|db| db.each{|k,v| p k; p v;}; db.close}'
"http://localhost:9292_queried_at"
2013-10-22 22:33:29 +0900
"http://localhost:9292_queried_at_created_at"
2013-10-22 22:33:29 +0900
"f1bb1c78a060f52be2480ca45091338e"
"nbixczgt"
"f1bb1c78a060f52be2480ca45091338e_created_at"
2013-10-22 22:33:29 +0900
クエリ結果をキャッシュするとともにその時刻を記録していることがわかります。これをもとにキャッシュの有効性およびリクエスト発行可否を判断しているのでしょう。
問題点
キャッシュはAPICache::get
に渡したキーごとに作られます。この点は問題ないのですが、ソースを読んでいると、リクエスト時刻もキーごとに記録しているようです。そのため、URLのパラメータだけ変更してリクエストする場合、それは新規のリクエストと扱われ、直ちにサーバへのアクセスが発生してしまうのでは…。毎回同じURLから「新着情報」を確認するという場合にしか使えない気がします。
補足
APICacheでキャッシュできるのはHTTPリクエストだけではありません。APICache::getはブロックを取ることもでき、ブロックの実行結果がキャッシュされます。この場合getの第1引数がキャッシュのキーになります。
詳しくはGithubで確認できます。