2012-09-12 22:22:10 -04:00
|
|
|
# xmlrpc/server.rb
|
|
|
|
# Copyright (C) 2001, 2002, 2003, 2005 by Michael Neumann (mneumann@ntecs.de)
|
|
|
|
#
|
|
|
|
# Released under the same term of license as Ruby.
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
require "xmlrpc/parser"
|
|
|
|
require "xmlrpc/create"
|
|
|
|
require "xmlrpc/config"
|
|
|
|
require "xmlrpc/utils" # ParserWriterChooseMixin
|
|
|
|
|
|
|
|
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
module XMLRPC # :nodoc:
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# This is the base class for all XML-RPC server-types (CGI, standalone).
|
|
|
|
# You can add handler and set a default handler.
|
|
|
|
# Do not use this server, as this is/should be an abstract class.
|
|
|
|
#
|
|
|
|
# === How the method to call is found
|
|
|
|
# The arity (number of accepted arguments) of a handler (method or Proc
|
|
|
|
# object) is compared to the given arguments submitted by the client for a
|
|
|
|
# RPC, or Remote Procedure Call.
|
|
|
|
#
|
|
|
|
# A handler is only called if it accepts the number of arguments, otherwise
|
|
|
|
# the search for another handler will go on. When at the end no handler was
|
|
|
|
# found, the default_handler, XMLRPC::BasicServer#set_default_handler will be
|
|
|
|
# called.
|
|
|
|
#
|
|
|
|
# With this technique it is possible to do overloading by number of parameters, but
|
|
|
|
# only for Proc handler, because you cannot define two methods of the same name in
|
|
|
|
# the same class.
|
2003-07-19 06:05:54 -04:00
|
|
|
class BasicServer
|
|
|
|
|
|
|
|
include ParserWriterChooseMixin
|
|
|
|
include ParseContentType
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
ERR_METHOD_MISSING = 1
|
2003-07-19 06:05:54 -04:00
|
|
|
ERR_UNCAUGHT_EXCEPTION = 2
|
|
|
|
ERR_MC_WRONG_PARAM = 3
|
|
|
|
ERR_MC_MISSING_PARAMS = 4
|
|
|
|
ERR_MC_MISSING_METHNAME = 5
|
|
|
|
ERR_MC_RECURSIVE_CALL = 6
|
|
|
|
ERR_MC_WRONG_PARAM_PARAMS = 7
|
|
|
|
ERR_MC_EXPECTED_STRUCT = 8
|
|
|
|
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Creates a new XMLRPC::BasicServer instance, which should not be
|
|
|
|
# done, because XMLRPC::BasicServer is an abstract class. This
|
|
|
|
# method should be called from a subclass indirectly by a +super+ call
|
|
|
|
# in the initialize method.
|
|
|
|
#
|
2013-12-07 05:27:27 -05:00
|
|
|
# The parameter +class_delim+ is used by add_handler, see
|
2012-09-12 22:22:10 -04:00
|
|
|
# XMLRPC::BasicServer#add_handler, when an object is added as a handler, to
|
|
|
|
# delimit the object-prefix and the method-name.
|
2003-07-19 06:05:54 -04:00
|
|
|
def initialize(class_delim=".")
|
|
|
|
@handler = []
|
2009-03-05 22:56:38 -05:00
|
|
|
@default_handler = nil
|
2003-07-19 06:05:54 -04:00
|
|
|
@service_hook = nil
|
|
|
|
|
|
|
|
@class_delim = class_delim
|
|
|
|
@create = nil
|
|
|
|
@parser = nil
|
|
|
|
|
|
|
|
add_multicall if Config::ENABLE_MULTICALL
|
|
|
|
add_introspection if Config::ENABLE_INTROSPECTION
|
|
|
|
end
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Adds +aBlock+ to the list of handlers, with +name+ as the name of
|
|
|
|
# the method.
|
|
|
|
#
|
|
|
|
# Parameters +signature+ and +help+ are used by the Introspection method if
|
|
|
|
# specified, where +signature+ is either an Array containing strings each
|
|
|
|
# representing a type of it's signature (the first is the return value) or
|
|
|
|
# an Array of Arrays if the method has multiple signatures.
|
|
|
|
#
|
|
|
|
# Value type-names are "int, boolean, double, string, dateTime.iso8601,
|
|
|
|
# base64, array, struct".
|
|
|
|
#
|
|
|
|
# Parameter +help+ is a String with information about how to call this method etc.
|
|
|
|
#
|
|
|
|
# When a method fails, it can tell the client by throwing an
|
|
|
|
# XMLRPC::FaultException like in this example:
|
|
|
|
#
|
|
|
|
# s.add_handler("michael.div") do |a,b|
|
|
|
|
# if b == 0
|
|
|
|
# raise XMLRPC::FaultException.new(1, "division by zero")
|
|
|
|
# else
|
|
|
|
# a / b
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# In the case of <code>b==0</code> the client gets an object back of type
|
|
|
|
# XMLRPC::FaultException that has a +faultCode+ and +faultString+ field.
|
|
|
|
#
|
|
|
|
# This is the second form of ((<add_handler|XMLRPC::BasicServer#add_handler>)).
|
|
|
|
# To add an object write:
|
|
|
|
#
|
|
|
|
# server.add_handler("michael", MyHandlerClass.new)
|
|
|
|
#
|
|
|
|
# All public methods of MyHandlerClass are accessible to
|
|
|
|
# the XML-RPC clients by <code>michael."name of method"</code>. This is
|
|
|
|
# where the +class_delim+ in XMLRPC::BasicServer.new plays it's role, a
|
|
|
|
# XML-RPC method-name is defined by +prefix+ + +class_delim+ + <code>"name
|
|
|
|
# of method"</code>.
|
|
|
|
#
|
|
|
|
# The third form of +add_handler is to use XMLRPC::Service::Interface to
|
|
|
|
# generate an object, which represents an interface (with signature and
|
|
|
|
# help text) for a handler class.
|
|
|
|
#
|
|
|
|
# The +interface+ parameter must be an instance of XMLRPC::Service::Interface.
|
|
|
|
# Adds all methods of +obj+ which are defined in the +interface+ to the server.
|
|
|
|
#
|
|
|
|
# This is the recommended way of adding services to a server!
|
2003-07-19 06:05:54 -04:00
|
|
|
def add_handler(prefix, obj_or_signature=nil, help=nil, &block)
|
|
|
|
if block_given?
|
|
|
|
# proc-handler
|
2009-03-05 22:56:38 -05:00
|
|
|
@handler << [prefix, block, obj_or_signature, help]
|
2003-07-19 06:05:54 -04:00
|
|
|
else
|
|
|
|
if prefix.kind_of? String
|
|
|
|
# class-handler
|
|
|
|
raise ArgumentError, "Expected non-nil value" if obj_or_signature.nil?
|
|
|
|
@handler << [prefix + @class_delim, obj_or_signature]
|
|
|
|
elsif prefix.kind_of? XMLRPC::Service::BasicInterface
|
|
|
|
# class-handler with interface
|
|
|
|
# add all methods
|
|
|
|
@handler += prefix.get_methods(obj_or_signature, @class_delim)
|
|
|
|
else
|
|
|
|
raise ArgumentError, "Wrong type for parameter 'prefix'"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Returns the service-hook, which is called on each service request (RPC)
|
|
|
|
# unless it's +nil+.
|
2003-07-19 06:05:54 -04:00
|
|
|
def get_service_hook
|
|
|
|
@service_hook
|
|
|
|
end
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# A service-hook is called for each service request (RPC).
|
|
|
|
#
|
|
|
|
# You can use a service-hook for example to wrap existing methods and catch
|
|
|
|
# exceptions of them or convert values to values recognized by XMLRPC.
|
|
|
|
#
|
|
|
|
# You can disable it by passing +nil+ as the +handler+ parameter.
|
|
|
|
#
|
|
|
|
# The service-hook is called with a Proc object along with any parameters.
|
|
|
|
#
|
|
|
|
# An example:
|
|
|
|
#
|
|
|
|
# server.set_service_hook {|obj, *args|
|
|
|
|
# begin
|
|
|
|
# ret = obj.call(*args) # call the original service-method
|
|
|
|
# # could convert the return value
|
|
|
|
# rescue
|
|
|
|
# # rescue exceptions
|
|
|
|
# end
|
|
|
|
# }
|
|
|
|
#
|
2003-07-19 06:05:54 -04:00
|
|
|
def set_service_hook(&handler)
|
|
|
|
@service_hook = handler
|
|
|
|
self
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Returns the default-handler, which is called when no handler for
|
|
|
|
# a method-name is found.
|
|
|
|
#
|
|
|
|
# It is either a Proc object or +nil+.
|
2003-07-19 06:05:54 -04:00
|
|
|
def get_default_handler
|
|
|
|
@default_handler
|
|
|
|
end
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Sets +handler+ as the default-handler, which is called when
|
|
|
|
# no handler for a method-name is found.
|
|
|
|
#
|
|
|
|
# +handler+ is a code-block.
|
|
|
|
#
|
|
|
|
# The default-handler is called with the (XML-RPC) method-name as first
|
|
|
|
# argument, and the other arguments are the parameters given by the
|
|
|
|
# client-call.
|
|
|
|
#
|
|
|
|
# If no block is specified the default of XMLRPC::BasicServer is
|
|
|
|
# used, which raises a XMLRPC::FaultException saying "method missing".
|
|
|
|
def set_default_handler(&handler)
|
2003-07-19 06:05:54 -04:00
|
|
|
@default_handler = handler
|
|
|
|
self
|
2009-03-05 22:56:38 -05:00
|
|
|
end
|
2003-07-19 06:05:54 -04:00
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Adds the multi-call handler <code>"system.multicall"</code>.
|
2003-07-19 06:05:54 -04:00
|
|
|
def add_multicall
|
|
|
|
add_handler("system.multicall", %w(array array), "Multicall Extension") do |arrStructs|
|
2009-03-05 22:56:38 -05:00
|
|
|
unless arrStructs.is_a? Array
|
2003-07-19 06:05:54 -04:00
|
|
|
raise XMLRPC::FaultException.new(ERR_MC_WRONG_PARAM, "system.multicall expects an array")
|
|
|
|
end
|
|
|
|
|
|
|
|
arrStructs.collect {|call|
|
|
|
|
if call.is_a? Hash
|
|
|
|
methodName = call["methodName"]
|
2009-03-05 22:56:38 -05:00
|
|
|
params = call["params"]
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
if params.nil?
|
|
|
|
multicall_fault(ERR_MC_MISSING_PARAMS, "Missing params")
|
|
|
|
elsif methodName.nil?
|
|
|
|
multicall_fault(ERR_MC_MISSING_METHNAME, "Missing methodName")
|
|
|
|
else
|
|
|
|
if methodName == "system.multicall"
|
|
|
|
multicall_fault(ERR_MC_RECURSIVE_CALL, "Recursive system.multicall forbidden")
|
|
|
|
else
|
|
|
|
unless params.is_a? Array
|
|
|
|
multicall_fault(ERR_MC_WRONG_PARAM_PARAMS, "Parameter params have to be an Array")
|
|
|
|
else
|
|
|
|
ok, val = call_method(methodName, *params)
|
|
|
|
if ok
|
|
|
|
# correct return value
|
|
|
|
[val]
|
|
|
|
else
|
|
|
|
# exception
|
2009-03-05 22:56:38 -05:00
|
|
|
multicall_fault(val.faultCode, val.faultString)
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
end
|
|
|
|
|
2003-07-19 06:05:54 -04:00
|
|
|
else
|
|
|
|
multicall_fault(ERR_MC_EXPECTED_STRUCT, "system.multicall expected struct")
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
}
|
2003-07-19 06:05:54 -04:00
|
|
|
end # end add_handler
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Adds the introspection handlers <code>"system.listMethods"</code>,
|
|
|
|
# <code>"system.methodSignature"</code> and
|
|
|
|
# <code>"system.methodHelp"</code>, where only the first one works.
|
2003-07-19 06:05:54 -04:00
|
|
|
def add_introspection
|
|
|
|
add_handler("system.listMethods",%w(array), "List methods available on this XML-RPC server") do
|
|
|
|
methods = []
|
|
|
|
@handler.each do |name, obj|
|
|
|
|
if obj.kind_of? Proc
|
|
|
|
methods << name
|
|
|
|
else
|
2008-12-17 18:15:56 -05:00
|
|
|
obj.class.public_instance_methods(false).each do |meth|
|
|
|
|
methods << "#{name}#{meth}"
|
|
|
|
end
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
methods
|
|
|
|
end
|
|
|
|
|
|
|
|
add_handler("system.methodSignature", %w(array string), "Returns method signature") do |meth|
|
|
|
|
sigs = []
|
|
|
|
@handler.each do |name, obj, sig|
|
|
|
|
if obj.kind_of? Proc and sig != nil and name == meth
|
|
|
|
if sig[0].kind_of? Array
|
|
|
|
# sig contains multiple signatures, e.g. [["array"], ["array", "string"]]
|
|
|
|
sig.each {|s| sigs << s}
|
|
|
|
else
|
|
|
|
# sig is a single signature, e.g. ["array"]
|
2009-03-05 22:56:38 -05:00
|
|
|
sigs << sig
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
sigs.uniq! || sigs # remove eventually duplicated signatures
|
|
|
|
end
|
|
|
|
|
|
|
|
add_handler("system.methodHelp", %w(string string), "Returns help on using this method") do |meth|
|
2009-03-05 22:56:38 -05:00
|
|
|
help = nil
|
2003-07-19 06:05:54 -04:00
|
|
|
@handler.each do |name, obj, sig, hlp|
|
2009-03-05 22:56:38 -05:00
|
|
|
if obj.kind_of? Proc and name == meth
|
2003-07-19 06:05:54 -04:00
|
|
|
help = hlp
|
2009-03-05 22:56:38 -05:00
|
|
|
break
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
help || ""
|
|
|
|
end
|
|
|
|
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2003-07-19 06:05:54 -04:00
|
|
|
def process(data)
|
2009-03-05 22:56:38 -05:00
|
|
|
method, params = parser().parseMethodCall(data)
|
2003-07-19 06:05:54 -04:00
|
|
|
handle(method, *params)
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
private
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
def multicall_fault(nr, str)
|
|
|
|
{"faultCode" => nr, "faultString" => str}
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2003-07-19 06:05:54 -04:00
|
|
|
def dispatch(methodname, *args)
|
|
|
|
for name, obj in @handler
|
|
|
|
if obj.kind_of? Proc
|
2005-07-10 16:37:32 -04:00
|
|
|
next unless methodname == name
|
2003-07-19 06:05:54 -04:00
|
|
|
else
|
2005-07-10 16:37:32 -04:00
|
|
|
next unless methodname =~ /^#{name}(.+)$/
|
2003-07-19 06:05:54 -04:00
|
|
|
next unless obj.respond_to? $1
|
2005-07-10 16:37:32 -04:00
|
|
|
obj = obj.method($1)
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
if check_arity(obj, args.size)
|
|
|
|
if @service_hook.nil?
|
2009-03-05 22:56:38 -05:00
|
|
|
return obj.call(*args)
|
2003-07-19 06:05:54 -04:00
|
|
|
else
|
|
|
|
return @service_hook.call(obj, *args)
|
|
|
|
end
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
end
|
|
|
|
|
2003-07-19 06:05:54 -04:00
|
|
|
if @default_handler.nil?
|
|
|
|
raise XMLRPC::FaultException.new(ERR_METHOD_MISSING, "Method #{methodname} missing or wrong number of parameters!")
|
|
|
|
else
|
2009-03-05 22:56:38 -05:00
|
|
|
@default_handler.call(methodname, *args)
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Returns +true+, if the arity of +obj+ matches +n_args+
|
2003-07-19 06:05:54 -04:00
|
|
|
def check_arity(obj, n_args)
|
|
|
|
ary = obj.arity
|
|
|
|
|
|
|
|
if ary >= 0
|
|
|
|
n_args == ary
|
|
|
|
else
|
2009-03-05 22:56:38 -05:00
|
|
|
n_args >= (ary+1).abs
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def call_method(methodname, *args)
|
|
|
|
begin
|
|
|
|
[true, dispatch(methodname, *args)]
|
2009-03-05 22:56:38 -05:00
|
|
|
rescue XMLRPC::FaultException => e
|
|
|
|
[false, e]
|
2003-07-19 06:05:54 -04:00
|
|
|
rescue Exception => e
|
|
|
|
[false, XMLRPC::FaultException.new(ERR_UNCAUGHT_EXCEPTION, "Uncaught exception #{e.message} in method #{methodname}")]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def handle(methodname, *args)
|
|
|
|
create().methodResponse(*call_method(methodname, *args))
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Implements a CGI-based XML-RPC server.
|
|
|
|
#
|
|
|
|
# require "xmlrpc/server"
|
|
|
|
#
|
|
|
|
# s = XMLRPC::CGIServer.new
|
|
|
|
#
|
|
|
|
# s.add_handler("michael.add") do |a,b|
|
|
|
|
# a + b
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# s.add_handler("michael.div") do |a,b|
|
|
|
|
# if b == 0
|
|
|
|
# raise XMLRPC::FaultException.new(1, "division by zero")
|
|
|
|
# else
|
|
|
|
# a / b
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# s.set_default_handler do |name, *args|
|
|
|
|
# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
|
|
|
|
# " or wrong number of parameters!")
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# s.serve
|
|
|
|
#
|
|
|
|
#
|
|
|
|
# <b>Note:</b> Make sure that you don't write to standard-output in a
|
|
|
|
# handler, or in any other part of your program, this would cause a CGI-based
|
|
|
|
# server to fail!
|
2003-07-19 06:05:54 -04:00
|
|
|
class CGIServer < BasicServer
|
|
|
|
@@obj = nil
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Creates a new XMLRPC::CGIServer instance.
|
|
|
|
#
|
|
|
|
# All parameters given are by-passed to XMLRPC::BasicServer.new.
|
|
|
|
#
|
|
|
|
# You can only create <b>one</b> XMLRPC::CGIServer instance, because more
|
|
|
|
# than one makes no sense.
|
2003-07-19 06:05:54 -04:00
|
|
|
def CGIServer.new(*a)
|
|
|
|
@@obj = super(*a) if @@obj.nil?
|
|
|
|
@@obj
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize(*a)
|
|
|
|
super(*a)
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Call this after you have added all you handlers to the server.
|
|
|
|
#
|
|
|
|
# This method processes a XML-RPC method call and sends the answer
|
|
|
|
# back to the client.
|
2003-07-19 06:05:54 -04:00
|
|
|
def serve
|
|
|
|
catch(:exit_serve) {
|
|
|
|
length = ENV['CONTENT_LENGTH'].to_i
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
http_error(405, "Method Not Allowed") unless ENV['REQUEST_METHOD'] == "POST"
|
2004-11-16 16:25:50 -05:00
|
|
|
http_error(400, "Bad Request") unless parse_content_type(ENV['CONTENT_TYPE']).first == "text/xml"
|
2009-03-05 22:56:38 -05:00
|
|
|
http_error(411, "Length Required") unless length > 0
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
# TODO: do we need a call to binmode?
|
|
|
|
$stdin.binmode if $stdin.respond_to? :binmode
|
|
|
|
data = $stdin.read(length)
|
|
|
|
|
2011-08-05 00:35:28 -04:00
|
|
|
http_error(400, "Bad Request") if data.nil? or data.bytesize != length
|
2003-07-19 06:05:54 -04:00
|
|
|
|
2005-10-05 12:15:16 -04:00
|
|
|
http_write(process(data), "Content-type" => "text/xml; charset=utf-8")
|
2003-07-19 06:05:54 -04:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def http_error(status, message)
|
|
|
|
err = "#{status} #{message}"
|
2009-03-05 22:56:38 -05:00
|
|
|
msg = <<-"MSGEND"
|
2003-07-19 06:05:54 -04:00
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>#{err}</title>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h1>#{err}</h1>
|
2013-05-19 10:50:47 -04:00
|
|
|
<p>Unexpected error occurred while processing XML-RPC request!</p>
|
2003-07-19 06:05:54 -04:00
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
MSGEND
|
|
|
|
|
|
|
|
http_write(msg, "Status" => err, "Content-type" => "text/html")
|
|
|
|
throw :exit_serve # exit from the #serve method
|
|
|
|
end
|
|
|
|
|
|
|
|
def http_write(body, header)
|
|
|
|
h = {}
|
|
|
|
header.each {|key, value| h[key.to_s.capitalize] = value}
|
|
|
|
h['Status'] ||= "200 OK"
|
2011-08-05 00:35:28 -04:00
|
|
|
h['Content-length'] ||= body.bytesize.to_s
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
str = ""
|
|
|
|
h.each {|key, value| str << "#{key}: #{value}\r\n"}
|
|
|
|
str << "\r\n#{body}"
|
|
|
|
|
|
|
|
print str
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Implements a XML-RPC server, which works with Apache mod_ruby.
|
|
|
|
#
|
|
|
|
# Use it in the same way as XMLRPC::CGIServer!
|
2003-07-19 06:05:54 -04:00
|
|
|
class ModRubyServer < BasicServer
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Creates a new XMLRPC::ModRubyServer instance.
|
|
|
|
#
|
|
|
|
# All parameters given are by-passed to XMLRPC::BasicServer.new.
|
2003-07-19 06:05:54 -04:00
|
|
|
def initialize(*a)
|
|
|
|
@ap = Apache::request
|
|
|
|
super(*a)
|
|
|
|
end
|
2003-10-07 07:22:16 -04:00
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Call this after you have added all you handlers to the server.
|
|
|
|
#
|
|
|
|
# This method processes a XML-RPC method call and sends the answer
|
|
|
|
# back to the client.
|
2003-07-19 06:05:54 -04:00
|
|
|
def serve
|
|
|
|
catch(:exit_serve) {
|
|
|
|
header = {}
|
2003-10-07 07:22:16 -04:00
|
|
|
@ap.headers_in.each {|key, value| header[key.capitalize] = value}
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
length = header['Content-length'].to_i
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
http_error(405, "Method Not Allowed") unless @ap.request_method == "POST"
|
2003-07-19 06:05:54 -04:00
|
|
|
http_error(400, "Bad Request") unless parse_content_type(header['Content-type']).first == "text/xml"
|
2009-03-05 22:56:38 -05:00
|
|
|
http_error(411, "Length Required") unless length > 0
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
# TODO: do we need a call to binmode?
|
|
|
|
@ap.binmode
|
|
|
|
data = @ap.read(length)
|
|
|
|
|
2011-08-05 00:35:28 -04:00
|
|
|
http_error(400, "Bad Request") if data.nil? or data.bytesize != length
|
2003-07-19 06:05:54 -04:00
|
|
|
|
2005-10-05 12:15:16 -04:00
|
|
|
http_write(process(data), 200, "Content-type" => "text/xml; charset=utf-8")
|
2003-07-19 06:05:54 -04:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def http_error(status, message)
|
|
|
|
err = "#{status} #{message}"
|
2009-03-05 22:56:38 -05:00
|
|
|
msg = <<-"MSGEND"
|
2003-07-19 06:05:54 -04:00
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<title>#{err}</title>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h1>#{err}</h1>
|
2013-05-19 10:50:47 -04:00
|
|
|
<p>Unexpected error occurred while processing XML-RPC request!</p>
|
2003-07-19 06:05:54 -04:00
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
MSGEND
|
|
|
|
|
|
|
|
http_write(msg, status, "Status" => err, "Content-type" => "text/html")
|
|
|
|
throw :exit_serve # exit from the #serve method
|
|
|
|
end
|
|
|
|
|
|
|
|
def http_write(body, status, header)
|
|
|
|
h = {}
|
|
|
|
header.each {|key, value| h[key.to_s.capitalize] = value}
|
|
|
|
h['Status'] ||= "200 OK"
|
2011-08-05 00:35:28 -04:00
|
|
|
h['Content-length'] ||= body.bytesize.to_s
|
2003-07-19 06:05:54 -04:00
|
|
|
|
2003-10-07 07:22:16 -04:00
|
|
|
h.each {|key, value| @ap.headers_out[key] = value }
|
2009-03-05 22:56:38 -05:00
|
|
|
@ap.content_type = h["Content-type"]
|
|
|
|
@ap.status = status.to_i
|
|
|
|
@ap.send_http_header
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
@ap.print body
|
|
|
|
end
|
|
|
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
2005-07-10 16:37:32 -04:00
|
|
|
class WEBrickServlet < BasicServer; end # forward declaration
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Implements a standalone XML-RPC server. The method XMLRPC::Server#serve is
|
|
|
|
# left if a SIGHUP is sent to the program.
|
|
|
|
#
|
|
|
|
# require "xmlrpc/server"
|
|
|
|
#
|
|
|
|
# s = XMLRPC::Server.new(8080)
|
|
|
|
#
|
|
|
|
# s.add_handler("michael.add") do |a,b|
|
|
|
|
# a + b
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# s.add_handler("michael.div") do |a,b|
|
|
|
|
# if b == 0
|
|
|
|
# raise XMLRPC::FaultException.new(1, "division by zero")
|
|
|
|
# else
|
|
|
|
# a / b
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# s.set_default_handler do |name, *args|
|
|
|
|
# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
|
|
|
|
# " or wrong number of parameters!")
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# s.serve
|
2005-07-10 16:37:32 -04:00
|
|
|
class Server < WEBrickServlet
|
2003-07-19 06:05:54 -04:00
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Creates a new XMLRPC::Server instance, which is a XML-RPC server
|
|
|
|
# listening on the given +port+ and accepts requests for the given +host+,
|
|
|
|
# which is +localhost+ by default.
|
|
|
|
#
|
|
|
|
# The server is not started, to start it you have to call
|
|
|
|
# XMLRPC::Server#serve.
|
|
|
|
#
|
|
|
|
# The optional +audit+ and +debug+ parameters are obsolete!
|
|
|
|
#
|
|
|
|
# All additionally provided parameters in <code>*a</code> are by-passed to
|
|
|
|
# XMLRPC::BasicServer.new.
|
2003-07-19 06:05:54 -04:00
|
|
|
def initialize(port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a)
|
|
|
|
super(*a)
|
2005-07-10 16:37:32 -04:00
|
|
|
require 'webrick'
|
2009-03-05 22:56:38 -05:00
|
|
|
@server = WEBrick::HTTPServer.new(:Port => port, :BindAddress => host, :MaxClients => maxConnections,
|
2005-07-10 16:37:32 -04:00
|
|
|
:Logger => WEBrick::Log.new(stdlog))
|
2005-10-04 15:46:35 -04:00
|
|
|
@server.mount("/", self)
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Call this after you have added all you handlers to the server.
|
|
|
|
# This method starts the server to listen for XML-RPC requests and answer them.
|
2003-07-19 06:05:54 -04:00
|
|
|
def serve
|
2009-02-01 18:12:52 -05:00
|
|
|
signals = %w[INT TERM HUP] & Signal.list.keys
|
2007-12-01 11:01:49 -05:00
|
|
|
signals.each { |signal| trap(signal) { @server.shutdown } }
|
2003-07-19 06:05:54 -04:00
|
|
|
|
2005-10-04 15:09:56 -04:00
|
|
|
@server.start
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Stops and shuts the server down.
|
2003-07-19 06:05:54 -04:00
|
|
|
def shutdown
|
|
|
|
@server.shutdown
|
|
|
|
end
|
2009-03-05 22:56:38 -05:00
|
|
|
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Implements a servlet for use with WEBrick, a pure Ruby (HTTP) server
|
|
|
|
# framework.
|
|
|
|
#
|
|
|
|
# require "webrick"
|
|
|
|
# require "xmlrpc/server"
|
|
|
|
#
|
|
|
|
# s = XMLRPC::WEBrickServlet.new
|
|
|
|
# s.add_handler("michael.add") do |a,b|
|
|
|
|
# a + b
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# s.add_handler("michael.div") do |a,b|
|
|
|
|
# if b == 0
|
|
|
|
# raise XMLRPC::FaultException.new(1, "division by zero")
|
|
|
|
# else
|
|
|
|
# a / b
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# s.set_default_handler do |name, *args|
|
|
|
|
# raise XMLRPC::FaultException.new(-99, "Method #{name} missing" +
|
|
|
|
# " or wrong number of parameters!")
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# httpserver = WEBrick::HTTPServer.new(:Port => 8080)
|
|
|
|
# httpserver.mount("/RPC2", s)
|
|
|
|
# trap("HUP") { httpserver.shutdown } # use 1 instead of "HUP" on Windows
|
|
|
|
# httpserver.start
|
2003-07-19 06:05:54 -04:00
|
|
|
class WEBrickServlet < BasicServer
|
|
|
|
def initialize(*a)
|
|
|
|
super
|
|
|
|
require "webrick/httpstatus"
|
2005-07-10 16:37:32 -04:00
|
|
|
@valid_ip = nil
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Deprecated from WEBrick/1.2.2, but does not break anything.
|
2003-07-19 06:05:54 -04:00
|
|
|
def require_path_info?
|
2009-03-05 22:56:38 -05:00
|
|
|
false
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def get_instance(config, *options)
|
|
|
|
# TODO: set config & options
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Specifies the valid IP addresses that are allowed to connect to the server.
|
|
|
|
#
|
|
|
|
# Each IP is either a String or a Regexp.
|
2005-07-10 16:37:32 -04:00
|
|
|
def set_valid_ip(*ip_addr)
|
|
|
|
if ip_addr.size == 1 and ip_addr[0].nil?
|
|
|
|
@valid_ip = nil
|
|
|
|
else
|
|
|
|
@valid_ip = ip_addr
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-09-12 22:22:10 -04:00
|
|
|
# Return the valid IP addresses that are allowed to connect to the server.
|
|
|
|
#
|
|
|
|
# See also, XMLRPC::Server#set_valid_ip
|
2005-07-10 16:37:32 -04:00
|
|
|
def get_valid_ip
|
|
|
|
@valid_ip
|
|
|
|
end
|
|
|
|
|
2003-07-19 06:05:54 -04:00
|
|
|
def service(request, response)
|
2005-07-10 16:37:32 -04:00
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
if @valid_ip
|
2005-07-10 16:37:32 -04:00
|
|
|
raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip }
|
|
|
|
end
|
|
|
|
|
2003-07-19 06:05:54 -04:00
|
|
|
if request.request_method != "POST"
|
2004-11-16 08:35:45 -05:00
|
|
|
raise WEBrick::HTTPStatus::MethodNotAllowed,
|
2003-07-19 06:05:54 -04:00
|
|
|
"unsupported method `#{request.request_method}'."
|
|
|
|
end
|
|
|
|
|
2009-03-05 22:56:38 -05:00
|
|
|
if parse_content_type(request['Content-type']).first != "text/xml"
|
2004-11-16 08:35:45 -05:00
|
|
|
raise WEBrick::HTTPStatus::BadRequest
|
2009-03-05 22:56:38 -05:00
|
|
|
end
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
length = (request['Content-length'] || 0).to_i
|
|
|
|
|
2004-11-16 08:35:45 -05:00
|
|
|
raise WEBrick::HTTPStatus::LengthRequired unless length > 0
|
2003-07-19 06:05:54 -04:00
|
|
|
|
|
|
|
data = request.body
|
|
|
|
|
2011-08-05 00:35:28 -04:00
|
|
|
if data.nil? or data.bytesize != length
|
2004-11-16 08:35:45 -05:00
|
|
|
raise WEBrick::HTTPStatus::BadRequest
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
resp = process(data)
|
2011-08-05 00:35:28 -04:00
|
|
|
if resp.nil? or resp.bytesize <= 0
|
2004-11-16 08:35:45 -05:00
|
|
|
raise WEBrick::HTTPStatus::InternalServerError
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
response.status = 200
|
2011-08-05 00:35:28 -04:00
|
|
|
response['Content-Length'] = resp.bytesize
|
2005-10-05 12:15:16 -04:00
|
|
|
response['Content-Type'] = "text/xml; charset=utf-8"
|
2009-03-05 22:56:38 -05:00
|
|
|
response.body = resp
|
2003-07-19 06:05:54 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
end # module XMLRPC
|
|
|
|
|
|
|
|
|
|
|
|
=begin
|
|
|
|
= History
|
2009-03-05 22:56:38 -05:00
|
|
|
$Id$
|
2003-07-19 06:05:54 -04:00
|
|
|
=end
|
|
|
|
|