#WebAPIのCLIクライアントの試作
###最初に
すいません、requireではありません。
今日は、コマンドラインからWebAPIを利用するためのCLIクライアントを作成しようかと思います。
作成するアプリでは、mruby Advent Calendarで @kaishuu0123さんが作成してくださったmruby-readlineの他、多く方々が作成されたgemを利用させていただいています。mruby/mrubyデフォルトのgemに加えて、下記のgemを利用しています。
作者の方々には、この場を借りてお礼申し上げます。
conf.gem :github => 'kaishuu0123/mruby-readline', :branch => 'master'
conf.gem :git => 'https://github.com/iij/mruby-regexp-pcre.git'
conf.gem :git => 'https://github.com/iij/mruby-io.git'
conf.gem :git => 'https://github.com/iij/mruby-socket.git'
conf.gem :git => 'https://github.com/iij/mruby-mtest.git'
conf.gem :git => 'https://github.com/luisbebop/mruby-polarssl.git'
conf.gem :git => 'https://github.com/matsumoto-r/mruby-simplehttp.git'
###やりたいこと
お題ですが、下記の通りです。独自シェル(っぽい)のWebAPIクライアントを試作してみます。
- コマンドラインからWebAPIを叩けるアプリを作る
- シェルっぽい感じのUIを提供
- 入力の補完が出来たら嬉しい (readline任せ
###WebAPIを叩いてみる
WebAPIのCLIクライアントを作成するのに先立って、まずはhttp経由で何かしらのWebAPIを叩いてみます。
今回はYahoo! JAPANさんにて公開されているWebAPIを利用します。
仕様を調べるために http://developer.yahoo.co.jp/ を見ます。眺めます。
利用するためにはappidが必要とのことなので、取得しておきます。
APIによってはPOSTメソッドにも対応しているものがありますが、ひとまず対象はGETメソッドのみとします。
とりあえず、クエリストリングを作成するメソッドだけ実装したクラスを用意しておきます。
appidをベタ書きするのはアレなのでスクリプト起動時の引数として渡すことにします。
$appid = ARGV[0]
class YahooAPIBase
USER_AGENT = "webapi_test"
def build_querystring(params)
query = []
params.each do |k,v|
query << "#{k}=#{v}"
end
query << "output=json"
"?"+query.join("&")
end
end
次に、実際に叩いてみるAPIを決めます。千葉県の天気が気になるので幕張メッセ付近の緯度経度を指定して気象情報APIを叩いてみます。
class WeatherAPI < YahooAPIBase
def exec(req_params = { "coordinates" => "35.647306,140.034503"})
protocol = "http"
domain = "weather.olp.yahooapis.jp"
endpoint = "/v1/place"
port = "80"
method = "GET"
req_params.merge!({ "appid" => $appid })
query = build_querystring(req_params)
SimpleHttp.new(protocol, domain, port).request(method, endpoint+query, {'User-Agent' => "#{USER_AGENT}"}).body
end
end
このクラスのインスタンスを生成し、実行すると以下のようなレスポンスが得られます。
{"ResultInfo":{"Count":1,"Total":1,"Start":1,"Status":200,"Latency":0.007859,"Description":"","Copyright":"(C) Yahoo Japan Corporation."},"Feature":[{"Id":"201312140225_35.647306_140.0345","Name":"地点(35.647306,140.0345)の2013年12月14日 02時25分から60分間の天気情報","Geometry":{"Type":"point","Coordinates":"35.647306,140.0345"},"Property":{"WeatherList":{"Weather":[{"Type":"observation","Date":"201312140225"},{"Type":"forecast","Date":"201312140235"},{"Type":"forecast","Date":"201312140245"},{"Type":"forecast","Date":"201312140255"},{"Type":"forecast","Date":"201312140305"},{"Type":"forecast","Date":"201312140315"},{"Type":"forecast","Date":"201312140325"}]}}}]}
...と、本来はレスポンスに降水量の情報が入っていることを期待したのですが、入っていません。きっと雨は降らないのでしょう。
リクエストが間違っているような気もしますが、とりあえず今回はレスポンスが得られることが確認できれば良いので、このまま進めます。
###CLIを作る
次に入力受け付けるコマンドラインインターフェースを提供するクラスを作ります。
class Cli
def initialize
@cli_commands = {}
end
def add_command(command)
@cli_commands.merge!(command)
end
def run
comp = proc { |s| @cli_commands.keys.grep( /^#{s}/ ) }
Readline.completion_append_character = " "
Readline.completion_proc = comp
while command = Readline.readline('hogegeshell$ ', true)
toplevel_command = command.split[0]
if @cli_commands[toplevel_command]
@cli_commands[toplevel_command].call command
else
puts "#{toplevel_command} is not defined."
end
end
end
end
コマンドの登録ははkeyがString、valueがProcのHashを引数としてadd_commandで登録します。
valueのProcオブジェクトが実際に実行される処理となります。
cli = Cli.new
cli.add_command({
"weather" => proc { |s|
api = WeatherAPI.new
puts api.exec
}
})
cli.run
実際に実行すると下記のようになります。
$ bin/mruby cli.rb ${APPID}
hogegeshell$ weather
{"ResultInfo":{"Count":1,"Total":1,"Start":1,"Status":200,"Latency":0.015032,"Description":"","Copyright":"(C) Yahoo Japan Corporation."},"Feature":[{"Id":"201312140240_35.647306_140.0345","Name":"地点(35.647306,140.0345)の2013年12月14日 02時40分から120分間の天気情報","Geometry":{"Type":"point","Coordinates":"35.647306,140.0345"},"Property":{"WeatherList":{"Weather":[{"Type":"observation","Date":"201312140240"},{"Type":"observation","Date":"201312140250"},{"Type":"observation","Date":"201312140300"},{"Type":"observation","Date":"201312140310"},{"Type":"observation","Date":"201312140320"},{"Type":"observation","Date":"201312140330"},{"Type":"observation","Date":"201312140340"},{"Type":"forecast","Date":"201312140350"},{"Type":"forecast","Date":"201312140400"},{"Type":"forecast","Date":"201312140410"},{"Type":"forecast","Date":"201312140420"},{"Type":"forecast","Date":"201312140430"},{"Type":"forecast","Date":"201312140440"}]}}}]}
hogegeshell$
と、ここまででひとまずはコマンドの入力、WebAPIの実行、結果の取得が出来るようになりました。
全体のコードはgistに貼ってあります。
###やったこと
- WebAPIをCLIから叩けるスクリプトを作成した
- gemを組み合わせてシェルっぽいものを作成できることを確認した
課題
- gemにする
- WebAPIのクライアントとCLI部分を分け、もっと汎用的なものにした上でgemにしたい
- 補完機能を改善する
- サブコマンドや引数についても補完の対象にしたい
- JSONのレスポンスをよしなに処理できるようにしたい
終わりに
readlineを利用すれば独自のシェルを比較的容易に実装できそうです。
真面目に作り込むと補完機能等で詰まることもありそうですが、現状でも簡易な独自シェルを作成する分には十分機能するものが作れると思います。