オープンではないサービスのAPIを使うとき、APIクライアントが無いってことが多いです。なのでアプリケーションから使いたいAPIだけを叩くような簡易なAPIクライアントを自分で書きます。そのときはrest-clientあたりを使ってベタっと書いてしまうことがほとんどです。
rest-client
まずはサクッと
例をいきなり書いてしまうと↓な感じでやります。
class ApiClient
ENDPOINT = 'https://api.example.com/items.json'.freeze
class << self
def items(search)
url = ENDPOINT
params = {s: search}
get(url, params)
end
def item
# ...
end
private
def get(url, params)
res = RestClient.get(url, params: params)
return nil if res.code != 200
JSON.parse(res.body)
end
end
end
# こんな感じで使う
ApiClient.items('けんさく') => [...]
はい、これだけ。
レスポンスを工夫する
なんですが、レスポンスをJSON.parse
したHashそのままというのはなんだかイケてないので、rest-clietを使っているAPIクライアントライブラリはどうしているのか、みたいなことを調べました。いい感じの実装を発見したので、それをインスパイアしてみました。
まず、Hashの扱いが少し変わります。
def items(search)
url = ENDPOINT
params = {s: search}
get(url, params)
return [] if data.nil?
data['items'].map do |item|
ItemResponse.new(item)
end
end
こうするとApiClient.items('xxx')
の結果はItemResponse
の配列となります。
で、このItemResponse
とは何者かといいますと、レスポンスのHashをJSのオブジェクトっぽいものに変換する(ドットでアクセスできるようにする)ためのクラスです。
hash = {hoge: 'hogehoge', fuga: 'fugafuga', hash: { key1: 'value1', key2: 'value2'}}
obj = ApiClient::Response.new(hash)
obj.hoge => 'hogehoge'
obj.fuga => 'fugafuga'
obj.hash => {:key1=>"value1", :key2=>"value2"}
実装が半端なので入れ子のHashはメソッドでアクセスできるようになっていませんが、僕のケースはこれで事足りたのでそれ以上はがんばりませんでした。
ちなみに、このResponseクラスの実装はpayjp-ruby
のこの辺のコードを参考にしています(実際には参考にしたのはしばらく前なので、そのときから更に実装が変わっているみたい。Hashのネストも対応してそう)。
Response
とそれを継承したItemResponse
というのをApiClient
内に用意する形にしました。
class ApiClient
# (略)
class Response
def initialize(hash)
@values = {}
instance_eval do
add_accessors(hash.keys)
end
hash.each do |k, v|
@values[k] = v
end
end
private
def add_accessors(keys)
metaclass.instance_eval do
keys.each do |k|
k_eq = :"#{k}="
define_method(k) { @values[k] }
define_method(k_eq) do |v|
if v == ""
raise ArgumentError.new(
"You cannot set #{k} to an empty string." \
"We interpret empty strings as nil in requests." \
"You may set #{self}.#{k} = nil to delete the property.")
end
@values[k] = v
end
end
end
end
def metaclass
class << self; self; end
end
end
class ItemResponse < Response
# 必要であればメソッド生やす
def my_method
end
end
end
プロキシサーバーを経由する
冒頭に「オープンではないサービスのAPI」と書きました。そういう場合って、特定のIPアドレスからしかアクセスできませんみたい制約があったりするので、開発時には工夫が必要です。
このクライアントを実装したときには、プロキシサーバーのIPを許可してもらい、クライアントはプロキシサーバー経由でAPIリクエストを投げるようにしました。
rest-clientの場合のプロキシ設定は非常に簡単で、
RestClient.proxy = "https://accountname:password@54.xxx.yyy.zzz"
としておくだけでOK。Railsならinitializerで書いておくのが良いです。