mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@7287 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
		
			
				
	
	
		
			300 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
			
		
		
	
	
			300 lines
		
	
	
	
		
			8.4 KiB
		
	
	
	
		
			Text
		
	
	
	
	
	
= 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>.
 |