セッションの開始とノード情報の取得
Adobe Share APIにアクセスして、認証→セッションを開始→ノード情報を取得するところまで作った。
呼び出し例。
# クライアントを作る cl = Share::Client.new( <APIキー>, <shared secret>, <ユーザーID>, <パスワード> ) # セッションを開始 cl.session { |session| # ルートフォルダの情報を得る。 info = session.get_node_info # ルートフォルダのid puts info[:node][:nodeid] # 子要素の情報表示 info[:children].each { |item| puts item[:name] + " : " + item[:nodeid] } }
クライアントライブラリ(share-client.rb)
require 'http-requestor' require 'digest/md5' require "rexml/document" # Adobe Share クライアント module Share class Base def initialize( api_key, shared_secret ) @api_key = api_key @shared_secret = shared_secret end # 認証ヘッダを挿入する。 def set_authorization_header( req, shared_secret, session_id=nil ) h = req["headers"] data = req["method"] + " " + "https://" + req["host"] + req["path"] + " " + (Time.new.to_f * 1000).round.to_s auth = "AdobeAuth " auth << "sessionid=\"" << session_id << "\"," if session_id != nil auth << "apikey=\"" << @api_key << "\"," auth << "data=\"" << data << "\"," auth << "sig=\"" << Digest::MD5.hexdigest( data + shared_secret ) << "\"" h["Authorization"] = auth end # レスポンスを解析する def parse_response( body ) raise body unless body =~ /^\s*<response/ doc = REXML::Document.new(body) res = { :status => doc.attributes["status"], :property => {} } doc.elements.each("./response/*") { |item| res[:property][item.name.to_sym] = item.text } return res end end class Client < Base def initialize( api_key, shared_secret, user_name, password ) super( api_key, shared_secret ) @user_name = user_name @password = password @hr = HttpRequestor.new( File.dirname(__FILE__) + "/share-requests.yaml") @authtoken = login # ログイン end # セッションを開始する def session( &block ) # セッションを開始 res = @hr.new_session() { |req| set_authorization_header( req, @shared_secret ) req["body"] =<<-POST <request> <authtoken>#{@authtoken}</authtoken> </request> POST req["body"].gsub!( /\n/, "" ) # 改行を取る。 } res = parse_response( res.body ) session_id = res[:property][:sessionid] secret = res[:property][:secret] begin session = Session.new( @api_key, secret, session_id, @hr ) block.call( session ) if block_given? ensure # セッション終了 if ( session_id != nil ) @hr.end_session() { |req| req["path"] = req["path"] + session_id + "/" set_authorization_header( req, secret, session_id ) req["path"] = req["path"] + "?method=DELETE" } end end end private # ログインする。 def login() res = @hr.auth() { |req| set_authorization_header( req, @shared_secret ) req["body"] =<<-POST <request> <username>#{@user_name}</username> <password>#{@password}</password> </request> POST req["body"].gsub!( /\n/, "" ) # 改行を取る。 } res = parse_response( res.body ) return res[:property][:authtoken] end end class Session < Base def initialize( api_key, shared_secret, session_id, hr ) super( api_key, shared_secret ) @hr = hr @session_id = session_id end # フォルダの情報を取得する。 # @param node_id 情報を取得するノードのID。nilの場合ルートノードの情報を返す def get_node_info( node_id=nil ) res = @hr.get_node_info() { |req| req["path"] = req["path"] + node_id + "/" if node_id != nil set_authorization_header( req, @shared_secret, @session_id ) } return parse_get_nodeinfo_response( res.body ) end private # ノード情報取得のレスポンスを解析する def parse_get_nodeinfo_response( body ) raise body unless body =~ /^\s*<response/ doc = REXML::Document.new(body) res = { :status => doc.attributes["status"], :node => {}, :children=> [] } doc.elements.each("./response/node") { |item| item.attributes.each { |k, v| res[:node][k.to_sym] = v } } doc.elements.each("./response/children/node") { |item| attrs = {} item.attributes.each { |k, v| attrs[k.to_sym] = v } res[:children] << attrs } return res end end end
HTTPアクセスユーティリティもちょっと変更。
require "net/http" require "net/https" require 'yaml' require 'cgi' class HttpRequestor # 初期化する。 def initialize( request_file="./request.yaml" ) File.open( request_file ) {|f| @request = YAML.load( f ) } end # メソッドが定義されていない場合に呼ばれる。 def method_missing( name, *args, &block ) return do_request( name.to_s, &block ) end # リクエストを実行する。 # ブロックが与えられた場合は、ブロックにリクエスト情報を渡し編集する。 def do_request ( method, &block ) fail "unknown method " << method unless @request["requests"].key?( method ) request = merge( {"params"=>{}, "headers"=>{}}, @request["requests"][method] ) request = merge( request, @request["default"] ) block.call( request ) if block_given? response = nil Net::HTTP.version_1_2 requestor = nil if ( @request["conf"] != nil && @request["conf"]["proxy_host"] != nil && @request["conf"]["proxy_port"] != nil ) requestor = Net::HTTP::Proxy( @request["conf"]["proxy_host"], @request["conf"]["proxy_port"] ).new( request["host"], request["port"] ) else requestor = Net::HTTP.new( request["host"], request["port"] ) end # for ssl if ( request["ssl"] ) requestor.use_ssl = true requestor.ca_file = request["ca_file"] requestor.verify_mode = OpenSSL::SSL::VERIFY_PEER requestor.verify_depth = 5 end requestor.start { |http| param = map_to_param( request["params"] ) param += "&" + list_to_param(request["param-list"]) if request["method"] == "GET" request["path"] = request["path"] + "?" + param if param.length > 1 response = http.get( request["path"], request["headers"] ) elsif request["method"] == "POST" # POSTの場合、bodyの指定があればそちらを優先する。 param = request["body"] != nil ? request["body"] : param response = http.post( request["path"], param, request["headers"] ) end } return response end private def map_to_param( map ) return "" if map == nil str = "" map.each { | key, value | str << key << '=' << CGI.escape(value.to_s) << '&' if value != nil } return str end def list_to_param( list ) return "" if list == nil str = "" list.each { | item | if item["key"] != nil && item["value"] != nil str << item["key"] << '=' << CGI.escape(item["value"].to_s) << '&' end } return str end # リクエストの設定をマージする。 def merge (request, parent) return request if ( parent == nil ) request.merge!(parent) { |key, self_val, other_val| if ( key == "headers" || key == "params" ) merge(self_val != nil ? self_val : {}, other_val) else self_val != nil ? self_val : other_val end } return request end end
HTTPアクセスユーティリティの接続設定ファイル
--- conf: #proxy_host: <プロキシホスト> #proxy_port: <プロキシポート> default: host: api.share.adobe.com path: /webservices/api/v1/ port: 443 ssl: true ca_file: "./v.cer" # サーバーの公開鍵証明書 headers: requests: auth: path: /webservices/api/v1/auth/ method: POST new_session: path: /webservices/api/v1/sessions/ method: POST end_session: path: /webservices/api/v1/auth/sessions/ method: POST get_node_info: path: /webservices/api/v1/dc/ method: GET
修正(2007-10-09)
かとうさんからのご指摘を受けて、認証ヘッダ作成時のタイムスタンプ作成部分を修正。ミリ秒でなく秒になっていた・・。
# ○ req["method"] + " " + "https://" + req["host"] + req["path"] + " " + (Time.new.to_f * 1000).round.to_s # × req["method"] + " " + "https://" + req["host"] + req["path"] + " " + Time.new.to_i.to_s