= XMLRPC for Ruby

== Author and Copyright

Copyright (C) 2001-2004 by Michael Neumann (mailto:mneumann@ntecs.de)

Released under the same term of license as Ruby.

== Overview

XMLRPC is a lightweight protocol that enables remote procedure calls over
HTTP.  It is defined at http://www.xmlrpc.com.

XMLRPC allows you to create simple distributed computing solutions that span
computer languages.  Its distinctive feature is its simplicity compared to
other approaches like SOAP and CORBA.

The Ruby standard library package 'xmlrpc' enables you to create a server that
implements remote procedures and a client that calls them.  Very little code
is required to achieve either of these.

== Example

Try the following code.  It calls a standard demonstration remote procedure.

  require 'xmlrpc/client'
  require 'pp'

  server = XMLRPC::Client.new2("http://xmlrpc-c.sourceforge.net/api/sample.php")
  result = server.call("sample.sumAndDifference", 5, 3)
  pp result

== Documentation

See http://www.ntecs.de/projects/xmlrpc4r.  There is plenty of detail there to
use the client and implement a server.

== Features of XMLRPC for Ruby

* Extensions
  * Introspection
  * multiCall
  * optionally nil values and integers larger than 32 Bit

* Server
  * Standalone XML-RPC server
  * CGI-based (works with FastCGI)
  * Apache mod_ruby server
  * WEBrick servlet

* Client
  * synchronous/asynchronous calls
  * Basic HTTP-401 Authentification
  * HTTPS protocol (SSL)

* Parsers
  * NQXML (NQXMLStreamParser, NQXMLTreeParser)
  * Expat (XMLStreamParser, XMLTreeParser)
  * REXML (REXMLStreamParser)
  * xml-scan (XMLScanStreamParser)
  * Fastest parser is Expat's XMLStreamParser!
 
* General
  * possible to choose between XMLParser module (Expat wrapper) and REXML/NQXML (pure Ruby) parsers
  * Marshalling Ruby objects to Hashs and reconstruct them later from a Hash
  * SandStorm component architecture Client interface

== Howto

=== Client

  require "xmlrpc/client"
  
  # Make an object to represent the XML-RPC server.
  server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php")

  # Call the remote server and get our result
  result = server.call("sample.sumAndDifference", 5, 3)

  sum = result["sum"]
  difference = result["difference"]

  puts "Sum: #{sum}, Difference: #{difference}"

=== Client with XML-RPC fault-structure handling

There are two possible ways, of handling a fault-structure:

==== by catching a XMLRPC::FaultException exception 

  require "xmlrpc/client"

  # Make an object to represent the XML-RPC server.
  server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php")

  begin
    # Call the remote server and get our result
    result = server.call("sample.sumAndDifference", 5, 3)

    sum = result["sum"]
    difference = result["difference"]

    puts "Sum: #{sum}, Difference: #{difference}"

  rescue XMLRPC::FaultException => e
    puts "Error: "
    puts e.faultCode
    puts e.faultString
  end
   
==== by calling "call2" which returns a boolean

  require "xmlrpc/client"

  # Make an object to represent the XML-RPC server.
  server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php")

  # Call the remote server and get our result
  ok, result = server.call2("sample.sumAndDifference", 5, 3)

  if ok
    sum = result["sum"]
    difference = result["difference"]

    puts "Sum: #{sum}, Difference: #{difference}"
  else
    puts "Error: "
    puts result.faultCode
    puts result.faultString
  end
   
=== Client using Proxy

You can create a +Proxy+ object onto which you can call methods. This way it
looks nicer. Both forms, _call_ and _call2_ are supported through _proxy_ and
<i>proxy2</i>.  You can additionally give arguments to the Proxy, which will be
given to each XML-RPC call using that Proxy.

  require "xmlrpc/client"
  
  # Make an object to represent the XML-RPC server.
  server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php")

  # Create a Proxy object
  sample = server.proxy("sample")

  # Call the remote server and get our result
  result = sample.sumAndDifference(5,3)

  sum = result["sum"]
  difference = result["difference"]

  puts "Sum: #{sum}, Difference: #{difference}"

=== CGI-based Server

There are also two ways to define handler, the first is
like C/PHP, the second like Java, of course both ways
can be mixed:

==== C/PHP-like (handler functions)

  require "xmlrpc/server"

  s = XMLRPC::CGIServer.new

  s.add_handler("sample.sumAndDifference") do |a,b|
    { "sum" => a + b, "difference" => a - b }
  end
    
  s.serve

==== Java-like (handler classes)

  require "xmlrpc/server"

  s = XMLRPC::CGIServer.new

  class MyHandler
    def sumAndDifference(a, b)
      { "sum" => a + b, "difference" => a - b }
    end
  end
    
  # NOTE: Security Hole (read below)!!! 
  s.add_handler("sample", MyHandler.new)
  s.serve


To return a fault-structure you have to raise an FaultException e.g.:

  raise XMLRPC::FaultException.new(3, "division by Zero")

===== Security Note

From Brian Candler:

  Above code sample has an extremely nasty security hole, in that you can now call                                                        
  any method of 'MyHandler' remotely, including methods inherited from Object                                                        
  and Kernel! For example, in the client code, you can use                                                                           
                                                                                                                                   
    puts server.call("sample.send","`","ls")                                                                                       
                                                                                                                                   
  (backtick being the method name for running system processes). Needless to                                                         
  say, 'ls' can be replaced with something else.                                                                                     

  The version which binds proc objects (or the version presented below in the next section) 
  doesn't have this problem, but people may be tempted to use the second version because it's 
  so nice and 'Rubyesque'. I think it needs a big red disclaimer.


From Michael:

A solution is to undef insecure methods or to use (({XMLRPC::iPIMethods})) as shown below:

  class MyHandler
    def sumAndDifference(a, b)
      { "sum" => a + b, "difference" => a - b }
    end
  end

  # ... server initialization ...

  s.add_handler(XMLRPC::iPIMethods("sample"), MyHandler.new)

  # ...

This adds only public instance methods explicitly declared in class MyHandler 
(and not those inherited from any other class).

==== With interface declarations

Code sample from the book Ruby Developer's Guide:

  require "xmlrpc/server"

  class Num
    INTERFACE = XMLRPC::interface("num") {
      meth 'int add(int, int)', 'Add two numbers', 'add'
      meth 'int div(int, int)', 'Divide two numbers'
    }

    def add(a, b) a + b end
    def div(a, b) a / b end
  end


  s = XMLRPC::CGIServer.new
  s.add_handler(Num::INTERFACE, Num.new)
  s.serve

=== Standalone server

Same as CGI-based server, only that the line

  server = XMLRPC::CGIServer.new

must be changed to

  server = XMLRPC::Server.new(8080)

if you want a server listening on port 8080.
The rest is the same.

=== Choosing a different XML Parser or XML Writer

The examples above all use the default parser (which is now since 1.8
REXMLStreamParser) and a default XML writer.  If you want to use a different
XML parser, then you have to call the <i>set_parser</i> method of
<tt>XMLRPC::Client</tt> instances or instances of subclasses of
<tt>XMLRPC::BasicServer</tt> or by editing xmlrpc/config.rb.

Client Example:
 
  # ...
  server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php")
  server.set_parser(XMLRPC::XMLParser::XMLParser.new)
  # ...

Server Example:

  # ...
  s = XMLRPC::CGIServer.new
  s.set_parser(XMLRPC::XMLParser::XMLStreamParser.new)
  # ...
  
or:

  # ...
  server = XMLRPC::Server.new(8080)
  server.set_parser(XMLRPC::XMLParser::NQXMLParser.new)
  # ...


Note that XMLStreamParser is incredible faster (and uses less memory) than any
other parser and scales well for large documents. For example for a 0.5 MB XML
document with many tags, XMLStreamParser is ~350 (!) times faster than
NQXMLTreeParser and still ~18 times as fast as XMLTreeParser.

You can change the XML-writer by calling method <i>set_writer</i>.
