# Simple RPC client on YAML # # Example usage: # # yamlrpc = YamlRpc.new('http://rubyforge.org/projects/yamlrpc/') # yamlrpc.foo({ # :bar => 'foo-bar' # ) # # will HTTP POST # # { # :bar => YAML::dump('foo-bar') # } # # to http://rubyforge.org/projects/yamlrpc/foo # # On the server side you can use (Ruby on Rails): # # YAML::load(params[:yamlrpc])[:foo] # # or in PHP: # # $data = syck_load($_POST['yamlrpc']); # $data['foo'] # # See examples in /examples directory module YamlRpc class Client DEFAULT_FORM_DATA_ROOT = :yamlrpc DEFAULT_OPEN_TIMEOUT = 10 # 10 seconds DEFAULT_READ_TIMEOUT = 120 # 2 minutes attr_accessor :uri attr_accessor :form_data_root attr_accessor :open_timeout attr_accessor :read_timeout attr_accessor :user attr_accessor :password attr_accessor :permanent_data # * uri Root url, ie. "http://my.com/something/"; YamlRpc#some_method(:foo => 'bar') will POST to http://my.com/something/some_method with { :foo => 'bar' } params # * options # * :form_data_root Default to :yamlrpc, meaning all params are passed inside :yamlrpc form field # * :open_timeout Open timeout # * :read_timeout Read timeout # * :user Basic http auth, user name # * :password Basic http auth, password def initialize(uri, options = {}) @open_timeout = options[:open_timeout] @read_timeout = options[:read_timeout] @user = options[:user] @password = options[:password] @permanent_data = options[:permanent_data] @uri = URI.parse(uri.to_s) @form_data_root = options[:form_data_root] || DEFAULT_FORM_DATA_ROOT end # * uri Method name (mainly) or full uri (to support redirects to other domains) # * args Arguments, nil or Hash of values def method_missing(uri, *args) args = args.first if args.size == 1 && args.first.is_a?(Hash) uri = URI.parse(uri.to_s) if uri.host.nil? uri.host = @uri.host uri.port = @uri.port end uri.path = "#{@uri.path}#{uri.path}" unless uri.path =~ /^\// post(uri, args) end protected # * uri URI instance # * args Optional data to be passed as POST fields, ie: { :status => 'SUCCESS', :message => 'All ok' } def post(uri, args = nil) req = Net::HTTP::Post.new(uri.path) req.set_form_data(args_to_form_data(args) || {}, ';') req.basic_auth @user, @password if @user && @password http = Net::HTTP.new(uri.host, uri.port) http.open_timeout = @open_timeout http.read_timeout = @read_timeout res = http.start { |http| http.request(req) } case res when Net::HTTPSuccess YAML::load(res.body) when Net::HTTPRedirection redirect_uri = URI.parse(res['location']) if redirect_uri.host.nil? redirect_uri.host = uri.host redirect_uri.port = uri.port end post(redirect_uri, args) else res.value # raises error if not kind of success end end # POST form data is always passed in YamlRpc#form_data_root variable name # * args def args_to_form_data(args = nil) args = @permanent_data if args.nil? # HACK: this is hack as hell but logically ok for merging to work for nil args unless @permanent_data.nil? if @permanent_data.respond_to?('merge') args = @permanent_data.merge(args) elsif @permanent_data.respond_to?('+') args = @permanent_data + args else raise ArgumentError.new("Can't merge #{@permanent_data.class} and #{args.class}") end end { @form_data_root.to_sym => YAML::dump(args) } end end end