* transcode.c (transcode_restartable): my_transcoder argument removed.
[ruby-svn.git] / lib / xmlrpc / server.rb
blobca8d3b0a5b424c82c5558d8a6db5743abe45d97e
1 =begin
2 = xmlrpc/server.rb
3 Copyright (C) 2001, 2002, 2003, 2005 by Michael Neumann (mneumann@ntecs.de)
5 Released under the same term of license as Ruby.
7 = Classes
8 * ((<XMLRPC::BasicServer>))
9 * ((<XMLRPC::CGIServer>))
10 * ((<XMLRPC::ModRubyServer>))
11 * ((<XMLRPC::Server>))
12 * ((<XMLRPC::WEBrickServlet>))
14 = XMLRPC::BasicServer
15 == Description
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
28 the same class. 
31 == Class Methods
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.
40 == Instance Methods
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|
55           if b == 0
56             raise XMLRPC::FaultException.new(1, "division by zero")
57           else
58             a / b 
59           end
60         end 
61     The client gets in the case of (({b==0})) an object back of type
62     (({XMLRPC::FaultException})) that has a ((|faultCode|)) and ((|faultString|))
63     field.
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
83     server.
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.
98   
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  
127     ((|handler|)) .
129     The service-hook is called with a (({Proc})) object and with the parameters for this (({Proc})).
130     An example:
132        server.set_service_hook {|obj, *args|
133          begin
134            ret = obj.call(*args)  # call the original service-method
135            # could convert the return value 
136          resuce
137            # rescue exceptions
138          end
139        }
141 =end
145 require "xmlrpc/parser"
146 require "xmlrpc/create"
147 require "xmlrpc/config"
148 require "xmlrpc/utils"         # ParserWriterChooseMixin
152 module XMLRPC
155 class BasicServer
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=".")
171     @handler = []
172     @default_handler = nil 
173     @service_hook = nil
175     @class_delim = class_delim
176     @create = nil
177     @parser = nil
179     add_multicall     if Config::ENABLE_MULTICALL
180     add_introspection if Config::ENABLE_INTROSPECTION
181   end
183   def add_handler(prefix, obj_or_signature=nil, help=nil, &block)
184     if block_given?
185       # proc-handler
186       @handler << [prefix, block, obj_or_signature, help]   
187     else
188       if prefix.kind_of? String
189         # class-handler
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
194         # add all methods
195         @handler += prefix.get_methods(obj_or_signature, @class_delim)
196       else
197         raise ArgumentError, "Wrong type for parameter 'prefix'"
198       end
199     end
200     self
201   end
203   def get_service_hook
204     @service_hook
205   end
207   def set_service_hook(&handler)
208     @service_hook = handler
209     self
210   end
212   def get_default_handler
213     @default_handler
214   end
216   def set_default_handler (&handler)
217     @default_handler = handler
218     self
219   end  
221   def add_multicall
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")
225       end
227       arrStructs.collect {|call|
228         if call.is_a? Hash
229           methodName = call["methodName"]
230           params     = call["params"]  
232           if params.nil?
233             multicall_fault(ERR_MC_MISSING_PARAMS, "Missing params")
234           elsif methodName.nil?
235             multicall_fault(ERR_MC_MISSING_METHNAME, "Missing methodName")
236           else
237             if methodName == "system.multicall"
238               multicall_fault(ERR_MC_RECURSIVE_CALL, "Recursive system.multicall forbidden")
239             else
240               unless params.is_a? Array
241                 multicall_fault(ERR_MC_WRONG_PARAM_PARAMS, "Parameter params have to be an Array")
242               else
243                 ok, val = call_method(methodName, *params)
244                 if ok
245                   # correct return value
246                   [val]
247                 else
248                   # exception
249                   multicall_fault(val.faultCode, val.faultString) 
250                 end
251               end
252             end
253           end  
254            
255         else
256           multicall_fault(ERR_MC_EXPECTED_STRUCT, "system.multicall expected struct")
257         end
258       } 
259     end # end add_handler
260     self
261   end
263   def add_introspection
264     add_handler("system.listMethods",%w(array), "List methods available on this XML-RPC server") do
265       methods = []
266       @handler.each do |name, obj|
267         if obj.kind_of? Proc
268           methods << name
269         else
270           obj.methods.each {|meth| methods << name + meth}
271         end
272       end
273       methods
274     end
276     add_handler("system.methodSignature", %w(array string), "Returns method signature") do |meth|
277       sigs = []
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}
283           else
284             # sig is a single signature, e.g. ["array"]
285             sigs << sig 
286           end
287         end
288       end
289       sigs.uniq! || sigs  # remove eventually duplicated signatures
290     end
292     add_handler("system.methodHelp", %w(string string), "Returns help on using this method") do |meth|
293       help = nil 
294       @handler.each do |name, obj, sig, hlp|
295         if obj.kind_of? Proc and name == meth 
296           help = hlp
297           break      
298         end
299       end
300       help || ""
301     end
303     self
304   end
307   
308   def process(data)
309     method, params = parser().parseMethodCall(data) 
310     handle(method, *params)
311   end
313   private # --------------------------------------------------------------
315   def multicall_fault(nr, str)
316     {"faultCode" => nr, "faultString" => str}
317   end
319   #
320   # method dispatch
321   #
322   def dispatch(methodname, *args)
323     for name, obj in @handler
324       if obj.kind_of? Proc
325         next unless methodname == name
326       else
327         next unless methodname =~ /^#{name}(.+)$/
328         next unless obj.respond_to? $1
329         obj = obj.method($1)
330       end
332       if check_arity(obj, args.size)
333         if @service_hook.nil?
334           return obj.call(*args) 
335         else
336           return @service_hook.call(obj, *args)
337         end
338       end
339     end 
341     if @default_handler.nil?
342       raise XMLRPC::FaultException.new(ERR_METHOD_MISSING, "Method #{methodname} missing or wrong number of parameters!")
343     else
344       @default_handler.call(methodname, *args) 
345     end
346   end
349   #
350   # returns true, if the arity of "obj" matches
351   #
352   def check_arity(obj, n_args)
353     ary = obj.arity
355     if ary >= 0
356       n_args == ary
357     else
358       n_args >= (ary+1).abs 
359     end
360   end
364   def call_method(methodname, *args)
365     begin
366       [true, dispatch(methodname, *args)]
367     rescue XMLRPC::FaultException => e  
368       [false, e]  
369     rescue Exception => e
370       [false, XMLRPC::FaultException.new(ERR_UNCAUGHT_EXCEPTION, "Uncaught exception #{e.message} in method #{methodname}")]
371     end
372   end
374   #
375   #
376   #
377   def handle(methodname, *args)
378     create().methodResponse(*call_method(methodname, *args))
379   end
385 =begin
386 = XMLRPC::CGIServer
387 == Synopsis
388     require "xmlrpc/server"
390     s = XMLRPC::CGIServer.new     
392     s.add_handler("michael.add") do |a,b|
393       a + b
394     end
396     s.add_handler("michael.div") do |a,b|
397       if b == 0
398         raise XMLRPC::FaultException.new(1, "division by zero")
399       else
400         a / b 
401       end
402     end 
404     s.set_default_handler do |name, *args|
405       raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
406                                        " or wrong number of parameters!")
407     end
408   
409     s.serve
411 == Description
412 Implements a CGI-based XML-RPC server.
414 == Superclass
415 ((<XMLRPC::BasicServer>))
417 == Class Methods
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
422     no sense.
424 == Instance Methods
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
428     back to the client. 
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!
431 =end
433 class CGIServer < BasicServer
434   @@obj = nil
436   def CGIServer.new(*a)
437     @@obj = super(*a) if @@obj.nil?
438     @@obj
439   end
441   def initialize(*a)
442     super(*a)
443   end
444   
445   def serve
446     catch(:exit_serve) {
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")
460     }
461   end
464   private
466   def http_error(status, message)
467     err = "#{status} #{message}"
468     msg = <<-"MSGEND" 
469       <html>
470         <head>
471           <title>#{err}</title>
472         </head>
473         <body>
474           <h1>#{err}</h1>
475           <p>Unexpected error occured while processing XML-RPC request!</p>
476         </body>
477       </html>
478     MSGEND
480     http_write(msg, "Status" => err, "Content-type" => "text/html")
481     throw :exit_serve # exit from the #serve method
482   end
484   def http_write(body, header)
485     h = {}
486     header.each {|key, value| h[key.to_s.capitalize] = value}
487     h['Status']         ||= "200 OK"
488     h['Content-length'] ||= body.size.to_s 
490     str = ""
491     h.each {|key, value| str << "#{key}: #{value}\r\n"}
492     str << "\r\n#{body}"
494     print str
495   end
499 =begin
500 = XMLRPC::ModRubyServer
501 == Description
502 Implements a XML-RPC server, which works with Apache mod_ruby.
504 Use it in the same way as CGIServer!
506 == Superclass
507 ((<XMLRPC::BasicServer>))
508 =end 
510 class ModRubyServer < BasicServer
512   def initialize(*a)
513     @ap = Apache::request
514     super(*a)
515   end
517   def serve
518     catch(:exit_serve) {
519       header = {}
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?
529       @ap.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")
535     }
536   end
539   private
541   def http_error(status, message)
542     err = "#{status} #{message}"
543     msg = <<-"MSGEND" 
544       <html>
545         <head>
546           <title>#{err}</title>
547         </head>
548         <body>
549           <h1>#{err}</h1>
550           <p>Unexpected error occured while processing XML-RPC request!</p>
551         </body>
552       </html>
553     MSGEND
555     http_write(msg, status, "Status" => err, "Content-type" => "text/html")
556     throw :exit_serve # exit from the #serve method
557   end
559   def http_write(body, status, header)
560     h = {}
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 
568     @ap.send_http_header 
570     @ap.print body
571   end
575 =begin
576 = XMLRPC::Server
577 == Synopsis
578     require "xmlrpc/server"
580     s = XMLRPC::Server.new(8080) 
582     s.add_handler("michael.add") do |a,b|
583       a + b
584     end
586     s.add_handler("michael.div") do |a,b|
587       if b == 0
588         raise XMLRPC::FaultException.new(1, "division by zero")
589       else
590         a / b 
591       end
592     end 
594     s.set_default_handler do |name, *args|
595       raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
596                                        " or wrong number of parameters!")
597     end
599     s.serve
601 == Description
602 Implements a standalone XML-RPC server. The method (({serve}))) is left if a SIGHUP is sent to the
603 program.
605 == Superclass
606 ((<XMLRPC::WEBrickServlet>))
608 == Class Methods
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>)). 
617     
618 == Instance Methods
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.
625     
626 =end
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)
633     super(*a)
634     require 'webrick'
635     @server = WEBrick::HTTPServer.new(:Port => port, :BindAddress => host, :MaxClients => maxConnections, 
636                                       :Logger => WEBrick::Log.new(stdlog))
637     @server.mount("/", self)
638   end
639   
640   def serve
641     if RUBY_PLATFORM =~ /mingw|mswin32/
642       signals = [1]
643     else
644       signals = %w[INT TERM HUP]
645     end
646     signals.each { |signal| trap(signal) { @server.shutdown } }
648     @server.start
649   end
650   
651   def shutdown
652     @server.shutdown
653   end
657 =begin
658 = XMLRPC::WEBrickServlet
659 == Synopsis
661     require "webrick"
662     require "xmlrpc/server"
664     s = XMLRPC::WEBrickServlet.new
665     s.add_handler("michael.add") do |a,b|
666       a + b
667     end
669     s.add_handler("michael.div") do |a,b|
670       if b == 0
671         raise XMLRPC::FaultException.new(1, "division by zero")
672       else
673         a / b 
674       end
675     end 
677     s.set_default_handler do |name, *args|
678       raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
679                                        " or wrong number of parameters!")
680     end
682     httpserver = WEBrick::HTTPServer.new(:Port => 8080)    
683     httpserver.mount("/RPC2", s)
684     trap("HUP") { httpserver.shutdown }   # use 1 instead of "HUP" on Windows
685     httpserver.start
687 == Instance Methods
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
695     valid IP addresses.
697 == Description
698 Implements a servlet for use with WEBrick, a pure Ruby (HTTP-) server framework.
700 == Superclass
701 ((<XMLRPC::BasicServer>))
703 =end
705 class WEBrickServlet < BasicServer
706   def initialize(*a)
707     super
708     require "webrick/httpstatus"
709     @valid_ip = nil
710   end
712   # deprecated from WEBrick/1.2.2. 
713   # but does not break anything.
714   def require_path_info?
715     false 
716   end
718   def get_instance(config, *options)
719     # TODO: set config & options
720     self
721   end
723   def set_valid_ip(*ip_addr)
724     if ip_addr.size == 1 and ip_addr[0].nil?
725       @valid_ip = nil
726     else
727       @valid_ip = ip_addr
728     end
729   end
731   def get_valid_ip
732     @valid_ip
733   end
735   def service(request, response)
737     if @valid_ip 
738       raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip }
739     end
741     if request.request_method != "POST"
742       raise WEBrick::HTTPStatus::MethodNotAllowed,
743             "unsupported method `#{request.request_method}'."
744     end
746     if parse_content_type(request['Content-type']).first != "text/xml" 
747       raise WEBrick::HTTPStatus::BadRequest
748     end 
750     length = (request['Content-length'] || 0).to_i
752     raise WEBrick::HTTPStatus::LengthRequired unless length > 0
754     data = request.body
756     if data.nil? or data.size != length
757       raise WEBrick::HTTPStatus::BadRequest
758     end
760     resp = process(data)
761     if resp.nil? or resp.size <= 0  
762       raise WEBrick::HTTPStatus::InternalServerError
763     end
765     response.status = 200
766     response['Content-Length'] = resp.size
767     response['Content-Type']   = "text/xml; charset=utf-8"
768     response.body = resp 
769   end
773 end # module XMLRPC
776 =begin
777 = History
778     $Id$    
779 =end