# 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