1# xmlrpc/server.rb 2# Copyright (C) 2001, 2002, 2003, 2005 by Michael Neumann (mneumann@ntecs.de) 3# 4# Released under the same term of license as Ruby. 5 6require "xmlrpc/parser" 7require "xmlrpc/create" 8require "xmlrpc/config" 9require "xmlrpc/utils" # ParserWriterChooseMixin 10 11 12 13module XMLRPC # :nodoc: 14 15 16# This 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. 19# 20# === How the method to call is found 21# The arity (number of accepted arguments) of a handler (method or Proc 22# object) is compared to the given arguments submitted by the client for a 23# RPC, or Remote Procedure Call. 24# 25# A handler is only called if it accepts the number of arguments, otherwise 26# the search for another handler will go on. When at the end no handler was 27# found, the default_handler, XMLRPC::BasicServer#set_default_handler will be 28# called. 29# 30# With this technique it is possible to do overloading by number of parameters, but 31# only for Proc handler, because you cannot define two methods of the same name in 32# the same class. 33class BasicServer 34 35 include ParserWriterChooseMixin 36 include ParseContentType 37 38 ERR_METHOD_MISSING = 1 39 ERR_UNCAUGHT_EXCEPTION = 2 40 ERR_MC_WRONG_PARAM = 3 41 ERR_MC_MISSING_PARAMS = 4 42 ERR_MC_MISSING_METHNAME = 5 43 ERR_MC_RECURSIVE_CALL = 6 44 ERR_MC_WRONG_PARAM_PARAMS = 7 45 ERR_MC_EXPECTED_STRUCT = 8 46 47 48 # Creates a new XMLRPC::BasicServer instance, which should not be 49 # done, because XMLRPC::BasicServer is an abstract class. This 50 # method should be called from a subclass indirectly by a +super+ call 51 # in the initialize method. 52 # 53 # The paramter +class_delim+ is used by add_handler, see 54 # XMLRPC::BasicServer#add_handler, when an object is added as a handler, to 55 # delimit the object-prefix and the method-name. 56 def initialize(class_delim=".") 57 @handler = [] 58 @default_handler = nil 59 @service_hook = nil 60 61 @class_delim = class_delim 62 @create = nil 63 @parser = nil 64 65 add_multicall if Config::ENABLE_MULTICALL 66 add_introspection if Config::ENABLE_INTROSPECTION 67 end 68 69 # Adds +aBlock+ to the list of handlers, with +name+ as the name of 70 # the method. 71 # 72 # Parameters +signature+ and +help+ are used by the Introspection method if 73 # specified, where +signature+ is either an Array containing strings each 74 # representing a type of it's signature (the first is the return value) or 75 # an Array of Arrays if the method has multiple signatures. 76 # 77 # Value type-names are "int, boolean, double, string, dateTime.iso8601, 78 # base64, array, struct". 79 # 80 # Parameter +help+ is a String with information about how to call this method etc. 81 # 82 # When a method fails, it can tell the client by throwing an 83 # XMLRPC::FaultException like in this example: 84 # 85 # s.add_handler("michael.div") do |a,b| 86 # if b == 0 87 # raise XMLRPC::FaultException.new(1, "division by zero") 88 # else 89 # a / b 90 # end 91 # end 92 # 93 # In the case of <code>b==0</code> the client gets an object back of type 94 # XMLRPC::FaultException that has a +faultCode+ and +faultString+ field. 95 # 96 # This is the second form of ((<add_handler|XMLRPC::BasicServer#add_handler>)). 97 # To add an object write: 98 # 99 # server.add_handler("michael", MyHandlerClass.new) 100 # 101 # All public methods of MyHandlerClass are accessible to 102 # the XML-RPC clients by <code>michael."name of method"</code>. This is 103 # where the +class_delim+ in XMLRPC::BasicServer.new plays it's role, a 104 # XML-RPC method-name is defined by +prefix+ + +class_delim+ + <code>"name 105 # of method"</code>. 106 # 107 # The third form of +add_handler is to use XMLRPC::Service::Interface to 108 # generate an object, which represents an interface (with signature and 109 # help text) for a handler class. 110 # 111 # The +interface+ parameter must be an instance of XMLRPC::Service::Interface. 112 # Adds all methods of +obj+ which are defined in the +interface+ to the server. 113 # 114 # This is the recommended way of adding services to a server! 115 def add_handler(prefix, obj_or_signature=nil, help=nil, &block) 116 if block_given? 117 # proc-handler 118 @handler << [prefix, block, obj_or_signature, help] 119 else 120 if prefix.kind_of? String 121 # class-handler 122 raise ArgumentError, "Expected non-nil value" if obj_or_signature.nil? 123 @handler << [prefix + @class_delim, obj_or_signature] 124 elsif prefix.kind_of? XMLRPC::Service::BasicInterface 125 # class-handler with interface 126 # add all methods 127 @handler += prefix.get_methods(obj_or_signature, @class_delim) 128 else 129 raise ArgumentError, "Wrong type for parameter 'prefix'" 130 end 131 end 132 self 133 end 134 135 # Returns the service-hook, which is called on each service request (RPC) 136 # unless it's +nil+. 137 def get_service_hook 138 @service_hook 139 end 140 141 # A service-hook is called for each service request (RPC). 142 # 143 # You can use a service-hook for example to wrap existing methods and catch 144 # exceptions of them or convert values to values recognized by XMLRPC. 145 # 146 # You can disable it by passing +nil+ as the +handler+ parameter. 147 # 148 # The service-hook is called with a Proc object along with any parameters. 149 # 150 # An example: 151 # 152 # server.set_service_hook {|obj, *args| 153 # begin 154 # ret = obj.call(*args) # call the original service-method 155 # # could convert the return value 156 # rescue 157 # # rescue exceptions 158 # end 159 # } 160 # 161 def set_service_hook(&handler) 162 @service_hook = handler 163 self 164 end 165 166 # Returns the default-handler, which is called when no handler for 167 # a method-name is found. 168 # 169 # It is either a Proc object or +nil+. 170 def get_default_handler 171 @default_handler 172 end 173 174 # Sets +handler+ as the default-handler, which is called when 175 # no handler for a method-name is found. 176 # 177 # +handler+ is a code-block. 178 # 179 # The default-handler is called with the (XML-RPC) method-name as first 180 # argument, and the other arguments are the parameters given by the 181 # client-call. 182 # 183 # If no block is specified the default of XMLRPC::BasicServer is 184 # used, which raises a XMLRPC::FaultException saying "method missing". 185 def set_default_handler(&handler) 186 @default_handler = handler 187 self 188 end 189 190 # Adds the multi-call handler <code>"system.multicall"</code>. 191 def add_multicall 192 add_handler("system.multicall", %w(array array), "Multicall Extension") do |arrStructs| 193 unless arrStructs.is_a? Array 194 raise XMLRPC::FaultException.new(ERR_MC_WRONG_PARAM, "system.multicall expects an array") 195 end 196 197 arrStructs.collect {|call| 198 if call.is_a? Hash 199 methodName = call["methodName"] 200 params = call["params"] 201 202 if params.nil? 203 multicall_fault(ERR_MC_MISSING_PARAMS, "Missing params") 204 elsif methodName.nil? 205 multicall_fault(ERR_MC_MISSING_METHNAME, "Missing methodName") 206 else 207 if methodName == "system.multicall" 208 multicall_fault(ERR_MC_RECURSIVE_CALL, "Recursive system.multicall forbidden") 209 else 210 unless params.is_a? Array 211 multicall_fault(ERR_MC_WRONG_PARAM_PARAMS, "Parameter params have to be an Array") 212 else 213 ok, val = call_method(methodName, *params) 214 if ok 215 # correct return value 216 [val] 217 else 218 # exception 219 multicall_fault(val.faultCode, val.faultString) 220 end 221 end 222 end 223 end 224 225 else 226 multicall_fault(ERR_MC_EXPECTED_STRUCT, "system.multicall expected struct") 227 end 228 } 229 end # end add_handler 230 self 231 end 232 233 # Adds the introspection handlers <code>"system.listMethods"</code>, 234 # <code>"system.methodSignature"</code> and 235 # <code>"system.methodHelp"</code>, where only the first one works. 236 def add_introspection 237 add_handler("system.listMethods",%w(array), "List methods available on this XML-RPC server") do 238 methods = [] 239 @handler.each do |name, obj| 240 if obj.kind_of? Proc 241 methods << name 242 else 243 obj.class.public_instance_methods(false).each do |meth| 244 methods << "#{name}#{meth}" 245 end 246 end 247 end 248 methods 249 end 250 251 add_handler("system.methodSignature", %w(array string), "Returns method signature") do |meth| 252 sigs = [] 253 @handler.each do |name, obj, sig| 254 if obj.kind_of? Proc and sig != nil and name == meth 255 if sig[0].kind_of? Array 256 # sig contains multiple signatures, e.g. [["array"], ["array", "string"]] 257 sig.each {|s| sigs << s} 258 else 259 # sig is a single signature, e.g. ["array"] 260 sigs << sig 261 end 262 end 263 end 264 sigs.uniq! || sigs # remove eventually duplicated signatures 265 end 266 267 add_handler("system.methodHelp", %w(string string), "Returns help on using this method") do |meth| 268 help = nil 269 @handler.each do |name, obj, sig, hlp| 270 if obj.kind_of? Proc and name == meth 271 help = hlp 272 break 273 end 274 end 275 help || "" 276 end 277 278 self 279 end 280 281 282 283 def process(data) 284 method, params = parser().parseMethodCall(data) 285 handle(method, *params) 286 end 287 288 private 289 290 def multicall_fault(nr, str) 291 {"faultCode" => nr, "faultString" => str} 292 end 293 294 def dispatch(methodname, *args) 295 for name, obj in @handler 296 if obj.kind_of? Proc 297 next unless methodname == name 298 else 299 next unless methodname =~ /^#{name}(.+)$/ 300 next unless obj.respond_to? $1 301 obj = obj.method($1) 302 end 303 304 if check_arity(obj, args.size) 305 if @service_hook.nil? 306 return obj.call(*args) 307 else 308 return @service_hook.call(obj, *args) 309 end 310 end 311 end 312 313 if @default_handler.nil? 314 raise XMLRPC::FaultException.new(ERR_METHOD_MISSING, "Method #{methodname} missing or wrong number of parameters!") 315 else 316 @default_handler.call(methodname, *args) 317 end 318 end 319 320 321 # Returns +true+, if the arity of +obj+ matches +n_args+ 322 def check_arity(obj, n_args) 323 ary = obj.arity 324 325 if ary >= 0 326 n_args == ary 327 else 328 n_args >= (ary+1).abs 329 end 330 end 331 332 333 334 def call_method(methodname, *args) 335 begin 336 [true, dispatch(methodname, *args)] 337 rescue XMLRPC::FaultException => e 338 [false, e] 339 rescue Exception => e 340 [false, XMLRPC::FaultException.new(ERR_UNCAUGHT_EXCEPTION, "Uncaught exception #{e.message} in method #{methodname}")] 341 end 342 end 343 344 def handle(methodname, *args) 345 create().methodResponse(*call_method(methodname, *args)) 346 end 347 348 349end 350 351 352# Implements a CGI-based XML-RPC server. 353# 354# require "xmlrpc/server" 355# 356# s = XMLRPC::CGIServer.new 357# 358# s.add_handler("michael.add") do |a,b| 359# a + b 360# end 361# 362# s.add_handler("michael.div") do |a,b| 363# if b == 0 364# raise XMLRPC::FaultException.new(1, "division by zero") 365# else 366# a / b 367# end 368# end 369# 370# s.set_default_handler do |name, *args| 371# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" + 372# " or wrong number of parameters!") 373# end 374# 375# s.serve 376# 377# 378# <b>Note:</b> Make sure that you don't write to standard-output in a 379# handler, or in any other part of your program, this would cause a CGI-based 380# server to fail! 381class CGIServer < BasicServer 382 @@obj = nil 383 384 # Creates a new XMLRPC::CGIServer instance. 385 # 386 # All parameters given are by-passed to XMLRPC::BasicServer.new. 387 # 388 # You can only create <b>one</b> XMLRPC::CGIServer instance, because more 389 # than one makes no sense. 390 def CGIServer.new(*a) 391 @@obj = super(*a) if @@obj.nil? 392 @@obj 393 end 394 395 def initialize(*a) 396 super(*a) 397 end 398 399 # Call this after you have added all you handlers to the server. 400 # 401 # This method processes a XML-RPC method call and sends the answer 402 # back to the client. 403 def serve 404 catch(:exit_serve) { 405 length = ENV['CONTENT_LENGTH'].to_i 406 407 http_error(405, "Method Not Allowed") unless ENV['REQUEST_METHOD'] == "POST" 408 http_error(400, "Bad Request") unless parse_content_type(ENV['CONTENT_TYPE']).first == "text/xml" 409 http_error(411, "Length Required") unless length > 0 410 411 # TODO: do we need a call to binmode? 412 $stdin.binmode if $stdin.respond_to? :binmode 413 data = $stdin.read(length) 414 415 http_error(400, "Bad Request") if data.nil? or data.bytesize != length 416 417 http_write(process(data), "Content-type" => "text/xml; charset=utf-8") 418 } 419 end 420 421 422 private 423 424 def http_error(status, message) 425 err = "#{status} #{message}" 426 msg = <<-"MSGEND" 427 <html> 428 <head> 429 <title>#{err}</title> 430 </head> 431 <body> 432 <h1>#{err}</h1> 433 <p>Unexpected error occurred while processing XML-RPC request!</p> 434 </body> 435 </html> 436 MSGEND 437 438 http_write(msg, "Status" => err, "Content-type" => "text/html") 439 throw :exit_serve # exit from the #serve method 440 end 441 442 def http_write(body, header) 443 h = {} 444 header.each {|key, value| h[key.to_s.capitalize] = value} 445 h['Status'] ||= "200 OK" 446 h['Content-length'] ||= body.bytesize.to_s 447 448 str = "" 449 h.each {|key, value| str << "#{key}: #{value}\r\n"} 450 str << "\r\n#{body}" 451 452 print str 453 end 454 455end 456 457 458# Implements a XML-RPC server, which works with Apache mod_ruby. 459# 460# Use it in the same way as XMLRPC::CGIServer! 461class ModRubyServer < BasicServer 462 463 # Creates a new XMLRPC::ModRubyServer instance. 464 # 465 # All parameters given are by-passed to XMLRPC::BasicServer.new. 466 def initialize(*a) 467 @ap = Apache::request 468 super(*a) 469 end 470 471 # Call this after you have added all you handlers to the server. 472 # 473 # This method processes a XML-RPC method call and sends the answer 474 # back to the client. 475 def serve 476 catch(:exit_serve) { 477 header = {} 478 @ap.headers_in.each {|key, value| header[key.capitalize] = value} 479 480 length = header['Content-length'].to_i 481 482 http_error(405, "Method Not Allowed") unless @ap.request_method == "POST" 483 http_error(400, "Bad Request") unless parse_content_type(header['Content-type']).first == "text/xml" 484 http_error(411, "Length Required") unless length > 0 485 486 # TODO: do we need a call to binmode? 487 @ap.binmode 488 data = @ap.read(length) 489 490 http_error(400, "Bad Request") if data.nil? or data.bytesize != length 491 492 http_write(process(data), 200, "Content-type" => "text/xml; charset=utf-8") 493 } 494 end 495 496 497 private 498 499 def http_error(status, message) 500 err = "#{status} #{message}" 501 msg = <<-"MSGEND" 502 <html> 503 <head> 504 <title>#{err}</title> 505 </head> 506 <body> 507 <h1>#{err}</h1> 508 <p>Unexpected error occurred while processing XML-RPC request!</p> 509 </body> 510 </html> 511 MSGEND 512 513 http_write(msg, status, "Status" => err, "Content-type" => "text/html") 514 throw :exit_serve # exit from the #serve method 515 end 516 517 def http_write(body, status, header) 518 h = {} 519 header.each {|key, value| h[key.to_s.capitalize] = value} 520 h['Status'] ||= "200 OK" 521 h['Content-length'] ||= body.bytesize.to_s 522 523 h.each {|key, value| @ap.headers_out[key] = value } 524 @ap.content_type = h["Content-type"] 525 @ap.status = status.to_i 526 @ap.send_http_header 527 528 @ap.print body 529 end 530 531end 532 533 534class WEBrickServlet < BasicServer; end # forward declaration 535 536# Implements a standalone XML-RPC server. The method XMLRPC::Server#serve is 537# left if a SIGHUP is sent to the program. 538# 539# require "xmlrpc/server" 540# 541# s = XMLRPC::Server.new(8080) 542# 543# s.add_handler("michael.add") do |a,b| 544# a + b 545# end 546# 547# s.add_handler("michael.div") do |a,b| 548# if b == 0 549# raise XMLRPC::FaultException.new(1, "division by zero") 550# else 551# a / b 552# end 553# end 554# 555# s.set_default_handler do |name, *args| 556# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" + 557# " or wrong number of parameters!") 558# end 559# 560# s.serve 561class Server < WEBrickServlet 562 563 # Creates a new XMLRPC::Server instance, which is a XML-RPC server 564 # listening on the given +port+ and accepts requests for the given +host+, 565 # which is +localhost+ by default. 566 # 567 # The server is not started, to start it you have to call 568 # XMLRPC::Server#serve. 569 # 570 # The optional +audit+ and +debug+ parameters are obsolete! 571 # 572 # All additionally provided parameters in <code>*a</code> are by-passed to 573 # XMLRPC::BasicServer.new. 574 def initialize(port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a) 575 super(*a) 576 require 'webrick' 577 @server = WEBrick::HTTPServer.new(:Port => port, :BindAddress => host, :MaxClients => maxConnections, 578 :Logger => WEBrick::Log.new(stdlog)) 579 @server.mount("/", self) 580 end 581 582 # Call this after you have added all you handlers to the server. 583 # This method starts the server to listen for XML-RPC requests and answer them. 584 def serve 585 signals = %w[INT TERM HUP] & Signal.list.keys 586 signals.each { |signal| trap(signal) { @server.shutdown } } 587 588 @server.start 589 end 590 591 # Stops and shuts the server down. 592 def shutdown 593 @server.shutdown 594 end 595 596end 597 598 599# Implements a servlet for use with WEBrick, a pure Ruby (HTTP) server 600# framework. 601# 602# require "webrick" 603# require "xmlrpc/server" 604# 605# s = XMLRPC::WEBrickServlet.new 606# s.add_handler("michael.add") do |a,b| 607# a + b 608# end 609# 610# s.add_handler("michael.div") do |a,b| 611# if b == 0 612# raise XMLRPC::FaultException.new(1, "division by zero") 613# else 614# a / b 615# end 616# end 617# 618# s.set_default_handler do |name, *args| 619# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" + 620# " or wrong number of parameters!") 621# end 622# 623# httpserver = WEBrick::HTTPServer.new(:Port => 8080) 624# httpserver.mount("/RPC2", s) 625# trap("HUP") { httpserver.shutdown } # use 1 instead of "HUP" on Windows 626# httpserver.start 627class WEBrickServlet < BasicServer 628 def initialize(*a) 629 super 630 require "webrick/httpstatus" 631 @valid_ip = nil 632 end 633 634 # Deprecated from WEBrick/1.2.2, but does not break anything. 635 def require_path_info? 636 false 637 end 638 639 def get_instance(config, *options) 640 # TODO: set config & options 641 self 642 end 643 644 # Specifies the valid IP addresses that are allowed to connect to the server. 645 # 646 # Each IP is either a String or a Regexp. 647 def set_valid_ip(*ip_addr) 648 if ip_addr.size == 1 and ip_addr[0].nil? 649 @valid_ip = nil 650 else 651 @valid_ip = ip_addr 652 end 653 end 654 655 # Return the valid IP addresses that are allowed to connect to the server. 656 # 657 # See also, XMLRPC::Server#set_valid_ip 658 def get_valid_ip 659 @valid_ip 660 end 661 662 def service(request, response) 663 664 if @valid_ip 665 raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip } 666 end 667 668 if request.request_method != "POST" 669 raise WEBrick::HTTPStatus::MethodNotAllowed, 670 "unsupported method `#{request.request_method}'." 671 end 672 673 if parse_content_type(request['Content-type']).first != "text/xml" 674 raise WEBrick::HTTPStatus::BadRequest 675 end 676 677 length = (request['Content-length'] || 0).to_i 678 679 raise WEBrick::HTTPStatus::LengthRequired unless length > 0 680 681 data = request.body 682 683 if data.nil? or data.bytesize != length 684 raise WEBrick::HTTPStatus::BadRequest 685 end 686 687 resp = process(data) 688 if resp.nil? or resp.bytesize <= 0 689 raise WEBrick::HTTPStatus::InternalServerError 690 end 691 692 response.status = 200 693 response['Content-Length'] = resp.bytesize 694 response['Content-Type'] = "text/xml; charset=utf-8" 695 response.body = resp 696 end 697end 698 699 700end # module XMLRPC 701 702 703=begin 704= History 705 $Id: server.rb 44391 2013-12-24 15:46:01Z nagachika $ 706=end 707 708