3 Copyright (C) 2001, 2002, 2003, 2005 by Michael Neumann (mneumann@ntecs.de)
5 Released under the same term of license as Ruby.
8 * ((<XMLRPC::BasicServer>))
9 * ((<XMLRPC::CGIServer>))
10 * ((<XMLRPC::ModRubyServer>))
11 * ((<XMLRPC::Server>))
12 * ((<XMLRPC::WEBrickServlet>))
16 Is the base class for all XML-RPC server-types (CGI, standalone).
17 You can add handler and set a default handler.
18 Do not use this server, as this is/should be an abstract class.
20 === How the method to call is found
21 The arity (number of accepted arguments) of a handler (method or (({Proc})) object) is
22 compared to the given arguments submitted by the client for a RPC ((-Remote Procedure Call-)).
23 A handler is only called if it accepts the number of arguments, otherwise the search
24 for another handler will go on. When at the end no handler was found,
25 the ((<default_handler|XMLRPC::BasicServer#set_default_handler>)) will be called.
26 With this technique it is possible to do overloading by number of parameters, but
27 only for (({Proc})) handler, because you cannot define two methods of the same name in
32 --- XMLRPC::BasicServer.new( class_delim="." )
33 Creates a new (({XMLRPC::BasicServer})) instance, which should not be
34 done, because (({XMLRPC::BasicServer})) is an abstract class. This
35 method should be called from a subclass indirectly by a (({super})) call
36 in the method (({initialize})). The paramter ((|class_delim|)) is used
37 in ((<add_handler|XMLRPC::BasicServer#add_handler>)) when an object is
38 added as handler, to delimit the object-prefix and the method-name.
41 --- XMLRPC::BasicServer#add_handler( name, signature=nil, help=nil ) { aBlock }
42 Adds ((|aBlock|)) to the list of handlers, with ((|name|)) as the name of the method.
43 Parameters ((|signature|)) and ((|help|)) are used by the Introspection method if specified,
44 where ((|signature|)) is either an Array containing strings each representing a type of it's
45 signature (the first is the return value) or an Array of Arrays if the method has multiple
46 signatures. Value type-names are "int, boolean, double, string, dateTime.iso8601, base64, array, struct".
48 Parameter ((|help|)) is a String with informations about how to call this method etc.
50 A handler method or code-block can return the types listed at
51 ((<XMLRPC::Client#call|URL:client.html#index:0>)).
52 When a method fails, it can tell it the client by throwing an
53 (({XMLRPC::FaultException})) like in this example:
54 s.add_handler("michael.div") do |a,b|
56 raise XMLRPC::FaultException.new(1, "division by zero")
61 The client gets in the case of (({b==0})) an object back of type
62 (({XMLRPC::FaultException})) that has a ((|faultCode|)) and ((|faultString|))
65 --- XMLRPC::BasicServer#add_handler( prefix, obj )
66 This is the second form of ((<add_handler|XMLRPC::BasicServer#add_handler>)).
67 To add an object write:
68 server.add_handler("michael", MyHandlerClass.new)
69 All public methods of (({MyHandlerClass})) are accessible to
70 the XML-RPC clients by (('michael."name of method"')). This is
71 where the ((|class_delim|)) in ((<new|XMLRPC::BasicServer.new>))
72 has it's role, a XML-RPC method-name is defined by
73 ((|prefix|)) + ((|class_delim|)) + (('"name of method"')).
75 --- XMLRPC::BasicServer#add_handler( interface, obj )
76 This is the third form of ((<add_handler|XMLRPC::BasicServer#add_handler>)).
78 Use (({XMLRPC::interface})) to generate an ServiceInterface object, which
79 represents an interface (with signature and help text) for a handler class.
81 Parameter ((|interface|)) must be of type (({XMLRPC::ServiceInterface})).
82 Adds all methods of ((|obj|)) which are defined in ((|interface|)) to the
85 This is the recommended way of adding services to a server!
88 --- XMLRPC::BasicServer#get_default_handler
89 Returns the default-handler, which is called when no handler for
90 a method-name is found.
91 It is a (({Proc})) object or (({nil})).
93 --- XMLRPC::BasicServer#set_default_handler ( &handler )
94 Sets ((|handler|)) as the default-handler, which is called when
95 no handler for a method-name is found. ((|handler|)) is a code-block.
96 The default-handler is called with the (XML-RPC) method-name as first argument, and
97 the other arguments are the parameters given by the client-call.
99 If no block is specified the default of (({XMLRPC::BasicServer})) is used, which raises a
100 XMLRPC::FaultException saying "method missing".
103 --- XMLRPC::BasicServer#set_writer( writer )
104 Sets the XML writer to use for generating XML output.
105 Should be an instance of a class from module (({XMLRPC::XMLWriter})).
106 If this method is not called, then (({XMLRPC::Config::DEFAULT_WRITER})) is used.
108 --- XMLRPC::BasicServer#set_parser( parser )
109 Sets the XML parser to use for parsing XML documents.
110 Should be an instance of a class from module (({XMLRPC::XMLParser})).
111 If this method is not called, then (({XMLRPC::Config::DEFAULT_PARSER})) is used.
113 --- XMLRPC::BasicServer#add_introspection
114 Adds the introspection handlers "system.listMethods", "system.methodSignature" and "system.methodHelp",
115 where only the first one works.
117 --- XMLRPC::BasicServer#add_multicall
118 Adds the multi-call handler "system.multicall".
120 --- XMLRPC::BasicServer#get_service_hook
121 Returns the service-hook, which is called on each service request (RPC) unless it's (({nil})).
123 --- XMLRPC::BasicServer#set_service_hook ( &handler )
124 A service-hook is called for each service request (RPC).
125 You can use a service-hook for example to wrap existing methods and catch exceptions of them or
126 convert values to values recognized by XMLRPC. You can disable it by passing (({nil})) as parameter
129 The service-hook is called with a (({Proc})) object and with the parameters for this (({Proc})).
132 server.set_service_hook {|obj, *args|
134 ret = obj.call(*args) # call the original service-method
135 # could convert the return value
145 require "xmlrpc/parser"
146 require "xmlrpc/create"
147 require "xmlrpc/config"
148 require "xmlrpc/utils" # ParserWriterChooseMixin
157 include ParserWriterChooseMixin
158 include ParseContentType
160 ERR_METHOD_MISSING = 1
161 ERR_UNCAUGHT_EXCEPTION = 2
162 ERR_MC_WRONG_PARAM = 3
163 ERR_MC_MISSING_PARAMS = 4
164 ERR_MC_MISSING_METHNAME = 5
165 ERR_MC_RECURSIVE_CALL = 6
166 ERR_MC_WRONG_PARAM_PARAMS = 7
167 ERR_MC_EXPECTED_STRUCT = 8
170 def initialize(class_delim=".")
172 @default_handler = nil
175 @class_delim = class_delim
179 add_multicall if Config::ENABLE_MULTICALL
180 add_introspection if Config::ENABLE_INTROSPECTION
183 def add_handler(prefix, obj_or_signature=nil, help=nil, &block)
186 @handler << [prefix, block, obj_or_signature, help]
188 if prefix.kind_of? String
190 raise ArgumentError, "Expected non-nil value" if obj_or_signature.nil?
191 @handler << [prefix + @class_delim, obj_or_signature]
192 elsif prefix.kind_of? XMLRPC::Service::BasicInterface
193 # class-handler with interface
195 @handler += prefix.get_methods(obj_or_signature, @class_delim)
197 raise ArgumentError, "Wrong type for parameter 'prefix'"
207 def set_service_hook(&handler)
208 @service_hook = handler
212 def get_default_handler
216 def set_default_handler (&handler)
217 @default_handler = handler
222 add_handler("system.multicall", %w(array array), "Multicall Extension") do |arrStructs|
223 unless arrStructs.is_a? Array
224 raise XMLRPC::FaultException.new(ERR_MC_WRONG_PARAM, "system.multicall expects an array")
227 arrStructs.collect {|call|
229 methodName = call["methodName"]
230 params = call["params"]
233 multicall_fault(ERR_MC_MISSING_PARAMS, "Missing params")
234 elsif methodName.nil?
235 multicall_fault(ERR_MC_MISSING_METHNAME, "Missing methodName")
237 if methodName == "system.multicall"
238 multicall_fault(ERR_MC_RECURSIVE_CALL, "Recursive system.multicall forbidden")
240 unless params.is_a? Array
241 multicall_fault(ERR_MC_WRONG_PARAM_PARAMS, "Parameter params have to be an Array")
243 ok, val = call_method(methodName, *params)
245 # correct return value
249 multicall_fault(val.faultCode, val.faultString)
256 multicall_fault(ERR_MC_EXPECTED_STRUCT, "system.multicall expected struct")
259 end # end add_handler
263 def add_introspection
264 add_handler("system.listMethods",%w(array), "List methods available on this XML-RPC server") do
266 @handler.each do |name, obj|
270 obj.methods.each {|meth| methods << name + meth}
276 add_handler("system.methodSignature", %w(array string), "Returns method signature") do |meth|
278 @handler.each do |name, obj, sig|
279 if obj.kind_of? Proc and sig != nil and name == meth
280 if sig[0].kind_of? Array
281 # sig contains multiple signatures, e.g. [["array"], ["array", "string"]]
282 sig.each {|s| sigs << s}
284 # sig is a single signature, e.g. ["array"]
289 sigs.uniq! || sigs # remove eventually duplicated signatures
292 add_handler("system.methodHelp", %w(string string), "Returns help on using this method") do |meth|
294 @handler.each do |name, obj, sig, hlp|
295 if obj.kind_of? Proc and name == meth
309 method, params = parser().parseMethodCall(data)
310 handle(method, *params)
313 private # --------------------------------------------------------------
315 def multicall_fault(nr, str)
316 {"faultCode" => nr, "faultString" => str}
322 def dispatch(methodname, *args)
323 for name, obj in @handler
325 next unless methodname == name
327 next unless methodname =~ /^#{name}(.+)$/
328 next unless obj.respond_to? $1
332 if check_arity(obj, args.size)
333 if @service_hook.nil?
334 return obj.call(*args)
336 return @service_hook.call(obj, *args)
341 if @default_handler.nil?
342 raise XMLRPC::FaultException.new(ERR_METHOD_MISSING, "Method #{methodname} missing or wrong number of parameters!")
344 @default_handler.call(methodname, *args)
350 # returns true, if the arity of "obj" matches
352 def check_arity(obj, n_args)
358 n_args >= (ary+1).abs
364 def call_method(methodname, *args)
366 [true, dispatch(methodname, *args)]
367 rescue XMLRPC::FaultException => e
369 rescue Exception => e
370 [false, XMLRPC::FaultException.new(ERR_UNCAUGHT_EXCEPTION, "Uncaught exception #{e.message} in method #{methodname}")]
377 def handle(methodname, *args)
378 create().methodResponse(*call_method(methodname, *args))
388 require "xmlrpc/server"
390 s = XMLRPC::CGIServer.new
392 s.add_handler("michael.add") do |a,b|
396 s.add_handler("michael.div") do |a,b|
398 raise XMLRPC::FaultException.new(1, "division by zero")
404 s.set_default_handler do |name, *args|
405 raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
406 " or wrong number of parameters!")
412 Implements a CGI-based XML-RPC server.
415 ((<XMLRPC::BasicServer>))
418 --- XMLRPC::CGIServer.new( *a )
419 Creates a new (({XMLRPC::CGIServer})) instance. All parameters given
420 are by-passed to ((<XMLRPC::BasicServer.new>)). You can only create
421 ((*one*)) (({XMLRPC::CGIServer})) instance, because more than one makes
425 --- XMLRPC::CGIServer#serve
426 Call this after you have added all you handlers to the server.
427 This method processes a XML-RPC methodCall and sends the answer
429 Make sure that you don't write to standard-output in a handler, or in
430 any other part of your program, this would case a CGI-based server to fail!
433 class CGIServer < BasicServer
436 def CGIServer.new(*a)
437 @@obj = super(*a) if @@obj.nil?
447 length = ENV['CONTENT_LENGTH'].to_i
449 http_error(405, "Method Not Allowed") unless ENV['REQUEST_METHOD'] == "POST"
450 http_error(400, "Bad Request") unless parse_content_type(ENV['CONTENT_TYPE']).first == "text/xml"
451 http_error(411, "Length Required") unless length > 0
453 # TODO: do we need a call to binmode?
454 $stdin.binmode if $stdin.respond_to? :binmode
455 data = $stdin.read(length)
457 http_error(400, "Bad Request") if data.nil? or data.size != length
459 http_write(process(data), "Content-type" => "text/xml; charset=utf-8")
466 def http_error(status, message)
467 err = "#{status} #{message}"
471 <title>#{err}</title>
475 <p>Unexpected error occured while processing XML-RPC request!</p>
480 http_write(msg, "Status" => err, "Content-type" => "text/html")
481 throw :exit_serve # exit from the #serve method
484 def http_write(body, header)
486 header.each {|key, value| h[key.to_s.capitalize] = value}
487 h['Status'] ||= "200 OK"
488 h['Content-length'] ||= body.size.to_s
491 h.each {|key, value| str << "#{key}: #{value}\r\n"}
500 = XMLRPC::ModRubyServer
502 Implements a XML-RPC server, which works with Apache mod_ruby.
504 Use it in the same way as CGIServer!
507 ((<XMLRPC::BasicServer>))
510 class ModRubyServer < BasicServer
513 @ap = Apache::request
520 @ap.headers_in.each {|key, value| header[key.capitalize] = value}
522 length = header['Content-length'].to_i
524 http_error(405, "Method Not Allowed") unless @ap.request_method == "POST"
525 http_error(400, "Bad Request") unless parse_content_type(header['Content-type']).first == "text/xml"
526 http_error(411, "Length Required") unless length > 0
528 # TODO: do we need a call to binmode?
530 data = @ap.read(length)
532 http_error(400, "Bad Request") if data.nil? or data.size != length
534 http_write(process(data), 200, "Content-type" => "text/xml; charset=utf-8")
541 def http_error(status, message)
542 err = "#{status} #{message}"
546 <title>#{err}</title>
550 <p>Unexpected error occured while processing XML-RPC request!</p>
555 http_write(msg, status, "Status" => err, "Content-type" => "text/html")
556 throw :exit_serve # exit from the #serve method
559 def http_write(body, status, header)
561 header.each {|key, value| h[key.to_s.capitalize] = value}
562 h['Status'] ||= "200 OK"
563 h['Content-length'] ||= body.size.to_s
565 h.each {|key, value| @ap.headers_out[key] = value }
566 @ap.content_type = h["Content-type"]
567 @ap.status = status.to_i
578 require "xmlrpc/server"
580 s = XMLRPC::Server.new(8080)
582 s.add_handler("michael.add") do |a,b|
586 s.add_handler("michael.div") do |a,b|
588 raise XMLRPC::FaultException.new(1, "division by zero")
594 s.set_default_handler do |name, *args|
595 raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
596 " or wrong number of parameters!")
602 Implements a standalone XML-RPC server. The method (({serve}))) is left if a SIGHUP is sent to the
606 ((<XMLRPC::WEBrickServlet>))
609 --- XMLRPC::Server.new( port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a )
610 Creates a new (({XMLRPC::Server})) instance, which is a XML-RPC server listening on
611 port ((|port|)) and accepts requests for the host ((|host|)), which is by default only the localhost.
612 The server is not started, to start it you have to call ((<serve|XMLRPC::Server#serve>)).
614 Parameters ((|audit|)) and ((|debug|)) are obsolete!
616 All additionally given parameters in ((|*a|)) are by-passed to ((<XMLRPC::BasicServer.new>)).
619 --- XMLRPC::Server#serve
620 Call this after you have added all you handlers to the server.
621 This method starts the server to listen for XML-RPC requests and answer them.
623 --- XMLRPC::Server#shutdown
624 Stops and shuts the server down.
628 class WEBrickServlet < BasicServer; end # forward declaration
630 class Server < WEBrickServlet
632 def initialize(port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a)
635 @server = WEBrick::HTTPServer.new(:Port => port, :BindAddress => host, :MaxClients => maxConnections,
636 :Logger => WEBrick::Log.new(stdlog))
637 @server.mount("/", self)
641 if RUBY_PLATFORM =~ /mingw|mswin32/
644 signals = %w[INT TERM HUP]
646 signals.each { |signal| trap(signal) { @server.shutdown } }
658 = XMLRPC::WEBrickServlet
662 require "xmlrpc/server"
664 s = XMLRPC::WEBrickServlet.new
665 s.add_handler("michael.add") do |a,b|
669 s.add_handler("michael.div") do |a,b|
671 raise XMLRPC::FaultException.new(1, "division by zero")
677 s.set_default_handler do |name, *args|
678 raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
679 " or wrong number of parameters!")
682 httpserver = WEBrick::HTTPServer.new(:Port => 8080)
683 httpserver.mount("/RPC2", s)
684 trap("HUP") { httpserver.shutdown } # use 1 instead of "HUP" on Windows
689 --- XMLRPC::WEBrickServlet#set_valid_ip( *ip_addr )
690 Specifies the valid IP addresses that are allowed to connect to the server.
691 Each IP is either a (({String})) or a (({Regexp})).
693 --- XMLRPC::WEBrickServlet#get_valid_ip
694 Return the via method ((<set_valid_ip|XMLRPC::Server#set_valid_ip>)) specified
698 Implements a servlet for use with WEBrick, a pure Ruby (HTTP-) server framework.
701 ((<XMLRPC::BasicServer>))
705 class WEBrickServlet < BasicServer
708 require "webrick/httpstatus"
712 # deprecated from WEBrick/1.2.2.
713 # but does not break anything.
714 def require_path_info?
718 def get_instance(config, *options)
719 # TODO: set config & options
723 def set_valid_ip(*ip_addr)
724 if ip_addr.size == 1 and ip_addr[0].nil?
735 def service(request, response)
738 raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip }
741 if request.request_method != "POST"
742 raise WEBrick::HTTPStatus::MethodNotAllowed,
743 "unsupported method `#{request.request_method}'."
746 if parse_content_type(request['Content-type']).first != "text/xml"
747 raise WEBrick::HTTPStatus::BadRequest
750 length = (request['Content-length'] || 0).to_i
752 raise WEBrick::HTTPStatus::LengthRequired unless length > 0
756 if data.nil? or data.size != length
757 raise WEBrick::HTTPStatus::BadRequest
761 if resp.nil? or resp.size <= 0
762 raise WEBrick::HTTPStatus::InternalServerError
765 response.status = 200
766 response['Content-Length'] = resp.size
767 response['Content-Type'] = "text/xml; charset=utf-8"