123456789_123456789_123456789_123456789_123456789_

Class: XMLRPC::Client

Relationships & Source Files
Namespace Children
Classes:
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
Inherits: Object
Defined in: lib/xmlrpc/client.rb

Overview

Provides remote procedure calls to a XML-RPC server.

After setting the connection-parameters with .new which creates a new Client instance, you can execute a remote procedure by sending the #call or #call2 message to this new instance.

The given parameters indicate which method to call on the remote-side and of course the parameters for the remote procedure.

require "xmlrpc/client"

server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
begin
  param = server.call("michael.add", 4, 5)
  puts "4 + 5 = #{param}"
rescue XMLRPC::FaultException => e
  puts "Error:"
  puts e.faultCode
  puts e.faultString
end

or

require "xmlrpc/client"

server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
ok, param = server.call2("michael.add", 4, 5)
if ok then
  puts "4 + 5 = #{param}"
else
  puts "Error:"
  puts param.faultCode
  puts param.faultString
end

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

ParseContentType - Included

ParserWriterChooseMixin - Included

#set_parser

Sets the XMLParser to use for parsing XML documents.

#set_writer

Sets the XMLWriter to use for generating XML output.

#create, #parser

Constructor Details

.new(host = nil, path = nil, port = nil, proxy_host = nil, proxy_port = nil, user = nil, password = nil, use_ssl = nil, timeout = nil) ⇒ Client

Creates an object which represents the remote XML-RPC server on the given host. If the server is CGI-based, path is the path to the CGI-script, which will be called, otherwise (in the case of a standalone server) path should be "/RPC2". port is the port on which the XML-RPC server listens.

If proxy_host is given, then a proxy server listening at proxy_host is used. proxy_port is the port of the proxy server.

Default values for host, path and port are ‘localhost’, ‘/RPC2’ and ‘80’ respectively using SSL ‘443’.

If #user and #password are given, each time a request is sent, an Authorization header is sent. Currently only Basic Authentication is implemented, no Digest.

If use_ssl is set to true, communication over SSL is enabled.

Parameter #timeout is the time to wait for a XML-RPC response, defaults to 30.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 82

def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil,
               user=nil, password=nil, use_ssl=nil, timeout=nil)

  @http_header_extra = nil
  @http_last_response = nil
  @cookie = nil

  @host       = host || "localhost"
  @path       = path || "/RPC2"
  @proxy_host = proxy_host
  @proxy_port = proxy_port
  @proxy_host ||= 'localhost' if @proxy_port != nil
  @proxy_port ||= 8080 if @proxy_host != nil
  @use_ssl    = use_ssl || false
  @timeout    = timeout || 30

  if use_ssl
    require "net/https"
    @port = port || 443
  else
    @port = port || 80
  end

  @user, @password = user, password

  set_auth

  # convert ports to integers
  @port = @port.to_i if @port != nil
  @proxy_port = @proxy_port.to_i if @proxy_port != nil

  # HTTP object for synchronous calls
  @http = net_http(@host, @port, @proxy_host, @proxy_port)
  @http.use_ssl = @use_ssl if @use_ssl
  @http.read_timeout = @timeout
  @http.open_timeout = @timeout

  @parser = nil
  @create = nil
end

Class Method Details

.new2(uri, proxy = nil, timeout = nil) Also known as: .new_from_uri

Creates an object which represents the remote XML-RPC server at the given uri. The URI should have a host, port, path, user and password. Example: user:password@host:port/path

Raises an ArgumentError if the uri is invalid, or if the protocol isn’t http or https.

If a #proxy is given it should be in the form of “host:port”.

The optional #timeout defaults to 30 seconds.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 136

def new2(uri, proxy=nil, timeout=nil)
  begin
    url = URI(uri)
  rescue URI::InvalidURIError => e
    raise ArgumentError, e.message, e.backtrace
  end

  unless URI::HTTP === url
    raise ArgumentError, "Wrong protocol specified. Only http or https allowed!"
  end

  proto  = url.scheme
  user   = url.user
  passwd = url.password
  host   = url.host
  port   = url.port
  path   = url.path.empty? ? nil : url.request_uri

  proxy_host, proxy_port = (proxy || "").split(":")
  proxy_port = proxy_port.to_i if proxy_port

  self.new(host, path, port, proxy_host, proxy_port, user, passwd, (proto == "https"), timeout)
end

.new3(hash = {}) Also known as: .new_from_hash

Receives a Hash and calls .new with the corresponding values.

The hash parameter has following case-insensitive keys:

  • host

  • path

  • port

  • proxy_host

  • proxy_port

  • user

  • password

  • use_ssl

  • timeout

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 175

def new3(hash={})

  # convert all keys into lowercase strings
  h = {}
  hash.each { |k,v| h[k.to_s.downcase] = v }

  self.new(h['host'], h['path'], h['port'], h['proxy_host'], h['proxy_port'], h['user'], h['password'],
           h['use_ssl'], h['timeout'])
end

.new_from_hash(hash = {})

Alias for .new3.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 185

alias new_from_hash new3

.new_from_uri(uri, proxy = nil, timeout = nil)

Alias for .new2.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 160

alias new_from_uri new2

Instance Attribute Details

#http (readonly)

Returns the Net::HTTP object for the client. If you want to change HTTP client options except header, cookie, timeout, user and password, use Net::HTTP directly.

Since 2.1.0.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 195

attr_reader :http

#http_header_extra (rw)

Add additional HTTP headers to the request

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 198

attr_accessor :http_header_extra

#http_last_response (readonly)

Returns the Net::HTTPResponse object of the last RPC.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 201

attr_reader :http_last_response

#password (rw)

Return the corresponding attributes.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 208

attr_reader :timeout, :user, :password

#password=(new_password) (rw)

Changes the password for the Basic Authentication header to new_password

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 226

def password=(new_password)
  @password = new_password
  set_auth
end

#timeout (rw)

Return the corresponding attributes.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 208

attr_reader :timeout, :user, :password

#timeout=(new_timeout) (rw)

Sets the Net::HTTP#read_timeout and Net::HTTP#open_timeout to new_timeout

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 212

def timeout=(new_timeout)
  @timeout = new_timeout
  @http.read_timeout = @timeout
  @http.open_timeout = @timeout
end

#user (rw)

Return the corresponding attributes.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 208

attr_reader :timeout, :user, :password

#user=(new_user) (rw)

Changes the user for the Basic Authentication header to new_user

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 219

def user=(new_user)
  @user = new_user
  set_auth
end

Instance Method Details

#call(method, *args)

Invokes the method named method with the parameters given by args on the XML-RPC server.

The method parameter is converted into a String and should be a valid XML-RPC method-name.

Each parameter of args must be of one of the following types, where Hash, Struct and Array can contain any of these listed types:

  • Integer

  • TrueClass, FalseClass, true, false

  • String, Symbol

  • Float

  • Hash, Struct

  • Array

  • Date, Time, DateTime

  • Base64

  • A Ruby object which class includes Marshallable (only if Config::ENABLE_MARSHALLING is true). That object is converted into a hash, with one additional key/value pair _class___ which contains the class name for restoring that object later.

The method returns the return-value from the Remote Procedure Call.

The type of the return-value is one of the types shown above.

An Integer is only allowed when it fits in 32-bit. A XML-RPC dateTime.iso8601 type is always returned as a DateTime object. Struct is never returned, only a Hash, the same for a Symbol, where as a String is always returned. Base64 is returned as a String from xmlrpc4r version 1.6.1 on.

If the remote procedure returned a fault-structure, then a FaultException exception is raised, which has two accessor-methods faultCode an Integer, and faultString a String.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 267

def call(method, *args)
  ok, param = call2(method, *args)
  if ok
    param
  else
    raise param
  end
end

#call2(method, *args)

The difference between this method and #call is, that this method will NOT raise a FaultException exception.

The method returns an array of two values. The first value indicates if the second value is true or an FaultException.

Both are explained in #call.

Simple to remember: The “2” in “call2” denotes the number of values it returns.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 285

def call2(method, *args)
  request = create().methodCall(method, *args)
  data = do_rpc(request, false)
  parser().parseMethodResponse(data)
end

#call2_async(method, *args)

Same as #call2, but can be called concurrently.

See also #call_async

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 319

def call2_async(method, *args)
  request = create().methodCall(method, *args)
  data = do_rpc(request, true)
  parser().parseMethodResponse(data)
end

#call_async(method, *args)

Similar to #call, however can be called concurrently and use a new connection for each request. In contrast to the corresponding method without the _async suffix, which use connect-alive (one connection for all requests).

Note, that you have to use Thread to call these methods concurrently. The following example calls two methods concurrently:

Thread.new {
  p client.call_async("michael.add", 4, 5)
}

Thread.new {
  p client.call_async("michael.div", 7, 9)
}
[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 307

def call_async(method, *args)
  ok, param = call2_async(method, *args)
  if ok
    param
  else
    raise param
  end
end

#do_rpc(request, async = false) (private)

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 464

def do_rpc(request, async=false)
  header = {
   "User-Agent"     =>  USER_AGENT,
   "Content-Type"   => "text/xml; charset=utf-8",
   "Content-Length" => request.bytesize.to_s,
   "Connection"     => (async ? "close" : "keep-alive")
  }

  header["Cookie"] = @cookie        if @cookie
  header.update(@http_header_extra) if @http_header_extra

  if @auth != nil
    # add authorization header
    header["Authorization"] = @auth
  end

  resp = nil
  @http_last_response = nil

  if async
    # use a new HTTP object for each call
    http = dup_net_http

    # post request
    http.start {
      resp = http.request_post(@path, request, header)
    }
  else
    # reuse the HTTP object for each call => connection alive is possible
    # we must start connection explicitly first time so that http.request
    # does not assume that we don't want keepalive
    @http.start if not @http.started?

    # post request
    resp = @http.request_post(@path, request, header)
  end

  @http_last_response = resp

  data = resp.body

  if resp.code == "401"
    # Authorization Required
    raise "Authorization failed.\nHTTP-Error: #{resp.code} #{resp.message}"
  elsif resp.code[0,1] != "2"
    raise "HTTP-Error: #{resp.code} #{resp.message}"
  end

  # assume text/xml on instances where Content-Type header is not set
  ct_expected = resp["Content-Type"] || 'text/xml'
  ct = parse_content_type(ct_expected).first
  if ct != "text/xml"
    if ct == "text/html"
      raise "Wrong content-type (received '#{ct}' but expected 'text/xml'): \n#{data}"
    else
      raise "Wrong content-type (received '#{ct}' but expected 'text/xml')"
    end
  end

  expected = resp["Content-Length"] || "<unknown>"
  if data.nil? or data.bytesize == 0
    raise "Wrong size. Was #{data.bytesize}, should be #{expected}"
  end

  parse_set_cookies(resp.get_fields("Set-Cookie"))

  return data
end

#dup_net_http (private)

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 436

def dup_net_http
  http = net_http(@http.address,
                  @http.port,
                  @http.proxy_address,
                  @http.proxy_port)
  http.proxy_user = @http.proxy_user
  http.proxy_pass = @http.proxy_pass
  if @http.use_ssl?
    http.use_ssl = true
    Net::HTTP::SSL_ATTRIBUTES.each do |attribute|
      http.__send__("#{attribute}=", @http.__send__(attribute))
    end
  end
  http.read_timeout = @http.read_timeout
  http.open_timeout = @http.open_timeout
  http
end

#gen_multicall(methods = [], async = false) (private)

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 549

def gen_multicall(methods=[], async=false)
  meth = :call2
  meth = :call2_async if async

  ok, params = self.send(meth, "system.multicall",
    methods.collect {|m| {'methodName' => m[0], 'params' => m[1..-1]} }
  )

  if ok
    params = params.collect do |param|
      if param.is_a? Array
        param[0]
      elsif param.is_a? Hash
        XMLRPC::FaultException.new(param["faultCode"], param["faultString"])
      else
        raise "Wrong multicall return value"
      end
    end
  end

  return ok, params
end

#multicall(*methods)

You can use this method to execute several methods on a ::XMLRPC server which support the multi-call extension.

s.multicall(
  ['michael.add', 3, 4],
  ['michael.sub', 4, 5]
)
# => [7, -1]
[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 334

def multicall(*methods)
  ok, params = multicall2(*methods)
  if ok
    params
  else
    raise params
  end
end

#multicall2(*methods)

Same as #multicall, but returns two parameters instead of raising an FaultException.

See #call2

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 347

def multicall2(*methods)
  gen_multicall(methods, false)
end

#multicall2_async(*methods)

Same as #multicall2, but can be called concurrently.

See also #multicall_async

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 379

def multicall2_async(*methods)
  gen_multicall(methods, true)
end

#multicall_async(*methods)

Similar to #multicall, however can be called concurrently and use a new connection for each request. In contrast to the corresponding method without the _async suffix, which use connect-alive (one connection for all requests).

Note, that you have to use Thread to call these methods concurrently. The following example calls two methods concurrently:

Thread.new {
  p client.multicall_async("michael.add", 4, 5)
}

Thread.new {
  p client.multicall_async("michael.div", 7, 9)
}
[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 367

def multicall_async(*methods)
  ok, params = multicall2_async(*methods)
  if ok
    params
  else
    raise params
  end
end

#net_http(host, port, proxy_host, proxy_port) (private)

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 432

def net_http(host, port, proxy_host, proxy_port)
  Net::HTTP.new host, port, proxy_host, proxy_port
end

#parse_set_cookies(set_cookies) (private)

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 533

def parse_set_cookies(set_cookies)
  return if set_cookies.nil?
  return if set_cookies.empty?
  require 'webrick/cookie'
  pairs = {}
  set_cookies.each do |set_cookie|
    cookie = WEBrick::Cookie.parse_set_cookie(set_cookie)
    pairs.delete(cookie.name)
    pairs[cookie.name] = cookie.value
  end
  cookies = pairs.collect do |name, value|
    WEBrick::Cookie.new(name, value).to_s
  end
  @cookie = cookies.join("; ")
end

#proxy(prefix = nil, *args)

Returns an object of class Client::Proxy, initialized with prefix and args.

A proxy object returned by this method behaves like #call, i.e. a call on that object will raise a FaultException when a fault-structure is returned by that call.

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 390

def proxy(prefix=nil, *args)
  Proxy.new(self, prefix, args, :call)
end

#proxy2(prefix = nil, *args)

Almost the same like #proxy only that a call on the returned Client::Proxy object will return two parameters.

See #call2

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 398

def proxy2(prefix=nil, *args)
  Proxy.new(self, prefix, args, :call2)
end

#proxy2_async(prefix = nil, *args)

Same as #proxy2, but can be called concurrently.

See also #proxy_async

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 425

def proxy2_async(prefix=nil, *args)
  Proxy.new(self, prefix, args, :call2_async)
end

#proxy_async(prefix = nil, *args)

Similar to #proxy, however can be called concurrently and use a new connection for each request. In contrast to the corresponding method without the _async suffix, which use connect-alive (one connection for all requests).

Note, that you have to use Thread to call these methods concurrently. The following example calls two methods concurrently:

Thread.new {
  p client.proxy_async("michael.add", 4, 5)
}

Thread.new {
  p client.proxy_async("michael.div", 7, 9)
}
[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 418

def proxy_async(prefix=nil, *args)
  Proxy.new(self, prefix, args, :call_async)
end

#set_auth (private)

[ GitHub ]

  
# File 'lib/xmlrpc/client.rb', line 454

def set_auth
  if @user.nil?
    @auth = nil
  else
    a =  "#@user"
    a << ":#@password" if @password != nil
    @auth = "Basic " + [a].pack("m0")
  end
end