source: extensions/gsdl-video/trunk/installed/cmdline/lib/ruby/1.8/xmlrpc/server.rb@ 18425

Last change on this file since 18425 was 18425, checked in by davidb, 15 years ago

Video extension to Greenstone

File size: 21.7 KB
Line 
1=begin
2= xmlrpc/server.rb
3Copyright (C) 2001, 2002, 2003, 2005 by Michael Neumann ([email protected])
4
5Released under the same term of license as Ruby.
6
7= Classes
8* ((<XMLRPC::BasicServer>))
9* ((<XMLRPC::CGIServer>))
10* ((<XMLRPC::ModRubyServer>))
11* ((<XMLRPC::Server>))
12* ((<XMLRPC::WEBrickServlet>))
13
14= XMLRPC::BasicServer
15== Description
16Is the base class for all XML-RPC server-types (CGI, standalone).
17You can add handler and set a default handler.
18Do not use this server, as this is/should be an abstract class.
19
20=== How the method to call is found
21The arity (number of accepted arguments) of a handler (method or (({Proc})) object) is
22compared to the given arguments submitted by the client for a RPC ((-Remote Procedure Call-)).
23A handler is only called if it accepts the number of arguments, otherwise the search
24for another handler will go on. When at the end no handler was found,
25the ((<default_handler|XMLRPC::BasicServer#set_default_handler>)) will be called.
26With this technique it is possible to do overloading by number of parameters, but
27only for (({Proc})) handler, because you cannot define two methods of the same name in
28the same class.
29
30
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.
39
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".
47
48 Parameter ((|help|)) is a String with informations about how to call this method etc.
49
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.
64
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"')).
74
75--- XMLRPC::BasicServer#add_handler( interface, obj )
76 This is the third form of ((<add_handler|XMLRPC::BasicServer#add_handler>)).
77
78 Use (({XMLRPC::interface})) to generate an ServiceInterface object, which
79 represents an interface (with signature and help text) for a handler class.
80
81 Parameter ((|interface|)) must be of type (({XMLRPC::ServiceInterface})).
82 Adds all methods of ((|obj|)) which are defined in ((|interface|)) to the
83 server.
84
85 This is the recommended way of adding services to a server!
86
87
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})).
92
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".
101
102
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.
107
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.
112
113--- XMLRPC::BasicServer#add_introspection
114 Adds the introspection handlers "system.listMethods", "system.methodSignature" and "system.methodHelp",
115 where only the first one works.
116
117--- XMLRPC::BasicServer#add_multicall
118 Adds the multi-call handler "system.multicall".
119
120--- XMLRPC::BasicServer#get_service_hook
121 Returns the service-hook, which is called on each service request (RPC) unless it's (({nil})).
122
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|)) .
128
129 The service-hook is called with a (({Proc})) object and with the parameters for this (({Proc})).
130 An example:
131
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 }
140
141=end
142
143
144
145require "xmlrpc/parser"
146require "xmlrpc/create"
147require "xmlrpc/config"
148require "xmlrpc/utils" # ParserWriterChooseMixin
149
150
151
152module XMLRPC
153
154
155class BasicServer
156
157 include ParserWriterChooseMixin
158 include ParseContentType
159
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
168
169
170 def initialize(class_delim=".")
171 @handler = []
172 @default_handler = nil
173 @service_hook = nil
174
175 @class_delim = class_delim
176 @create = nil
177 @parser = nil
178
179 add_multicall if Config::ENABLE_MULTICALL
180 add_introspection if Config::ENABLE_INTROSPECTION
181 end
182
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
202
203 def get_service_hook
204 @service_hook
205 end
206
207 def set_service_hook(&handler)
208 @service_hook = handler
209 self
210 end
211
212 def get_default_handler
213 @default_handler
214 end
215
216 def set_default_handler (&handler)
217 @default_handler = handler
218 self
219 end
220
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
226
227 arrStructs.collect {|call|
228 if call.is_a? Hash
229 methodName = call["methodName"]
230 params = call["params"]
231
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
262
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
275
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
291
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
302
303 self
304 end
305
306
307
308 def process(data)
309 method, params = parser().parseMethodCall(data)
310 handle(method, *params)
311 end
312
313 private # --------------------------------------------------------------
314
315 def multicall_fault(nr, str)
316 {"faultCode" => nr, "faultString" => str}
317 end
318
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
331
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
340
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
347
348
349 #
350 # returns true, if the arity of "obj" matches
351 #
352 def check_arity(obj, n_args)
353 ary = obj.arity
354
355 if ary >= 0
356 n_args == ary
357 else
358 n_args >= (ary+1).abs
359 end
360 end
361
362
363
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
373
374 #
375 #
376 #
377 def handle(methodname, *args)
378 create().methodResponse(*call_method(methodname, *args))
379 end
380
381
382end
383
384
385=begin
386= XMLRPC::CGIServer
387== Synopsis
388 require "xmlrpc/server"
389
390 s = XMLRPC::CGIServer.new
391
392 s.add_handler("michael.add") do |a,b|
393 a + b
394 end
395
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
403
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
410
411== Description
412Implements a CGI-based XML-RPC server.
413
414== Superclass
415((<XMLRPC::BasicServer>))
416
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.
423
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
432
433class CGIServer < BasicServer
434 @@obj = nil
435
436 def CGIServer.new(*a)
437 @@obj = super(*a) if @@obj.nil?
438 @@obj
439 end
440
441 def initialize(*a)
442 super(*a)
443 end
444
445 def serve
446 catch(:exit_serve) {
447 length = ENV['CONTENT_LENGTH'].to_i
448
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
452
453 # TODO: do we need a call to binmode?
454 $stdin.binmode if $stdin.respond_to? :binmode
455 data = $stdin.read(length)
456
457 http_error(400, "Bad Request") if data.nil? or data.size != length
458
459 http_write(process(data), "Content-type" => "text/xml; charset=utf-8")
460 }
461 end
462
463
464 private
465
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
479
480 http_write(msg, "Status" => err, "Content-type" => "text/html")
481 throw :exit_serve # exit from the #serve method
482 end
483
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
489
490 str = ""
491 h.each {|key, value| str << "#{key}: #{value}\r\n"}
492 str << "\r\n#{body}"
493
494 print str
495 end
496
497end
498
499=begin
500= XMLRPC::ModRubyServer
501== Description
502Implements a XML-RPC server, which works with Apache mod_ruby.
503
504Use it in the same way as CGIServer!
505
506== Superclass
507((<XMLRPC::BasicServer>))
508=end
509
510class ModRubyServer < BasicServer
511
512 def initialize(*a)
513 @ap = Apache::request
514 super(*a)
515 end
516
517 def serve
518 catch(:exit_serve) {
519 header = {}
520 @ap.headers_in.each {|key, value| header[key.capitalize] = value}
521
522 length = header['Content-length'].to_i
523
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
527
528 # TODO: do we need a call to binmode?
529 @ap.binmode
530 data = @ap.read(length)
531
532 http_error(400, "Bad Request") if data.nil? or data.size != length
533
534 http_write(process(data), 200, "Content-type" => "text/xml; charset=utf-8")
535 }
536 end
537
538
539 private
540
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
554
555 http_write(msg, status, "Status" => err, "Content-type" => "text/html")
556 throw :exit_serve # exit from the #serve method
557 end
558
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
564
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
569
570 @ap.print body
571 end
572
573end
574
575=begin
576= XMLRPC::Server
577== Synopsis
578 require "xmlrpc/server"
579
580 s = XMLRPC::Server.new(8080)
581
582 s.add_handler("michael.add") do |a,b|
583 a + b
584 end
585
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
593
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
598
599 s.serve
600
601== Description
602Implements a standalone XML-RPC server. The method (({serve}))) is left if a SIGHUP is sent to the
603program.
604
605== Superclass
606((<XMLRPC::WEBrickServlet>))
607
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>)).
613
614 Parameters ((|audit|)) and ((|debug|)) are obsolete!
615
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.
622
623--- XMLRPC::Server#shutdown
624 Stops and shuts the server down.
625
626=end
627
628class WEBrickServlet < BasicServer; end # forward declaration
629
630class Server < WEBrickServlet
631
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 signal = 1
643 else
644 signal = "HUP"
645 end
646 trap(signal) { @server.shutdown }
647
648 @server.start
649 end
650
651 def shutdown
652 @server.shutdown
653 end
654
655end
656
657=begin
658= XMLRPC::WEBrickServlet
659== Synopsis
660
661 require "webrick"
662 require "xmlrpc/server"
663
664 s = XMLRPC::WEBrickServlet.new
665 s.add_handler("michael.add") do |a,b|
666 a + b
667 end
668
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
676
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
681
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
686
687== Instance Methods
688
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})).
692
693--- XMLRPC::WEBrickServlet#get_valid_ip
694 Return the via method ((<set_valid_ip|XMLRPC::Server#set_valid_ip>)) specified
695 valid IP addresses.
696
697== Description
698Implements a servlet for use with WEBrick, a pure Ruby (HTTP-) server framework.
699
700== Superclass
701((<XMLRPC::BasicServer>))
702
703=end
704
705class WEBrickServlet < BasicServer
706 def initialize(*a)
707 super
708 require "webrick/httpstatus"
709 @valid_ip = nil
710 end
711
712 # deprecated from WEBrick/1.2.2.
713 # but does not break anything.
714 def require_path_info?
715 false
716 end
717
718 def get_instance(config, *options)
719 # TODO: set config & options
720 self
721 end
722
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
730
731 def get_valid_ip
732 @valid_ip
733 end
734
735 def service(request, response)
736
737 if @valid_ip
738 raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip }
739 end
740
741 if request.request_method != "POST"
742 raise WEBrick::HTTPStatus::MethodNotAllowed,
743 "unsupported method `#{request.request_method}'."
744 end
745
746 if parse_content_type(request['Content-type']).first != "text/xml"
747 raise WEBrick::HTTPStatus::BadRequest
748 end
749
750 length = (request['Content-length'] || 0).to_i
751
752 raise WEBrick::HTTPStatus::LengthRequired unless length > 0
753
754 data = request.body
755
756 if data.nil? or data.size != length
757 raise WEBrick::HTTPStatus::BadRequest
758 end
759
760 resp = process(data)
761 if resp.nil? or resp.size <= 0
762 raise WEBrick::HTTPStatus::InternalServerError
763 end
764
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
770end
771
772
773end # module XMLRPC
774
775
776=begin
777= History
778 $Id: server.rb 11708 2007-02-12 23:01:19Z shyouhei $
779=end
780
Note: See TracBrowser for help on using the repository browser.