2009年7月23日木曜日

[Ruby][MashUp] WebAPI 用 Rubyモジュール

WebAPIのMashUpをしていると、リクエスト&レスポンスの処理を統一して扱うフレームワーク的なものが欲しくなります。RubyでWebAPIを扱うための標準的なライブラリがありそうですが、知らないので簡易モジュールを書いてみました。
# みんなどうやっているのだろう...
# この程度の処理は、ふつうのプログラマならさくっと自分で書いているのでしょうか。

モジュール名は(使い方が正しいのか微妙ですが)、RESTとしています。


require 'uri'
require 'cgi'
require 'open-uri'
require 'rexml/document'
require 'rexml/parsers/streamparser'
require 'rexml/parsers/baseparser'
require 'rss'

module REST
class RESTRequest
attr_reader :base_url

# base_url: WebAPIのリクエストURL
# params: リクエストパラメータ(ハッシュ)
# enc_params: URLエンコードが必要なリクエストパラメータ(ハッシュ)
def initialize(base_url, params, enc_params)
@base_url = base_url # WebAPI base URL
@req_params = Hash.new # request parameters
params.each{ |key, val|
set_param key, val
}
enc_params.each{ |key, val|
set_encoded_param key, val
}
@closed = false
@resp_str = nil
end

# リクエストURL作成
def make_request_url
url = @base_url + "?"
@req_params.each{|key, val|
url.concat "#{key}=#{val}&"
}
url.sub!(/&$/, "")
return url
end

# レスポンス文字列取得
def response_string
# リクエストを投げるのは初回のメソッド呼び出しのみ
if !@closed
begin
io = OpenURI::open_uri make_request_url
if io != nil
str = io.read
io.close
@resp_str = str # レスポンスのキャッシュ
end
rescue => ex
raise RESTRequestError, ex.message
end
@closed = true
end
return @resp_str
end

def set_param(key, val)
@req_params.store key, val
end

def set_encoded_param(key, val)
encoded_val = CGI::escape val
set_param key, encoded_val
end

private :set_param, :set_encoded_param

end

# 例外クラス
class RESTError < StandardError; end

class RESTRequestError < RESTError; end

class RESTResponseParseError < RESTError; end

# XMLレスポンスをパース
# req: RESTRequestオブジェクト
# listener: REXML::StreamListener を include した任意のリスナーオブジェクト
def parse_xml req, listener
xml = req.response_string
if xml != nil
begin
REXML::Parsers::StreamParser.new(xml, listener).parse
rescue => ex
raise RESTResponseParseError, ex.message
end
end
end

# RSSをパース
# req: RESTRequestオブジェクト
def parse_rss req
rss_str = req.response_string
if rss_str != nil
begin
rss = RSS::Parser.parse rss_str, false
return rss
rescue => ex
raise RESTResponseParseError, ex.message
end
end
end

module_function :parse_xml, :parse_rss
end



1つのクラスと2つの関数をもつモジュールです。
  • RESTRequestクラス: リクエストURLの作成とレスポンス文字列の取得を受け持ちます。
  • parse_xml関数: REXML::StreamListener を include した任意のリスナーオブジェクトを使ってレスポンスをパースします。
  • parse_rss関数: RSS::Parser で RSS をパースします。
処理はいたってオーソドックスです(使っているのはOpenURI,REXML::StreamParser, RSS::Parser)。しいて工夫した点を挙げるなら、レスポンス文字列をキャッシュして複数のパーサを適用できるようにしたことぐらい。


使いかた(かんたんバージョン)


Google Blog Search の RSS を取得してパースします。RSSなので parse_rss でほぼOKなのですが、RSSの仕様に含まれない<opensearch:totalResults>もとりたいので、その部分のみ parse_xml でとってきます。

google.rb

require 'rexml/streamlistener'
require 'rest'

include REST

class GoogleBlogResponseListener
include REXML::StreamListener
attr_reader :total_results

def tag_start(name, attrs)
@tag_name = name
end

def text text
if @tag_name == "opensearch:totalResults"
@total_results = text if @total_results == nil
end
end
end

base_url = "http://blogsearch.google.co.jp/blogsearch_feeds"
params = {
"hl" => "ja",
"lr" => "lang_ja",
"ie" => "utf-8",
"output" => "rss"
}
enc_params = {
"q" => "日食"
}
req = RESTRequest.new base_url, params, enc_params

puts req.make_request_url

listener = GoogleBlogResponseListener.new
begin
parse_xml req, listener
rss = parse_rss req

puts "Total Results: #{listener.total_results}"
if rss != nil
rss.items.each{ |item|
puts " - #{item.title} (#{item.dc_date.strftime '%Y/%m/%d-%X'})"
}
end
rescue RESTError => ex
puts "Error: #{ex.message}"
end



実行結果

$ ruby google.rb
http://blogsearch.google.co.jp/blogsearch_feeds?hl=ja&lr=lang_ja&ie=utf-8&output=rss&q=%E6%97%A5%E9%A3%9F
Total Results: 4416456
- 皆既<b>日食</b>: 星的考察。。。 (2009/07/23-19:01:43)
- 西内まりや Official Voice&amp;Movie Blog:皆既<b>日食</b>!! (2009/07/22-21:33:45)
- 大萩康司のココ最近。 powered by ココログ: 皆既<b>日食</b> (2009/07/23-16:46:54)
- ねこの笹舟 <b>日食</b>! (2009/07/23-10:21:51)
- 130i を中心としたオハナシ : <b>日食</b>!! (2009/07/23-21:15:04)
- <b>日食</b>!:沖縄古着スクラップ [ロング ライフ プロジェクト] (2009/07/23-11:42:38)
- 皆既<b>日食</b>!|安田美沙子オフィシャルブログ「MICHAEL(ミチャエル <b>...</b> (2009/07/23-19:50:36)
- nana cafe : 皆既<b>日食</b> (2009/07/23-18:15:27)
- <b>日食</b>ゴーグル。|江戸むらさき 野村オフィシャルブログ『Men&#39;s クラブ <b>...</b> (2009/07/23-02:11:34)
- おおなかこなか: #2238 <b>日食</b>帯世界地図 (2009/07/23-06:36:04)



使いかた(モジュール組み込みバージョン)

上のコードをもう少しきれいに書きます。
具体的にはリクエストURLなどを隠蔽して、Google Blog Search専用の拡張モジュールを作成します。


require 'rexml/streamlistener'
require 'rest'

module GoogleBlogSearch
# Google Blog Searchのリクエストを隠蔽するクラス
class GoogleBlogRequest < REST::RESTRequest
BASE_URL = "http://blogsearch.google.co.jp/blogsearch_feeds"

def initialize query
params = {
"hl" => "ja",
"lr" => "lang_ja",
"ie" => "utf-8",
"output" => "rss"
}
enc_params = { "q" => query }
super BASE_URL, params, enc_params
end
end

class GoogleBlogResponseListener
include REXML::StreamListener
attr_reader :total_results

def tag_start(name, attrs)
@tag_name = name
end

def text text
if @tag_name == "opensearch:totalResults"
@total_results = text if @total_results == nil
end
end
end

# ブログ検索クラス
class BlogSearch
include REST # RESTモジュールを include

attr_reader :rss

def initialize query
@req = GoogleBlogRequest.new query
@listener = GoogleBlogResponseListener.new
end

def search
parse_xml @req, @listener
@rss = parse_rss @req
end

def total_results
@listener.total_results
end
end
end

# GoogleBlogSearch モジュールを使う
s = GoogleBlogSearch::BlogSearch.new "日食"
begin
s.search
puts "Total Results: #{s.total_results}"
s.rss.items.each{ |item|
puts " - #{item.title} (#{item.dc_date.strftime '%Y/%m/%d-%X'})"
}
rescue REST::RESTError => ex
puts "RESTError: #{ex.message}"
rescue => ex
puts "Error: #{ex.message}"
end


実行結果は上と同じになります。

0 件のコメント:

コメントを投稿