1# == Author and Copyright 2# 3# Copyright (C) 2001-2004 by Michael Neumann (mailto:mneumann@ntecs.de) 4# 5# Released under the same term of license as Ruby. 6# 7# == Overview 8# 9# XMLRPC is a lightweight protocol that enables remote procedure calls over 10# HTTP. It is defined at http://www.xmlrpc.com. 11# 12# XMLRPC allows you to create simple distributed computing solutions that span 13# computer languages. Its distinctive feature is its simplicity compared to 14# other approaches like SOAP and CORBA. 15# 16# The Ruby standard library package 'xmlrpc' enables you to create a server that 17# implements remote procedures and a client that calls them. Very little code 18# is required to achieve either of these. 19# 20# == Example 21# 22# Try the following code. It calls a standard demonstration remote procedure. 23# 24# require 'xmlrpc/client' 25# require 'pp' 26# 27# server = XMLRPC::Client.new2("http://xmlrpc-c.sourceforge.net/api/sample.php") 28# result = server.call("sample.sumAndDifference", 5, 3) 29# pp result 30# 31# == Documentation 32# 33# See http://www.ntecs.de/projects/xmlrpc4r. There is plenty of detail there to 34# use the client and implement a server. 35# 36# == Features of XMLRPC for Ruby 37# 38# * Extensions 39# * Introspection 40# * multiCall 41# * optionally nil values and integers larger than 32 Bit 42# 43# * Server 44# * Standalone XML-RPC server 45# * CGI-based (works with FastCGI) 46# * Apache mod_ruby server 47# * WEBrick servlet 48# 49# * Client 50# * synchronous/asynchronous calls 51# * Basic HTTP-401 Authentification 52# * HTTPS protocol (SSL) 53# 54# * Parsers 55# * NQXML (XMLParser::NQXMLStreamParser, XMLParser::NQXMLTreeParser) 56# * Expat (XMLParser::XMLStreamParser, XMLParser::XMLTreeParser) 57# * REXML (XMLParser::REXMLStreamParser) 58# * xml-scan (XMLParser::XMLScanStreamParser) 59# * Fastest parser is Expat's XMLParser::XMLStreamParser! 60# 61# * General 62# * possible to choose between XMLParser module (Expat wrapper) and REXML/NQXML (pure Ruby) parsers 63# * Marshalling Ruby objects to Hashs and reconstruct them later from a Hash 64# * SandStorm component architecture XMLRPC::Client interface 65# 66# == Howto 67# 68# === Client 69# 70# require "xmlrpc/client" 71# 72# # Make an object to represent the XML-RPC server. 73# server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php") 74# 75# # Call the remote server and get our result 76# result = server.call("sample.sumAndDifference", 5, 3) 77# 78# sum = result["sum"] 79# difference = result["difference"] 80# 81# puts "Sum: #{sum}, Difference: #{difference}" 82# 83# === XMLRPC::Client with XML-RPC fault-structure handling 84# 85# There are two possible ways, of handling a fault-structure: 86# 87# ==== by catching a XMLRPC::FaultException exception 88# 89# require "xmlrpc/client" 90# 91# # Make an object to represent the XML-RPC server. 92# server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php") 93# 94# begin 95# # Call the remote server and get our result 96# result = server.call("sample.sumAndDifference", 5, 3) 97# 98# sum = result["sum"] 99# difference = result["difference"] 100# 101# puts "Sum: #{sum}, Difference: #{difference}" 102# 103# rescue XMLRPC::FaultException => e 104# puts "Error: " 105# puts e.faultCode 106# puts e.faultString 107# end 108# 109# ==== by calling "call2" which returns a boolean 110# 111# require "xmlrpc/client" 112# 113# # Make an object to represent the XML-RPC server. 114# server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php") 115# 116# # Call the remote server and get our result 117# ok, result = server.call2("sample.sumAndDifference", 5, 3) 118# 119# if ok 120# sum = result["sum"] 121# difference = result["difference"] 122# 123# puts "Sum: #{sum}, Difference: #{difference}" 124# else 125# puts "Error: " 126# puts result.faultCode 127# puts result.faultString 128# end 129# 130# === Using XMLRPC::Client::Proxy 131# 132# You can create a Proxy object onto which you can call methods. This way it 133# looks nicer. Both forms, _call_ and _call2_ are supported through _proxy_ and 134# _proxy2_. You can additionally give arguments to the Proxy, which will be 135# given to each XML-RPC call using that Proxy. 136# 137# require "xmlrpc/client" 138# 139# # Make an object to represent the XML-RPC server. 140# server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php") 141# 142# # Create a Proxy object 143# sample = server.proxy("sample") 144# 145# # Call the remote server and get our result 146# result = sample.sumAndDifference(5,3) 147# 148# sum = result["sum"] 149# difference = result["difference"] 150# 151# puts "Sum: #{sum}, Difference: #{difference}" 152# 153# === CGI-based server using XMLRPC::CGIServer 154# 155# There are also two ways to define handler, the first is 156# like C/PHP, the second like Java, of course both ways 157# can be mixed: 158# 159# ==== C/PHP-like (handler functions) 160# 161# require "xmlrpc/server" 162# 163# s = XMLRPC::CGIServer.new 164# 165# s.add_handler("sample.sumAndDifference") do |a,b| 166# { "sum" => a + b, "difference" => a - b } 167# end 168# 169# s.serve 170# 171# ==== Java-like (handler classes) 172# 173# require "xmlrpc/server" 174# 175# s = XMLRPC::CGIServer.new 176# 177# class MyHandler 178# def sumAndDifference(a, b) 179# { "sum" => a + b, "difference" => a - b } 180# end 181# end 182# 183# # NOTE: Security Hole (read below)!!! 184# s.add_handler("sample", MyHandler.new) 185# s.serve 186# 187# 188# To return a fault-structure you have to raise an XMLRPC::FaultException e.g.: 189# 190# raise XMLRPC::FaultException.new(3, "division by Zero") 191# 192# ===== Security Note 193# 194# From Brian Candler: 195# 196# Above code sample has an extremely nasty security hole, in that you can now call 197# any method of 'MyHandler' remotely, including methods inherited from Object 198# and Kernel! For example, in the client code, you can use 199# 200# puts server.call("sample.send","`","ls") 201# 202# (backtick being the method name for running system processes). Needless to 203# say, 'ls' can be replaced with something else. 204# 205# The version which binds proc objects (or the version presented below in the next section) 206# doesn't have this problem, but people may be tempted to use the second version because it's 207# so nice and 'Rubyesque'. I think it needs a big red disclaimer. 208# 209# 210# From Michael: 211# 212# A solution is to undef insecure methods or to use 213# XMLRPC::Service::PublicInstanceMethodsInterface as shown below: 214# 215# class MyHandler 216# def sumAndDifference(a, b) 217# { "sum" => a + b, "difference" => a - b } 218# end 219# end 220# 221# # ... server initialization ... 222# 223# s.add_handler(XMLRPC::iPIMethods("sample"), MyHandler.new) 224# 225# # ... 226# 227# This adds only public instance methods explicitly declared in class MyHandler 228# (and not those inherited from any other class). 229# 230# ==== With interface declarations 231# 232# Code sample from the book Ruby Developer's Guide: 233# 234# require "xmlrpc/server" 235# 236# class Num 237# INTERFACE = XMLRPC::interface("num") { 238# meth 'int add(int, int)', 'Add two numbers', 'add' 239# meth 'int div(int, int)', 'Divide two numbers' 240# } 241# 242# def add(a, b) a + b end 243# def div(a, b) a / b end 244# end 245# 246# 247# s = XMLRPC::CGIServer.new 248# s.add_handler(Num::INTERFACE, Num.new) 249# s.serve 250# 251# === Standalone XMLRPC::Server 252# 253# Same as CGI-based server, the only difference being 254# 255# server = XMLRPC::CGIServer.new 256# 257# must be changed to 258# 259# server = XMLRPC::Server.new(8080) 260# 261# if you want a server listening on port 8080. 262# The rest is the same. 263# 264# === Choosing a different XMLParser or XMLWriter 265# 266# The examples above all use the default parser (which is now since 1.8 267# XMLParser::REXMLStreamParser) and a default XMLRPC::XMLWriter. 268# If you want to use a different XMLParser, then you have to call the 269# ParserWriterChooseMixin#set_parser method of XMLRPC::Client instances 270# or instances of subclasses of XMLRPC::BasicServer or by editing 271# xmlrpc/config.rb. 272# 273# XMLRPC::Client Example: 274# 275# # ... 276# server = XMLRPC::Client.new( "xmlrpc-c.sourceforge.net", "/api/sample.php") 277# server.set_parser(XMLRPC::XMLParser::XMLParser.new) 278# # ... 279# 280# XMLRPC::Server Example: 281# 282# # ... 283# s = XMLRPC::CGIServer.new 284# s.set_parser(XMLRPC::XMLParser::XMLStreamParser.new) 285# # ... 286# 287# or: 288# 289# # ... 290# server = XMLRPC::Server.new(8080) 291# server.set_parser(XMLRPC::XMLParser::NQXMLParser.new) 292# # ... 293# 294# 295# Note that XMLParser::XMLStreamParser is incredible faster (and uses less memory) than any 296# other parser and scales well for large documents. For example for a 0.5 MB XML 297# document with many tags, XMLParser::XMLStreamParser is ~350 (!) times faster than 298# XMLParser::NQXMLTreeParser and still ~18 times as fast as XMLParser::XMLTreeParser. 299# 300# You can change the XML-writer by calling method ParserWriterChooseMixin#set_writer. 301module XMLRPC; end 302