1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

* lib/xmlrpc: import.

* eval.c (thgroup_add): should return group for terminated thread
  case.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4102 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
matz 2003-07-19 10:05:54 +00:00
parent c7d2ebd71e
commit d675dbc279
15 changed files with 3370 additions and 2 deletions

View file

@ -4,6 +4,11 @@ Sat Jul 19 19:03:24 2003 Takaaki Uematsu <uema2x@jcom.home.ne.jp>
Sat Jul 19 11:27:25 2003 Yukihiro Matsumoto <matz@ruby-lang.org>
* lib/xmlrpc: import.
* eval.c (thgroup_add): should return group for terminated thread
case.
* eval.c (thgroup_add): do not raise ThreadError on terminated
thread addition for compatibility. just warning.

View file

@ -141,6 +141,7 @@ lib/forwardable.rb
lib/ftools.rb
lib/getoptlong.rb
lib/getopts.rb
lib/gserver.rb
lib/importenv.rb
lib/ipaddr.rb
lib/irb.rb
@ -317,6 +318,16 @@ lib/rexml/parsers/sax2parser.rb
lib/rexml/parsers/streamparser.rb
lib/rexml/parsers/ultralightparser.rb
lib/rexml/parsers/xpathparser.rb
lib/xmlrpc/base64.rb
lib/xmlrpc/client.rb
lib/xmlrpc/config.rb
lib/xmlrpc/create.rb
lib/xmlrpc/datetime.rb
lib/xmlrpc/httpserver.rb
lib/xmlrpc/marshal.rb
lib/xmlrpc/parser.rb
lib/xmlrpc/server.rb
lib/xmlrpc/utils.rb
lib/yaml.rb
lib/yaml/basenode.rb
lib/yaml/constants.rb

2
config.guess vendored
View file

@ -1119,7 +1119,7 @@ EOF
*86) UNAME_PROCESSOR=i686 ;;
powerpc) UNAME_PROCESSOR=powerpc ;;
esac
echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE}
echo ${UNAME_PROCESSOR}-apple-darwin
exit 0 ;;
*:procnto*:*:* | *:QNX:[0123456789]*:*)
UNAME_PROCESSOR=`uname -p`

2
eval.c
View file

@ -9944,7 +9944,7 @@ thgroup_add(group, thread)
if (!th->thgroup) {
rb_warn("terminated thread");
return;
return group;
}
if (OBJ_FROZEN(th->thgroup)) {
rb_raise(rb_eThreadError, "can't move from the frozen thread group");

175
lib/gserver.rb Normal file
View file

@ -0,0 +1,175 @@
# Copyright (C) 2001 John W. Small All Rights Reserved
# mailto:jsmall@laser.net subject:ruby-generic-server
# Freeware
require "socket"
require "thread"
class GServer
DEFAULT_HOST = "127.0.0.1"
def serve(io)
end
@@services = {} # Hash of opened ports, i.e. services
@@servicesMutex = Mutex.new
def GServer.stop(port, host = DEFAULT_HOST)
@@servicesMutex.synchronize {
@@services[host][port].stop
}
end
def GServer.in_service?(port, host = DEFAULT_HOST)
@@services.has_key?(host) and
@@services[host].has_key?(port)
end
def stop
@connectionsMutex.synchronize {
if @tcpServerThread
@tcpServerThread.raise "stop"
end
}
end
def stopped?
@tcpServerThread == nil
end
def shutdown
@shutdown = true
end
def connections
@connections.size
end
def join
@tcpServerThread.join if @tcpServerThread
end
attr_reader :port, :host, :maxConnections
attr_accessor :stdlog, :audit, :debug
def connecting(client)
addr = client.peeraddr
log("#{self.class.to_s} #{@host}:#{@port} client:#{addr[1]} " +
"#{addr[2]}<#{addr[3]}> connect")
true
end
def disconnecting(clientPort)
log("#{self.class.to_s} #{@host}:#{@port} " +
"client:#{clientPort} disconnect")
end
protected :connecting, :disconnecting
def starting()
log("#{self.class.to_s} #{@host}:#{@port} start")
end
def stopping()
log("#{self.class.to_s} #{@host}:#{@port} stop")
end
protected :starting, :stopping
def error(detail)
log(detail.backtrace.join("\n"))
end
def log(msg)
if @stdlog
@stdlog.puts("[#{Time.new.ctime}] %s" % msg)
@stdlog.flush
end
end
protected :error, :log
def initialize(port, host = DEFAULT_HOST, maxConnections = 4,
stdlog = $stderr, audit = false, debug = false)
@tcpServerThread = nil
@port = port
@host = host
@maxConnections = maxConnections
@connections = []
@connectionsMutex = Mutex.new
@connectionsCV = ConditionVariable.new
@stdlog = stdlog
@audit = audit
@debug = debug
end
def start(maxConnections = -1)
raise "running" if !stopped?
@shutdown = false
@maxConnections = maxConnections if maxConnections > 0
@@servicesMutex.synchronize {
if GServer.in_service?(@port,@host)
raise "Port already in use: #{host}:#{@port}!"
end
@tcpServer = TCPServer.new(@host,@port)
@port = @tcpServer.addr[1]
@@services[@host] = {} unless @@services.has_key?(@host)
@@services[@host][@port] = self;
}
@tcpServerThread = Thread.new {
begin
starting if @audit
while !@shutdown
@connectionsMutex.synchronize {
while @connections.size >= @maxConnections
@connectionsCV.wait(@connectionsMutex)
end
}
client = @tcpServer.accept
@connections << Thread.new(client) { |myClient|
begin
myPort = myClient.peeraddr[1]
serve(myClient) if !@audit or connecting(myClient)
rescue => detail
error(detail) if @debug
ensure
begin
myClient.close
rescue
end
@connectionsMutex.synchronize {
@connections.delete(Thread.current)
@connectionsCV.signal
}
disconnecting(myPort) if @audit
end
}
end
rescue => detail
error(detail) if @debug
ensure
begin
@tcpServer.close
rescue
end
if @shutdown
@connectionsMutex.synchronize {
while @connections.size > 0
@connectionsCV.wait(@connectionsMutex)
end
}
else
@connections.each { |c| c.raise "stop" }
end
@tcpServerThread = nil
@@servicesMutex.synchronize {
@@services[@host].delete(@port)
}
stopping if @audit
end
}
self
end
end

81
lib/xmlrpc/base64.rb Normal file
View file

@ -0,0 +1,81 @@
=begin
= xmlrpc/base64.rb
Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
Released under the same term of license as Ruby.
= Classes
* ((<XMLRPC::Base64>))
= XMLRPC::Base64
== Description
This class is necessary for (('xmlrpc4r')) to determine that a string should
be transmitted base64-encoded and not as a raw-string.
You can use (({XMLRPC::Base64})) on the client and server-side as a
parameter and/or return-value.
== Class Methods
--- XMLRPC::Base64.new( str, state = :dec )
Creates a new (({XMLRPC::Base64})) instance with string ((|str|)) as the
internal string. When ((|state|)) is (({:dec})) it assumes that the
string ((|str|)) is not in base64 format (perhaps already decoded),
otherwise if ((|state|)) is (({:enc})) it decodes ((|str|))
and stores it as the internal string.
--- XMLRPC::Base64.decode( str )
Decodes string ((|str|)) with base64 and returns that value.
--- XMLRPC::Base64.encode( str )
Encodes string ((|str|)) with base64 and returns that value.
== Instance Methods
--- XMLRPC::Base64#decoded
Returns the internal string decoded.
--- XMLRPC::Base64#encoded
Returns the internal string encoded with base64.
=end
module XMLRPC
class Base64
def initialize(str, state = :dec)
case state
when :enc
@str = Base64.decode(str)
when :dec
@str = str
else
raise ArgumentError, "wrong argument; either :enc or :dec"
end
end
def decoded
@str
end
def encoded
Base64.encode(@str)
end
def Base64.decode(str)
str.gsub(/\s+/, "").unpack("m")[0]
end
def Base64.encode(str)
[str].pack("m")
end
end
end # module XMLRPC
=begin
= History
$Id$
=end

570
lib/xmlrpc/client.rb Normal file
View file

@ -0,0 +1,570 @@
=begin
= xmlrpc/client.rb
Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
Released under the same term of license as Ruby.
= Classes
* ((<XMLRPC::Client>))
* ((<XMLRPC::Client::Proxy>))
= XMLRPC::Client
== Synopsis
require "xmlrpc/client"
server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
begin
param = server.call("michael.add", 4, 5)
puts "4 + 5 = #{param}"
rescue XMLRPC::FaultException => e
puts "Error:"
puts e.faultCode
puts e.faultString
end
or
require "xmlrpc/client"
server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
ok, param = server.call2("michael.add", 4, 5)
if ok then
puts "4 + 5 = #{param}"
else
puts "Error:"
puts param.faultCode
puts param.faultString
end
== Description
Class (({XMLRPC::Client})) provides remote procedure calls to a XML-RPC server.
After setting the connection-parameters with ((<XMLRPC::Client.new>)) which
creates a new (({XMLRPC::Client})) instance, you can execute a remote procedure
by sending the ((<call|XMLRPC::Client#call>)) or ((<call2|XMLRPC::Client#call2>))
message to this new instance. The given parameters indicate which method to
call on the remote-side and of course the parameters for the remote procedure.
== Class Methods
--- XMLRPC::Client.new( host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil, user=nil, password=nil, use_ssl=false, timeout =nil)
Creates an object which represents the remote XML-RPC server on the
given host ((|host|)). If the server is CGI-based, ((|path|)) is the
path to the CGI-script, which will be called, otherwise (in the
case of a standalone server) ((|path|)) should be (({"/RPC2"})).
((|port|)) is the port on which the XML-RPC server listens.
If ((|proxy_host|)) is given, then a proxy server listening at
((|proxy_host|)) is used. ((|proxy_port|)) is the port of the
proxy server.
Default values for ((|host|)), ((|path|)) and ((|port|)) are 'localhost', '/RPC2' and
'80' respectively using SSL '443'.
If ((|user|)) and ((|password|)) are given, each time a request is send,
a Authorization header is send. Currently only Basic Authentification is
implemented no Digest.
If ((|use_ssl|)) is set to (({true})), comunication over SSL is enabled.
Note, that you need the SSL package from RAA installed.
Parameter ((|timeout|)) is the time to wait for a XML-RPC response, defaults to 30.
--- XMLRPC::Client.new2( uri, proxy=nil, timeout=nil)
: uri
URI specifying protocol (http or https), host, port, path, user and password.
Example: https://user:password@host:port/path
: proxy
Is of the form "host:port".
: timeout
Defaults to 30.
--- XMLRPC::Client.new3( hash={} )
Parameter ((|hash|)) has following case-insensitive keys:
* host
* path
* port
* proxy_host
* proxy_port
* user
* password
* use_ssl
* timeout
Calls ((<XMLRPC::Client.new>)) with the corresponding values.
== Instance Methods
--- XMLRPC::Client#call( method, *args )
Invokes the method named ((|method|)) with the parameters given by
((|args|)) on the XML-RPC server.
The parameter ((|method|)) is converted into a (({String})) and should
be a valid XML-RPC method-name.
Each parameter of ((|args|)) must be of one of the following types,
where (({Hash})), (({Struct})) and (({Array})) can contain any of these listed ((:types:)):
* (({Fixnum})), (({Bignum}))
* (({TrueClass})), (({FalseClass})) ((({true})), (({false})))
* (({String})), (({Symbol}))
* (({Float}))
* (({Hash})), (({Struct}))
* (({Array}))
* (({Date})), (({Time})), (({XMLRPC::DateTime}))
* (({XMLRPC::Base64}))
* A Ruby object which class includes XMLRPC::Marshallable (only if Config::ENABLE_MARSHALLABLE is (({true}))).
That object is converted into a hash, with one additional key/value pair "___class___" which contains the class name
for restoring later that object.
The method returns the return-value from the RPC
((-stands for Remote Procedure Call-)).
The type of the return-value is one of the above shown,
only that a (({Bignum})) is only allowed when it fits in 32-bit and
that a XML-RPC (('dateTime.iso8601')) type is always returned as
a ((<(({XMLRPC::DateTime}))|URL:datetime.html>)) object and
a (({Struct})) is never returned, only a (({Hash})), the same for a (({Symbol})), where
always a (({String})) is returned.
A (({XMLRPC::Base64})) is returned as a (({String})) from xmlrpc4r version 1.6.1 on.
If the remote procedure returned a fault-structure, then a
(({XMLRPC::FaultException})) exception is raised, which has two accessor-methods
(({faultCode})) and (({faultString})) of type (({Integer})) and (({String})).
--- XMLRPC::Client#call2( method, *args )
The difference between this method and ((<call|XMLRPC::Client#call>)) is, that
this method do ((*not*)) raise a (({XMLRPC::FaultException})) exception.
The method returns an array of two values. The first value indicates if
the second value is a return-value ((({true}))) or an object of type
(({XMLRPC::FaultException})).
Both are explained in ((<call|XMLRPC::Client#call>)).
--- XMLRPC::Client#multicall( *methods )
You can use this method to execute several methods on a XMLRPC server which supports
the multi-call extension.
Example:
s.multicall(
['michael.add', 3, 4],
['michael.sub', 4, 5]
)
# => [7, -1]
--- XMLRPC::Client#multicall2( *methods )
Same as ((<XMLRPC::Client#multicall>)), but returns like ((<XMLRPC::Client#call2>)) two parameters
instead of raising an (({XMLRPC::FaultException})).
--- XMLRPC::Client#proxy( prefix, *args )
Returns an object of class (({XMLRPC::Client::Proxy})), initialized with
((|prefix|)) and ((|args|)). A proxy object returned by this method behaves
like ((<XMLRPC::Client#call>)), i.e. a call on that object will raise a
(({XMLRPC::FaultException})) when a fault-structure is returned by that call.
--- XMLRPC::Client#proxy2( prefix, *args )
Almost the same like ((<XMLRPC::Client#proxy>)) only that a call on the returned
(({XMLRPC::Client::Proxy})) object behaves like ((<XMLRPC::Client#call2>)), i.e.
a call on that object will return two parameters.
--- XMLRPC::Client#call_async(...)
--- XMLRPC::Client#call2_async(...)
--- XMLRPC::Client#multicall_async(...)
--- XMLRPC::Client#multicall2_async(...)
--- XMLRPC::Client#proxy_async(...)
--- XMLRPC::Client#proxy2_async(...)
In contrast to corresponding methods without "_async", these can be
called concurrently and use for each request a new connection, where the
non-asynchronous counterparts use connection-alive (one connection for all requests)
if possible.
Note, that you have to use Threads to call these methods concurrently.
The following example calls two methods concurrently:
Thread.new {
p client.call_async("michael.add", 4, 5)
}
Thread.new {
p client.call_async("michael.div", 7, 9)
}
--- XMLRPC::Client#timeout
--- XMLRPC::Client#user
--- XMLRPC::Client#password
Return the corresponding attributes.
--- XMLRPC::Client#timeout= (new_timeout)
--- XMLRPC::Client#user= (new_user)
--- XMLRPC::Client#password= (new_password)
Set the corresponding attributes.
--- XMLRPC::Client#set_writer( writer )
Sets the XML writer to use for generating XML output.
Should be an instance of a class from module (({XMLRPC::XMLWriter})).
If this method is not called, then (({XMLRPC::Config::DEFAULT_WRITER})) is used.
--- XMLRPC::Client#set_parser( parser )
Sets the XML parser to use for parsing XML documents.
Should be an instance of a class from module (({XMLRPC::XMLParser})).
If this method is not called, then (({XMLRPC::Config::DEFAULT_PARSER})) is used.
= XMLRPC::Client::Proxy
== Synopsis
require "xmlrpc/client"
server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
michael = server.proxy("michael")
michael2 = server.proxy("michael", 4)
# both calls should return the same value '9'.
p michael.add(4,5)
p michael2.add(5)
== Description
Class (({XMLRPC::Client::Proxy})) makes XML-RPC calls look nicer!
You can call any method onto objects of that class - the object handles
(({method_missing})) and will forward the method call to a XML-RPC server.
Don't use this class directly, but use instead method ((<XMLRPC::Client#proxy>)) or
((<XMLRPC::Client#proxy2>)).
== Class Methods
--- XMLRPC::Client::Proxy.new( server, prefix, args=[], meth=:call, delim="." )
Creates an object which provides (({method_missing})).
((|server|)) must be of type (({XMLRPC::Client})), which is the XML-RPC server to be used
for a XML-RPC call. ((|prefix|)) and ((|delim|)) will be prepended to the methodname
called onto this object.
Parameter ((|meth|)) is the method (call, call2, call_async, call2_async) to use for
a RPC.
((|args|)) are arguments which are automatically given
to every XML-RPC call before the arguments provides through (({method_missing})).
== Instance Methods
Every method call is forwarded to the XML-RPC server defined in ((<new|XMLRPC::Client::Proxy#new>)).
Note: Inherited methods from class (({Object})) cannot be used as XML-RPC names, because they get around
(({method_missing})).
= History
$Id$
=end
require "xmlrpc/parser"
require "xmlrpc/create"
require "xmlrpc/config"
require "xmlrpc/utils" # ParserWriterChooseMixin
require "net/http"
module XMLRPC
class Client
USER_AGENT = "XMLRPC::Client (Ruby #{RUBY_VERSION})"
include ParserWriterChooseMixin
include ParseContentType
# Constructors -------------------------------------------------------------------
def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil,
user=nil, password=nil, use_ssl=nil, timeout=nil)
@host = host || "localhost"
@path = path || "/RPC2"
@proxy_host = proxy_host
@proxy_port = proxy_port
@proxy_host ||= 'localhost' if @proxy_port != nil
@proxy_port ||= 8080 if @proxy_host != nil
@use_ssl = use_ssl || false
@timeout = timeout || 30
if use_ssl
require "net/https"
@port = port || 443
else
@port = port || 80
end
@user, @password = user, password
set_auth
# convert ports to integers
@port = @port.to_i if @port != nil
@proxy_port = @proxy_port.to_i if @proxy_port != nil
# HTTP object for synchronous calls
Net::HTTP.version_1_2
@http = Net::HTTP.new(@host, @port, @proxy_host, @proxy_port)
@http.use_ssl = @use_ssl if @use_ssl
@http.read_timeout = @timeout
@http.open_timeout = @timeout
@parser = nil
@create = nil
end
def self.new2(uri, proxy=nil, timeout=nil)
if match = /^([^:]+):\/\/(([^@]+)@)?([^\/]+)(\/.*)?$/.match(uri)
proto = match[1]
user, passwd = (match[3] || "").split(":")
host, port = match[4].split(":")
path = match[5]
if proto != "http" and proto != "https"
raise "Wrong protocol specified. Only http or https allowed!"
end
else
raise "Wrong URI as parameter!"
end
proxy_host, proxy_port = (proxy || "").split(":")
self.new(host, path, port, proxy_host, proxy_port, user, passwd, (proto == "https"), timeout)
end
def self.new3(hash={})
# convert all keys into lowercase strings
h = {}
hash.each { |k,v| h[k.to_s.downcase] = v }
self.new(h['host'], h['path'], h['port'], h['proxy_host'], h['proxy_port'], h['user'], h['password'],
h['use_ssl'], h['timeout'])
end
# Attribute Accessors -------------------------------------------------------------------
attr_reader :timeout, :user, :password
def timeout=(new_timeout)
@timeout = new_timeout
@http.read_timeout = @timeout
@http.open_timeout = @timeout
end
def user=(new_user)
@user = new_user
set_auth
end
def password=(new_password)
@password = new_password
set_auth
end
# Call methods --------------------------------------------------------------
def call(method, *args)
ok, param = call2(method, *args)
if ok
param
else
raise param
end
end
def call2(method, *args)
request = create().methodCall(method, *args)
data = do_rpc(request, false)
parser().parseMethodResponse(data)
end
def call_async(method, *args)
ok, param = call2_async(method, *args)
if ok
param
else
raise param
end
end
def call2_async(method, *args)
request = create().methodCall(method, *args)
data = do_rpc(request, true)
parser().parseMethodResponse(data)
end
# Multicall methods --------------------------------------------------------------
def multicall(*methods)
ok, params = multicall2(*methods)
if ok
params
else
raise params
end
end
def multicall2(*methods)
gen_multicall(methods, false)
end
def multicall_async(*methods)
ok, params = multicall2_async(*methods)
if ok
params
else
raise params
end
end
def multicall2_async(*methods)
gen_multicall(methods, true)
end
# Proxy generating methods ------------------------------------------
def proxy(prefix, *args)
Proxy.new(self, prefix, args, :call)
end
def proxy2(prefix, *args)
Proxy.new(self, prefix, args, :call2)
end
def proxy_async(prefix, *args)
Proxy.new(self, prefix, args, :call_async)
end
def proxy2_async(prefix, *args)
Proxy.new(self, prefix, args, :call2_async)
end
private # ----------------------------------------------------------
def set_auth
if @user.nil?
@auth = nil
else
a = "#@user"
a << ":#@password" if @password != nil
@auth = ("Basic " + [a].pack("m")).chomp
end
end
def do_rpc(request, async=false)
header = {
"User-Agent" => USER_AGENT,
"Content-Type" => "text/xml",
"Content-Length" => request.size.to_s,
"Connection" => (async ? "close" : "keep-alive")
}
if @auth != nil
# add authorization header
header["Authorization"] = @auth
end
resp = nil
if async
# use a new HTTP object for each call
Net::HTTP.version_1_2
http = Net::HTTP.new(@host, @port, @proxy_host, @proxy_port)
http.use_ssl = @use_ssl if @use_ssl
http.read_timeout = @timeout
http.open_timeout = @timeout
# post request
http.start {
resp = http.post2(@path, request, header)
}
else
# reuse the HTTP object for each call => connection alive is possible
# post request
resp = @http.post2(@path, request, header)
end
data = resp.body
if resp.code == "401"
# Authorization Required
raise "Authorization failed.\nHTTP-Error: #{resp.code} #{resp.message}"
elsif resp.code[0,1] != "2"
raise "HTTP-Error: #{resp.code} #{resp.message}"
end
ct = parse_content_type(resp["Content-Type"]).first
if ct != "text/xml"
if ct == "text/html"
raise "Wrong content-type: \n#{data}"
else
raise "Wrong content-type"
end
end
expected = resp["Content-Length"] || "<unknown>"
if data.nil? or data.size == 0
raise "Wrong size. Was #{data.size}, should be #{expected}"
elsif expected.to_i != data.size and resp["Transfer-Encoding"].nil?
raise "Wrong size. Was #{data.size}, should be #{expected}"
end
return data
end
def gen_multicall(methods=[], async=false)
meth = :call2
meth = :call2_async if async
ok, params = self.send(meth, "system.multicall",
methods.collect {|m| {'methodName' => m[0], 'params' => m[1..-1]} }
)
if ok
params = params.collect do |param|
if param.is_a? Array
param[0]
elsif param.is_a? Hash
XMLRPC::FaultException.new(param["faultCode"], param["faultString"])
else
raise "Wrong multicall return value"
end
end
end
return ok, params
end
class Proxy
def initialize(server, prefix, args=[], meth=:call, delim=".")
@server = server
@prefix = prefix + delim
@args = args
@meth = meth
end
def method_missing(mid, *args)
pre = @prefix + mid.to_s
arg = @args + args
@server.send(@meth, pre, *arg)
end
end # class Proxy
end # class Client
end # module XMLRPC

40
lib/xmlrpc/config.rb Normal file
View file

@ -0,0 +1,40 @@
#
# $Id$
# Configuration file for XML-RPC for Ruby
#
module XMLRPC
module Config
DEFAULT_WRITER = XMLWriter::Simple # or XMLWriter::XMLParser
# available parser:
# * XMLParser::NQXMLTreeParser
# * XMLParser::NQXMLStreamParser
# * XMLParser::XMLTreeParser
# * XMLParser::XMLStreamParser (fastest)
# * XMLParser::REXMLStreamParser
# * XMLParser::XMLScanStreamParser
DEFAULT_PARSER = XMLParser::REXMLStreamParser
# enable <nil/> tag
ENABLE_NIL_CREATE = false
ENABLE_NIL_PARSER = false
# allows integers greater than 32-bit if true
ENABLE_BIGINT = false
# enable marshalling ruby objects which include XMLRPC::Marshallable
ENABLE_MARSHALLING = true
# enable multiCall extension by default
ENABLE_MULTICALL = false
# enable Introspection extension by default
ENABLE_INTROSPECTION = false
end
end

280
lib/xmlrpc/create.rb Normal file
View file

@ -0,0 +1,280 @@
#
# Creates XML-RPC call/response documents
#
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
#
# $Id$
#
require "date"
require "xmlrpc/base64"
module XMLRPC
module XMLWriter
class Abstract
def ele(name, *children)
element(name, nil, *children)
end
def tag(name, txt)
element(name, nil, text(txt))
end
end
class Simple < Abstract
def document_to_str(doc)
doc
end
def document(*params)
params.join("")
end
def pi(name, *params)
"<?#{name} " + params.join(" ") + " ?>"
end
def element(name, attrs, *children)
raise "attributes not yet implemented" unless attrs.nil?
if children.empty?
"<#{name}/>"
else
"<#{name}>" + children.join("") + "</#{name}>"
end
end
def text(txt)
cleaned = txt.dup
cleaned.gsub!(/&/, '&amp;')
cleaned.gsub!(/</, '&lt;')
cleaned.gsub!(/>/, '&gt;')
cleaned
end
end # class Simple
class XMLParser < Abstract
def initialize
require "xmltreebuilder"
end
def document_to_str(doc)
doc.to_s
end
def document(*params)
XML::SimpleTree::Document.new(*params)
end
def pi(name, *params)
XML::SimpleTree::ProcessingInstruction.new(name, *params)
end
def element(name, attrs, *children)
XML::SimpleTree::Element.new(name, attrs, *children)
end
def text(txt)
XML::SimpleTree::Text.new(txt)
end
end # class XMLParser
end # module XMLWriter
class Create
def initialize(xml_writer = nil)
@writer = xml_writer || Config::DEFAULT_WRITER.new
end
def methodCall(name, *params)
name = name.to_s
if name !~ /[a-zA-Z0-9_.:\/]+/
raise ArgumentError, "Wrong XML-RPC method-name"
end
parameter = params.collect do |param|
@writer.ele("param", conv2value(param))
end
tree = @writer.document(
@writer.pi("xml", 'version="1.0"'),
@writer.ele("methodCall",
@writer.tag("methodName", name),
@writer.ele("params", *parameter)
)
)
@writer.document_to_str(tree) + "\n"
end
#
# generates a XML-RPC methodResponse document
#
# if is_ret == false then the params array must
# contain only one element, which is a structure
# of a fault return-value.
#
# if is_ret == true then a normal
# return-value of all the given params is created.
#
def methodResponse(is_ret, *params)
if is_ret
resp = params.collect do |param|
@writer.ele("param", conv2value(param))
end
resp = [@writer.ele("params", *resp)]
else
if params.size != 1 or params[0] === XMLRPC::FaultException
raise ArgumentError, "no valid fault-structure given"
end
resp = @writer.ele("fault", conv2value(params[0].to_h))
end
tree = @writer.document(
@writer.pi("xml", 'version="1.0"'),
@writer.ele("methodResponse", resp)
)
@writer.document_to_str(tree) + "\n"
end
#####################################
private
#####################################
#
# converts a Ruby object into
# a XML-RPC <value> tag
#
def conv2value(param)
val = case param
when Fixnum
@writer.tag("i4", param.to_s)
when Bignum
if Config::ENABLE_BIGINT
@writer.tag("i4", param.to_s)
else
if param >= -(2**31) and param <= (2**31-1)
@writer.tag("i4", param.to_s)
else
raise "Bignum is too big! Must be signed 32-bit integer!"
end
end
when TrueClass, FalseClass
@writer.tag("boolean", param ? "1" : "0")
when String
@writer.tag("string", param)
when Symbol
@writer.tag("string", param.to_s)
when NilClass
if Config::ENABLE_NIL_CREATE
@writer.ele("nil")
else
raise "Wrong type NilClass. Not allowed!"
end
when Float
@writer.tag("double", param.to_s)
when Struct
h = param.members.collect do |key|
value = param[key]
@writer.ele("member",
@writer.tag("name", key.to_s),
conv2value(value)
)
end
@writer.ele("struct", *h)
when Hash
# TODO: can a Hash be empty?
h = param.collect do |key, value|
@writer.ele("member",
@writer.tag("name", key.to_s),
conv2value(value)
)
end
@writer.ele("struct", *h)
when Array
# TODO: can an Array be empty?
a = param.collect {|v| conv2value(v) }
@writer.ele("array",
@writer.ele("data", *a)
)
when Date
t = param
@writer.tag("dateTime.iso8601",
format("%.4d%02d%02dT00:00:00", t.year, t.month, t.day))
when Time
@writer.tag("dateTime.iso8601", param.strftime("%Y%m%dT%H:%M:%S"))
when XMLRPC::DateTime
@writer.tag("dateTime.iso8601",
format("%.4d%02d%02dT%02d:%02d:%02d", *param.to_a))
when XMLRPC::Base64
@writer.tag("base64", param.encoded)
else
if Config::ENABLE_MARSHALLING and param.class.included_modules.include? XMLRPC::Marshallable
# convert Ruby object into Hash
ret = {"___class___" => param.class.name}
param.__get_instance_variables.each {|name, val|
if val.nil?
ret[name] = val if Config::ENABLE_NIL_CREATE
else
ret[name] = val
end
}
return conv2value(ret)
else
ok, pa = wrong_type(param)
if ok
return conv2value(pa)
else
raise "Wrong type!"
end
end
end
@writer.ele("value", val)
end
def wrong_type(value)
false
end
end # class Create
end # module XMLRPC

138
lib/xmlrpc/datetime.rb Normal file
View file

@ -0,0 +1,138 @@
=begin
= xmlrpc/datetime.rb
Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
Released under the same term of license as Ruby.
= Classes
* ((<XMLRPC::DateTime>))
= XMLRPC::DateTime
== Description
This class is important to handle XMLRPC (('dateTime.iso8601')) values,
correcly, because normal UNIX-dates (class (({Date}))) only handle dates
from year 1970 on, and class (({Time})) handles dates without the time
component. (({XMLRPC::DateTime})) is able to store a XMLRPC
(('dateTime.iso8601')) value correctly.
== Class Methods
--- XMLRPC::DateTime.new( year, month, day, hour, min, sec )
Creates a new (({XMLRPC::DateTime})) instance with the
parameters ((|year|)), ((|month|)), ((|day|)) as date and
((|hour|)), ((|min|)), ((|sec|)) as time.
Raises (({ArgumentError})) if a parameter is out of range, or ((|year|)) is not
of type (({Integer})).
== Instance Methods
--- XMLRPC::DateTime#year
--- XMLRPC::DateTime#month
--- XMLRPC::DateTime#day
--- XMLRPC::DateTime#hour
--- XMLRPC::DateTime#min
--- XMLRPC::DateTime#sec
Return the value of the specified date/time component.
--- XMLRPC::DateTime#mon
Alias for ((<XMLRPC::DateTime#month>)).
--- XMLRPC::DateTime#year=( value )
--- XMLRPC::DateTime#month=( value )
--- XMLRPC::DateTime#day=( value )
--- XMLRPC::DateTime#hour=( value )
--- XMLRPC::DateTime#min=( value )
--- XMLRPC::DateTime#sec=( value )
Set ((|value|)) as the new date/time component.
Raises (({ArgumentError})) if ((|value|)) is out of range, or in the case
of (({XMLRPC::DateTime#year=})) if ((|value|)) is not of type (({Integer})).
--- XMLRPC::DateTime#mon=( value )
Alias for ((<XMLRPC::DateTime#month=>)).
--- XMLRPC::DateTime#to_time
Return a (({Time})) object of the date/time which (({self})) represents.
If the (('year')) is below 1970, this method returns (({nil})),
because (({Time})) cannot handle years below 1970.
The used timezone is GMT.
--- XMLRPC::DateTime#to_date
Return a (({Date})) object of the date which (({self})) represents.
The (({Date})) object do ((*not*)) contain the time component (only date).
--- XMLRPC::DateTime#to_a
Returns all date/time components in an array.
Returns (({[year, month, day, hour, min, sec]})).
=end
require "date"
module XMLRPC
class DateTime
attr_reader :year, :month, :day, :hour, :min, :sec
def year= (value)
raise ArgumentError, "date/time out of range" unless value.is_a? Integer
@year = value
end
def month= (value)
raise ArgumentError, "date/time out of range" unless (1..12).include? value
@month = value
end
def day= (value)
raise ArgumentError, "date/time out of range" unless (1..31).include? value
@day = value
end
def hour= (value)
raise ArgumentError, "date/time out of range" unless (0..24).include? value
@hour = value
end
def min= (value)
raise ArgumentError, "date/time out of range" unless (0..59).include? value
@min = value
end
def sec= (value)
raise ArgumentError, "date/time out of range" unless (0..59).include? value
@sec = value
end
alias mon month
alias mon= month=
def initialize(year, month, day, hour, min, sec)
self.year, self.month, self.day = year, month, day
self.hour, self.min, self.sec = hour, min, sec
end
def to_time
if @year >= 1970
Time.gm(*to_a)
else
nil
end
end
def to_date
Date.new(*to_a[0,3])
end
def to_a
[@year, @month, @day, @hour, @min, @sec]
end
end
end # module XMLRPC
=begin
= History
$Id$
=end

178
lib/xmlrpc/httpserver.rb Normal file
View file

@ -0,0 +1,178 @@
#
# Implements a simple HTTP-server by using John W. Small's (jsmall@laser.net)
# ruby-generic-server.
#
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
#
# $Id$
#
require "gserver"
class HttpServer < GServer
##
# handle_obj specifies the object, that receives calls to request_handler
# and ip_auth_handler
def initialize(handle_obj, port = 8080, host = DEFAULT_HOST, maxConnections = 4,
stdlog = $stdout, audit = true, debug = true)
@handler = handle_obj
super(port, host, maxConnections, stdlog, audit, debug)
end
private
# Constants -----------------------------------------------
CRLF = "\r\n"
HTTP_PROTO = "HTTP/1.0"
SERVER_NAME = "HttpServer (Ruby #{RUBY_VERSION})"
DEFAULT_HEADER = {
"Server" => SERVER_NAME
}
##
# Mapping of status code and error message
#
StatusCodeMapping = {
200 => "OK",
400 => "Bad Request",
403 => "Forbidden",
405 => "Method Not Allowed",
411 => "Length Required",
500 => "Internal Server Error"
}
# Classes -------------------------------------------------
class Request
attr_reader :data, :header, :method, :path, :proto
def initialize(data, method=nil, path=nil, proto=nil)
@header, @data = Table.new, data
@method, @path, @proto = method, path, proto
end
def content_length
len = @header['Content-Length']
return nil if len.nil?
return len.to_i
end
end
class Response
attr_reader :header
attr_accessor :body, :status, :status_message
def initialize(status=200)
@status = status
@status_message = nil
@header = Table.new
end
end
##
# a case-insensitive Hash class for HTTP header
#
class Table
include Enumerable
def initialize(hash={})
@hash = hash
update(hash)
end
def [](key)
@hash[key.to_s.capitalize]
end
def []=(key, value)
@hash[key.to_s.capitalize] = value
end
def update(hash)
hash.each {|k,v| self[k] = v}
self
end
def each
@hash.each {|k,v| yield k.capitalize, v }
end
def writeTo(port)
each { |k,v| port << "#{k}: #{v}" << CRLF }
end
end # class Table
# Helper Methods ------------------------------------------
def http_header(header=nil)
new_header = Table.new(DEFAULT_HEADER)
new_header.update(header) unless header.nil?
new_header["Connection"] = "close"
new_header["Date"] = http_date(Time.now)
new_header
end
def http_date( aTime )
aTime.gmtime.strftime( "%a, %d %b %Y %H:%M:%S GMT" )
end
def http_resp(status_code, status_message=nil, header=nil, body=nil)
status_message ||= StatusCodeMapping[status_code]
str = ""
str << "#{HTTP_PROTO} #{status_code} #{status_message}" << CRLF
http_header(header).writeTo(str)
str << CRLF
str << body unless body.nil?
str
end
# Main Serve Loop -----------------------------------------
def serve(io)
# perform IP authentification
unless @handler.ip_auth_handler(io)
io << http_resp(403, "Forbidden")
return
end
# parse first line
if io.gets =~ /^(\S+)\s+(\S+)\s+(\S+)/
request = Request.new(io, $1, $2, $3)
else
io << http_resp(400, "Bad Request")
return
end
# parse HTTP headers
while (line=io.gets) !~ /^(\n|\r)/
if line =~ /^([\w-]+):\s*(.*)$/
request.header[$1] = $2.strip
end
end
io.binmode
response = Response.new
# execute request handler
@handler.request_handler(request, response)
# write response back to the client
io << http_resp(response.status, response.status_message,
response.header, response.body)
rescue Exception => e
io << http_resp(500, "Internal Server Error")
end
end # class HttpServer

76
lib/xmlrpc/marshal.rb Normal file
View file

@ -0,0 +1,76 @@
#
# Marshalling of XML-RPC methodCall and methodResponse
#
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
#
# $Id$
#
require "xmlrpc/parser"
require "xmlrpc/create"
require "xmlrpc/config"
require "xmlrpc/utils"
module XMLRPC
class Marshal
include ParserWriterChooseMixin
# class methods -------------------------------
class << self
def dump_call( methodName, *params )
new.dump_call( methodName, *params )
end
def dump_response( param )
new.dump_response( param )
end
def load_call( stringOrReadable )
new.load_call( stringOrReadable )
end
def load_response( stringOrReadable )
new.load_response( stringOrReadable )
end
alias dump dump_response
alias load load_response
end # class self
# instance methods ----------------------------
def initialize( parser = nil, writer = nil )
set_parser( parser )
set_writer( writer )
end
def dump_call( methodName, *params )
create.methodCall( methodName, *params )
end
def dump_response( param )
create.methodResponse( ! param.kind_of?( XMLRPC::FaultException ) , param )
end
##
# returns [ methodname, params ]
#
def load_call( stringOrReadable )
parser.parseMethodCall( stringOrReadable )
end
##
# returns paramOrFault
#
def load_response( stringOrReadable )
parser.parseMethodResponse( stringOrReadable )[1]
end
end # class Marshal
end

803
lib/xmlrpc/parser.rb Normal file
View file

@ -0,0 +1,803 @@
#
# Parser for XML-RPC call and response
#
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
#
# $Id$
#
require "date"
require "xmlrpc/base64"
require "xmlrpc/datetime"
# add some methods to NQXML::Node
module NQXML
class Node
def removeChild(node)
@children.delete(node)
end
def childNodes
@children
end
def hasChildNodes
not @children.empty?
end
def [] (index)
@children[index]
end
def nodeType
if @entity.instance_of? NQXML::Text then :TEXT
elsif @entity.instance_of? NQXML::Comment then :COMMENT
#elsif @entity.instance_of? NQXML::Element then :ELEMENT
elsif @entity.instance_of? NQXML::Tag then :ELEMENT
else :ELSE
end
end
def nodeValue
#TODO: error when wrong Entity-type
@entity.text
end
def nodeName
#TODO: error when wrong Entity-type
@entity.name
end
end # class Node
end # module NQXML
module XMLRPC
class FaultException < Exception
attr_reader :faultCode, :faultString
def initialize(faultCode, faultString)
@faultCode = faultCode
@faultString = faultString
end
# returns a hash
def to_h
{"faultCode" => @faultCode, "faultString" => @faultString}
end
end
module Convert
def self.int(str)
str.to_i
end
def self.boolean(str)
case str
when "0" then false
when "1" then true
else
raise "RPC-value of type boolean is wrong"
end
end
def self.double(str)
str.to_f
end
def self.dateTime(str)
if str =~ /^(-?\d\d\d\d)(\d\d)(\d\d)T(\d\d):(\d\d):(\d\d)$/ then
# TODO: Time.gm ??? .local ???
a = [$1, $2, $3, $4, $5, $6].collect{|i| i.to_i}
XMLRPC::DateTime.new(*a)
#if a[0] >= 1970 then
# Time.gm(*a)
#else
# Date.new(*a[0,3])
#end
else
raise "wrong dateTime.iso8601 format"
end
end
def self.base64(str)
XMLRPC::Base64.decode(str)
end
def self.struct(hash)
# convert to marhalled object
klass = hash["___class___"]
if klass.nil? or Config::ENABLE_MARSHALLING == false
hash
else
begin
mod = Module
klass.split("::").each {|const| mod = mod.const_get const.strip }
Thread.critical = true
# let initialize take 0 parameters
mod.module_eval %{
begin
alias __initialize initialize
rescue NameError
end
def initialize; end
}
obj = mod.new
# restore old initialize
mod.module_eval %{
undef initialize
begin
alias initialize __initialize
rescue NameError
end
}
Thread.critical = false
hash.delete "___class___"
hash.each {|k,v| obj.__set_instance_variable(k, v) }
obj
rescue
hash
end
end
end
def self.fault(hash)
if hash.kind_of? Hash and hash.size == 2 and
hash.has_key? "faultCode" and hash.has_key? "faultString" and
hash["faultCode"].kind_of? Integer and hash["faultString"].kind_of? String
XMLRPC::FaultException.new(hash["faultCode"], hash["faultString"])
else
raise "wrong fault-structure: #{hash.inspect}"
end
end
end # module Convert
module XMLParser
class AbstractTreeParser
def parseMethodResponse(str)
methodResponse_document(createCleanedTree(str))
end
def parseMethodCall(str)
methodCall_document(createCleanedTree(str))
end
private
#
# remove all whitespaces but in the tags i4, int, boolean....
# and all comments
#
def removeWhitespacesAndComments(node)
remove = []
childs = node.childNodes.to_a
childs.each do |nd|
case _nodeType(nd)
when :TEXT
# TODO: add nil?
unless %w(i4 int boolean string double dateTime.iso8601 base64).include? node.nodeName
if node.nodeName == "value"
if not node.childNodes.to_a.detect {|n| _nodeType(n) == :ELEMENT}.nil?
remove << nd if nd.nodeValue.strip == ""
end
else
remove << nd if nd.nodeValue.strip == ""
end
end
when :COMMENT
remove << nd
else
removeWhitespacesAndComments(nd)
end
end
remove.each { |i| node.removeChild(i) }
end
def nodeMustBe(node, name)
cmp = case name
when Array
name.include?(node.nodeName)
when String
name == node.nodeName
else
raise "error"
end
if not cmp then
raise "wrong xml-rpc (name)"
end
node
end
#
# returns, when successfully the only child-node
#
def hasOnlyOneChild(node, name=nil)
if node.childNodes.to_a.size != 1
raise "wrong xml-rpc (size)"
end
if name != nil then
nodeMustBe(node.firstChild, name)
end
end
def assert(b)
if not b then
raise "assert-fail"
end
end
# the node `node` has empty string or string
def text_zero_one(node)
nodes = node.childNodes.to_a.size
if nodes == 1
text(node.firstChild)
elsif nodes == 0
""
else
raise "wrong xml-rpc (size)"
end
end
def integer(node)
#TODO: check string for float because to_i returnsa
# 0 when wrong string
nodeMustBe(node, %w(i4 int))
hasOnlyOneChild(node)
Convert.int(text(node.firstChild))
end
def boolean(node)
nodeMustBe(node, "boolean")
hasOnlyOneChild(node)
Convert.boolean(text(node.firstChild))
end
def v_nil(node)
nodeMustBe(node, "nil")
assert( node.childNodes.to_a.size == 0 )
nil
end
def string(node)
nodeMustBe(node, "string")
text_zero_one(node)
end
def double(node)
#TODO: check string for float because to_f returnsa
# 0.0 when wrong string
nodeMustBe(node, "double")
hasOnlyOneChild(node)
Convert.double(text(node.firstChild))
end
def dateTime(node)
nodeMustBe(node, "dateTime.iso8601")
hasOnlyOneChild(node)
Convert.dateTime( text(node.firstChild) )
end
def base64(node)
nodeMustBe(node, "base64")
#hasOnlyOneChild(node)
Convert.base64(text_zero_one(node))
end
def member(node)
nodeMustBe(node, "member")
assert( node.childNodes.to_a.size == 2 )
[ name(node[0]), value(node[1]) ]
end
def name(node)
nodeMustBe(node, "name")
#hasOnlyOneChild(node)
text_zero_one(node)
end
def array(node)
nodeMustBe(node, "array")
hasOnlyOneChild(node, "data")
data(node.firstChild)
end
def data(node)
nodeMustBe(node, "data")
node.childNodes.to_a.collect do |val|
value(val)
end
end
def param(node)
nodeMustBe(node, "param")
hasOnlyOneChild(node, "value")
value(node.firstChild)
end
def methodResponse(node)
nodeMustBe(node, "methodResponse")
hasOnlyOneChild(node, %w(params fault))
child = node.firstChild
case child.nodeName
when "params"
[ true, params(child,false) ]
when "fault"
[ false, fault(child) ]
else
raise "unexpected error"
end
end
def methodName(node)
nodeMustBe(node, "methodName")
hasOnlyOneChild(node)
text(node.firstChild)
end
def params(node, call=true)
nodeMustBe(node, "params")
if call
node.childNodes.to_a.collect do |n|
param(n)
end
else # response (only one param)
hasOnlyOneChild(node)
param(node.firstChild)
end
end
def fault(node)
nodeMustBe(node, "fault")
hasOnlyOneChild(node, "value")
f = value(node.firstChild)
Convert.fault(f)
end
# _nodeType is defined in the subclass
def text(node)
assert( _nodeType(node) == :TEXT )
assert( node.hasChildNodes == false )
assert( node.nodeValue != nil )
node.nodeValue.to_s
end
def struct(node)
nodeMustBe(node, "struct")
hash = {}
node.childNodes.to_a.each do |me|
n, v = member(me)
hash[n] = v
end
Convert.struct(hash)
end
def value(node)
nodeMustBe(node, "value")
nodes = node.childNodes.to_a.size
if nodes == 0
return ""
elsif nodes > 1
raise "wrong xml-rpc (size)"
end
child = node.firstChild
case _nodeType(child)
when :TEXT
text_zero_one(node)
when :ELEMENT
case child.nodeName
when "i4", "int" then integer(child)
when "boolean" then boolean(child)
when "string" then string(child)
when "double" then double(child)
when "dateTime.iso8601" then dateTime(child)
when "base64" then base64(child)
when "struct" then struct(child)
when "array" then array(child)
when "nil"
if Config::ENABLE_NIL_PARSER
v_nil(child)
else
raise "wrong/unknown XML-RPC type 'nil'"
end
else
raise "wrong/unknown XML-RPC type"
end
else
raise "wrong type of node"
end
end
def methodCall(node)
nodeMustBe(node, "methodCall")
assert( (1..2).include?( node.childNodes.to_a.size ) )
name = methodName(node[0])
if node.childNodes.to_a.size == 2 then
pa = params(node[1])
else # no parameters given
pa = []
end
[name, pa]
end
end # module TreeParserMixin
class AbstractStreamParser
def parseMethodResponse(str)
parser = @parser_class.new
parser.parse(str)
raise "No valid method response!" if parser.method_name != nil
if parser.fault != nil
# is a fault structure
[false, parser.fault]
else
# is a normal return value
raise "Missing return value!" if parser.params.size == 0
raise "To many return values. Only one allowed!" if parser.params.size > 1
[true, parser.params[0]]
end
end
def parseMethodCall(str)
parser = @parser_class.new
parser.parse(str)
raise "No valid method call - missing method name!" if parser.method_name.nil?
[parser.method_name, parser.params]
end
end
module StreamParserMixin
attr_reader :params
attr_reader :method_name
attr_reader :fault
def initialize(*a)
super(*a)
@params = []
@values = []
@val_stack = []
@names = []
@name = []
@structs = []
@struct = {}
@method_name = nil
@fault = nil
@data = nil
end
def startElement(name, attrs=[])
@data = nil
case name
when "value"
@value = nil
when "nil"
raise "wrong/unknown XML-RPC type 'nil'" unless Config::ENABLE_NIL_PARSER
@value = :nil
when "array"
@val_stack << @values
@values = []
when "struct"
@names << @name
@name = []
@structs << @struct
@struct = {}
end
end
def endElement(name)
@data ||= ""
case name
when "string"
@value = @data
when "i4", "int"
@value = Convert.int(@data)
when "boolean"
@value = Convert.boolean(@data)
when "double"
@value = Convert.double(@data)
when "dateTime.iso8601"
@value = Convert.dateTime(@data)
when "base64"
@value = Convert.base64(@data)
when "value"
@value = @data if @value.nil?
@values << (@value == :nil ? nil : @value)
when "array"
@value = @values
@values = @val_stack.pop
when "struct"
@value = Convert.struct(@struct)
@name = @names.pop
@struct = @structs.pop
when "name"
@name[0] = @data
when "member"
@struct[@name[0]] = @values.pop
when "param"
@params << @values[0]
@values = []
when "fault"
@fault = Convert.fault(@values[0])
when "methodName"
@method_name = @data
end
@data = nil
end
def character(data)
if @data
@data << data
else
@data = data
end
end
end # module StreamParserMixin
# ---------------------------------------------------------------------------
class XMLStreamParser < AbstractStreamParser
def initialize
require "xmlparser"
eval %{
class XMLRPCParser < ::XMLParser
include StreamParserMixin
end
}
@parser_class = XMLRPCParser
end
end # class XMLStreamParser
# ---------------------------------------------------------------------------
class NQXMLStreamParser < AbstractStreamParser
def initialize
require "nqxml/streamingparser"
@parser_class = XMLRPCParser
end
class XMLRPCParser
include StreamParserMixin
def parse(str)
parser = NQXML::StreamingParser.new(str)
parser.each do |ele|
case ele
when NQXML::Text
@data = ele.text
#character(ele.text)
when NQXML::Tag
if ele.isTagEnd
endElement(ele.name)
else
startElement(ele.name, ele.attrs)
end
end
end # do
end # method parse
end # class XMLRPCParser
end # class NQXMLStreamParser
# ---------------------------------------------------------------------------
class XMLTreeParser < AbstractTreeParser
def initialize
require "xmltreebuilder"
# The new XMLParser library (0.6.2+) uses a slightly different DOM implementation.
# The following code removes the differences between both versions.
if defined? XML::DOM::Builder
return if defined? XML::DOM::Node::DOCUMENT # code below has been already executed
klass = XML::DOM::Node
klass.const_set("DOCUMENT", klass::DOCUMENT_NODE)
klass.const_set("TEXT", klass::TEXT_NODE)
klass.const_set("COMMENT", klass::COMMENT_NODE)
klass.const_set("ELEMENT", klass::ELEMENT_NODE)
end
end
private
def _nodeType(node)
tp = node.nodeType
if tp == XML::SimpleTree::Node::TEXT then :TEXT
elsif tp == XML::SimpleTree::Node::COMMENT then :COMMENT
elsif tp == XML::SimpleTree::Node::ELEMENT then :ELEMENT
else :ELSE
end
end
def methodResponse_document(node)
assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
hasOnlyOneChild(node, "methodResponse")
methodResponse(node.firstChild)
end
def methodCall_document(node)
assert( node.nodeType == XML::SimpleTree::Node::DOCUMENT )
hasOnlyOneChild(node, "methodCall")
methodCall(node.firstChild)
end
def createCleanedTree(str)
doc = XML::SimpleTreeBuilder.new.parse(str)
doc.documentElement.normalize
removeWhitespacesAndComments(doc)
doc
end
end # class XMLParser
# ---------------------------------------------------------------------------
class NQXMLTreeParser < AbstractTreeParser
def initialize
require "nqxml/treeparser"
end
private
def _nodeType(node)
node.nodeType
end
def methodResponse_document(node)
methodResponse(node)
end
def methodCall_document(node)
methodCall(node)
end
def createCleanedTree(str)
doc = ::NQXML::TreeParser.new(str).document.rootNode
removeWhitespacesAndComments(doc)
doc
end
end # class NQXMLTreeParser
# ---------------------------------------------------------------------------
class REXMLStreamParser < AbstractStreamParser
def initialize
require "rexml/document"
@parser_class = StreamListener
end
class StreamListener
include StreamParserMixin
alias :tag_start :startElement
alias :tag_end :endElement
alias :text :character
def method_missing(*a)
# ignore
end
def parse(str)
parser = REXML::Document.parse_stream(str, self)
end
end
end
# ---------------------------------------------------------------------------
class XMLScanStreamParser < AbstractStreamParser
def initialize
require "xmlscan/parser"
@parser_class = XMLScanParser
end
class XMLScanParser
include StreamParserMixin
Entities = {
"lt" => "<",
"gt" => ">",
"amp" => "&",
"quot" => '"',
"apos" => "'"
}
def parse(str)
parser = XMLScan::XMLParser.new(self)
parser.parse(str)
end
alias :on_stag :startElement
alias :on_etag :endElement
def on_stag_end(name); end
def on_stag_end_empty(name)
startElement(name)
endElement(name)
end
def on_chardata(str)
character(str)
end
def on_entityref(ent)
str = Entities[ent]
if str
character(str)
else
raise "unknown entity"
end
end
def on_charref(code)
character(code.chr)
end
def on_charref_hex(code)
character(code.chr)
end
def method_missing(*a)
end
# TODO: call/implement?
# valid_name?
# valid_chardata?
# valid_char?
# parse_error
end
end
# ---------------------------------------------------------------------------
XMLParser = XMLTreeParser
NQXMLParser = NQXMLTreeParser
Classes = [XMLStreamParser, XMLTreeParser,
NQXMLStreamParser, NQXMLTreeParser,
REXMLStreamParser, XMLScanStreamParser]
end # module XMLParser
end # module XMLRPC

839
lib/xmlrpc/server.rb Normal file
View file

@ -0,0 +1,839 @@
=begin
= xmlrpc/server.rb
Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
Released under the same term of license as Ruby.
= Classes
* ((<XMLRPC::BasicServer>))
* ((<XMLRPC::CGIServer>))
* ((<XMLRPC::ModRubyServer>))
* ((<XMLRPC::Server>))
* ((<XMLRPC::WEBrickServlet>))
= XMLRPC::BasicServer
== Description
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 ((-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.
== Class Methods
--- XMLRPC::BasicServer.new( class_delim="." )
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 method (({initialize})). The paramter ((|class_delim|)) is used
in ((<add_handler|XMLRPC::BasicServer#add_handler>)) when an object is
added as handler, to delimit the object-prefix and the method-name.
== Instance Methods
--- XMLRPC::BasicServer#add_handler( name, signature=nil, help=nil ) { aBlock }
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 informations about how to call this method etc.
A handler method or code-block can return the types listed at
((<XMLRPC::Client#call|URL:client.html#index:0>)).
When a method fails, it can tell it 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
The client gets in the case of (({b==0})) an object back of type
(({XMLRPC::FaultException})) that has a ((|faultCode|)) and ((|faultString|))
field.
--- XMLRPC::BasicServer#add_handler( prefix, obj )
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 (('michael."name of method"')). This is
where the ((|class_delim|)) in ((<new|XMLRPC::BasicServer.new>))
has it's role, a XML-RPC method-name is defined by
((|prefix|)) + ((|class_delim|)) + (('"name of method"')).
--- XMLRPC::BasicServer#add_handler( interface, obj )
This is the third form of ((<add_handler|XMLRPC::BasicServer#add_handler>)).
Use (({XMLRPC::interface})) to generate an ServiceInterface object, which
represents an interface (with signature and help text) for a handler class.
Parameter ((|interface|)) must be of type (({XMLRPC::ServiceInterface})).
Adds all methods of ((|obj|)) which are defined in ((|interface|)) to the
server.
This is the recommended way of adding services to a server!
--- XMLRPC::BasicServer#get_default_handler
Returns the default-handler, which is called when no handler for
a method-name is found.
It is a (({Proc})) object or (({nil})).
--- XMLRPC::BasicServer#set_default_handler ( &handler )
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".
--- XMLRPC::BasicServer#set_writer( writer )
Sets the XML writer to use for generating XML output.
Should be an instance of a class from module (({XMLRPC::XMLWriter})).
If this method is not called, then (({XMLRPC::Config::DEFAULT_WRITER})) is used.
--- XMLRPC::BasicServer#set_parser( parser )
Sets the XML parser to use for parsing XML documents.
Should be an instance of a class from module (({XMLRPC::XMLParser})).
If this method is not called, then (({XMLRPC::Config::DEFAULT_PARSER})) is used.
--- XMLRPC::BasicServer#add_introspection
Adds the introspection handlers "system.listMethods", "system.methodSignature" and "system.methodHelp",
where only the first one works.
--- XMLRPC::BasicServer#add_multicall
Adds the multi-call handler "system.multicall".
--- XMLRPC::BasicServer#get_service_hook
Returns the service-hook, which is called on each service request (RPC) unless it's (({nil})).
--- XMLRPC::BasicServer#set_service_hook ( &handler )
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 parameter
((|handler|)) .
The service-hook is called with a (({Proc})) object and with the parameters for this (({Proc})).
An example:
server.set_service_hook {|obj, *args|
begin
ret = obj.call(*args) # call the original service-method
# could convert the return value
resuce
# rescue exceptions
end
}
=end
require "xmlrpc/parser"
require "xmlrpc/create"
require "xmlrpc/config"
require "xmlrpc/httpserver"
require "xmlrpc/utils" # ParserWriterChooseMixin
module XMLRPC
class BasicServer
include ParserWriterChooseMixin
include ParseContentType
ERR_METHOD_MISSING = 1
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
def initialize(class_delim=".")
@handler = []
@default_handler = nil
@service_hook = nil
@class_delim = class_delim
@create = nil
@parser = nil
add_multicall if Config::ENABLE_MULTICALL
add_introspection if Config::ENABLE_INTROSPECTION
end
def add_handler(prefix, obj_or_signature=nil, help=nil, &block)
if block_given?
# proc-handler
@handler << [prefix, block, obj_or_signature, help]
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
def get_service_hook
@service_hook
end
def set_service_hook(&handler)
@service_hook = handler
self
end
def get_default_handler
@default_handler
end
def set_default_handler (&handler)
@default_handler = handler
self
end
def add_multicall
add_handler("system.multicall", %w(array array), "Multicall Extension") do |arrStructs|
unless arrStructs.is_a? Array
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"]
params = call["params"]
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
multicall_fault(val.faultCode, val.faultString)
end
end
end
end
else
multicall_fault(ERR_MC_EXPECTED_STRUCT, "system.multicall expected struct")
end
}
end # end add_handler
self
end
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
obj.methods.each {|meth| methods << name + meth}
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"]
sigs << sig
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|
help = nil
@handler.each do |name, obj, sig, hlp|
if obj.kind_of? Proc and name == meth
help = hlp
break
end
end
help || ""
end
self
end
def process(data)
method, params = parser().parseMethodCall(data)
handle(method, *params)
end
private # --------------------------------------------------------------
def multicall_fault(nr, str)
{"faultCode" => nr, "faultString" => str}
end
#
# method dispatch
#
def dispatch(methodname, *args)
for name, obj in @handler
if obj.kind_of? Proc
next unless methodname == name
else
next unless methodname =~ /^#{name}(.+)$/
next unless obj.respond_to? $1
obj = obj.method($1)
end
if check_arity(obj, args.size)
if @service_hook.nil?
return obj.call(*args)
else
return @service_hook.call(obj, *args)
end
end
end
if @default_handler.nil?
raise XMLRPC::FaultException.new(ERR_METHOD_MISSING, "Method #{methodname} missing or wrong number of parameters!")
else
@default_handler.call(methodname, *args)
end
end
#
# returns true, if the arity of "obj" matches
#
def check_arity(obj, n_args)
ary = obj.arity
if ary >= 0
n_args == ary
else
n_args >= (ary+1).abs
end
end
def call_method(methodname, *args)
begin
[true, dispatch(methodname, *args)]
rescue XMLRPC::FaultException => e
[false, e]
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
=begin
= XMLRPC::CGIServer
== Synopsis
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
== Description
Implements a CGI-based XML-RPC server.
== Superclass
((<XMLRPC::BasicServer>))
== Class Methods
--- XMLRPC::CGIServer.new( *a )
Creates a new (({XMLRPC::CGIServer})) instance. All parameters given
are by-passed to ((<XMLRPC::BasicServer.new>)). You can only create
((*one*)) (({XMLRPC::CGIServer})) instance, because more than one makes
no sense.
== Instance Methods
--- XMLRPC::CGIServer#serve
Call this after you have added all you handlers to the server.
This method processes a XML-RPC methodCall and sends the answer
back to the client.
Make sure that you don't write to standard-output in a handler, or in
any other part of your program, this would case a CGI-based server to fail!
=end
class CGIServer < BasicServer
@@obj = nil
def CGIServer.new(*a)
@@obj = super(*a) if @@obj.nil?
@@obj
end
def initialize(*a)
super(*a)
end
def serve
catch(:exit_serve) {
length = ENV['CONTENT_LENGTH'].to_i
http_error(405, "Method Not Allowed") unless ENV['REQUEST_METHOD'] == "POST"
http_error(400, "Bad Request") unless ENV['CONTENT_TYPE'] == "text/xml"
http_error(411, "Length Required") unless length > 0
# TODO: do we need a call to binmode?
$stdin.binmode if $stdin.respond_to? :binmode
data = $stdin.read(length)
http_error(400, "Bad Request") if data.nil? or data.size != length
http_write(process(data), "Content-type" => "text/xml")
}
end
private
def http_error(status, message)
err = "#{status} #{message}"
msg = <<-"MSGEND"
<html>
<head>
<title>#{err}</title>
</head>
<body>
<h1>#{err}</h1>
<p>Unexpected error occured while processing XML-RPC request!</p>
</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"
h['Content-length'] ||= body.size.to_s
str = ""
h.each {|key, value| str << "#{key}: #{value}\r\n"}
str << "\r\n#{body}"
print str
end
end
=begin
= XMLRPC::ModRubyServer
== Description
Implements a XML-RPC server, which works with Apache mod_ruby.
Use it in the same way as CGIServer!
== Superclass
((<XMLRPC::BasicServer>))
=end
class ModRubyServer < BasicServer
@@obj = nil
def ModRubyServer.new(*a)
@@obj = super(*a) if @@obj.nil?
@@obj
end
def initialize(*a)
@ap = Apache::request
super(*a)
end
def serve
catch(:exit_serve) {
header = {}
@ap.each_header {|key, value| header[key.capitalize] = value}
length = header['Content-length'].to_i
http_error(405, "Method Not Allowed") unless @ap.request_method == "POST"
http_error(400, "Bad Request") unless parse_content_type(header['Content-type']).first == "text/xml"
http_error(411, "Length Required") unless length > 0
# TODO: do we need a call to binmode?
@ap.binmode
data = @ap.read(length)
http_error(400, "Bad Request") if data.nil? or data.size != length
http_write(process(data), 200, "Content-type" => "text/xml")
}
end
private
def http_error(status, message)
err = "#{status} #{message}"
msg = <<-"MSGEND"
<html>
<head>
<title>#{err}</title>
</head>
<body>
<h1>#{err}</h1>
<p>Unexpected error occured while processing XML-RPC request!</p>
</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"
h['Content-length'] ||= body.size.to_s
h.each {|key, value| @ap[key] = value }
@ap.content_type = h["Content-type"]
@ap.status = status.to_i
@ap.send_http_header
@ap.print body
end
end
=begin
= XMLRPC::Server
== Synopsis
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
== Description
Implements a standalone XML-RPC server. The method (({serve}))) is left if a SIGHUP is sent to the
program.
== Superclass
((<XMLRPC::BasicServer>))
== Class Methods
--- XMLRPC::Server.new( port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a )
Creates a new (({XMLRPC::Server})) instance, which is a XML-RPC server listening on
port ((|port|)) and accepts requests for the host ((|host|)), which is by default only the localhost.
The server is not started, to start it you have to call ((<serve|XMLRPC::Server#serve>)).
The parameters ((|maxConnections|)), ((|stdlog|)), ((|audit|)) and ((|debug|)) are passed to the HTTP server and
specify it's behaviour more precise.
All additionally given parameters in ((|*a|)) are by-passed to ((<XMLRPC::BasicServer.new>)).
== Instance Methods
--- XMLRPC::Server#serve
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.
--- XMLRPC::Server#shutdown
Stops and shuts the server down.
--- XMLRPC::Server#set_valid_ip( *ip_addr )
Specifies the valid IP addresses that are allowed to connect to the server.
Each IP is either a (({String})) or a (({Regexp})).
--- XMLRPC::Server#get_valid_ip
Return the via method ((<set_valid_ip|XMLRPC::Server#set_valid_ip>)) specified
valid IP addresses.
=end
class Server < BasicServer
def initialize(port=8080, host="127.0.0.1", maxConnections=4, stdlog=$stdout, audit=true, debug=true, *a)
super(*a)
@server = ::HttpServer.new(self, port, host, maxConnections, stdlog, audit, debug)
@valid_ip = nil
end
def serve
if RUBY_PLATFORM =~ /mingw|mswin32/
signal = 1
else
signal = "HUP"
end
trap(signal) { @server.shutdown }
@server.start.join
end
def shutdown
@server.shutdown
end
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
def get_valid_ip
@valid_ip
end
# methods that get called by HttpServer ------------------------------------------
def request_handler(request, response)
$stderr.puts "in request_handler" if $DEBUG
if request.method != "POST"
# Method not allowed
response.status = 405
return
end
if parse_content_type(request.header['Content-type']).first != "text/xml"
# Bad request
response.status = 400
return
end
length = request.content_length || 0
unless length > 0
# Length required
response.status = 411
return
end
data = request.data.read(length)
if data.nil? or data.size != length
# Bad request
response.status = 400
return
end
resp = process(data)
raise if resp.nil? or resp.size <= 0 # => Internal Server Error
response.status = 200
response.header['Content-Length'] = resp.size
response.header['Content-Type'] = "text/xml"
response.body = resp
end
##
# Is called before request_handler and should return true if
# the client is allowed to connect to the server.
# `io' is a Socket object.
def ip_auth_handler(io)
if @valid_ip
client_ip = io.peeraddr[3]
@valid_ip.each { |ip|
return true if client_ip =~ ip
}
false
else
true
end
end
end
=begin
= XMLRPC::WEBrickServlet
== Synopsis
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
== Description
Implements a servlet for use with WEBrick, a pure Ruby (HTTP-) server framework.
== Superclass
((<XMLRPC::BasicServer>))
=end
class WEBrickServlet < BasicServer
def initialize(*a)
super
require "webrick/httpstatus"
end
# deprecated from WEBrick/1.2.2.
# but does not break anything.
def require_path_info?
false
end
def get_instance(config, *options)
# TODO: set config & options
self
end
def service(request, response)
if request.request_method != "POST"
raise HTTPStatus::MethodNotAllowed,
"unsupported method `#{request.request_method}'."
end
if parse_content_type(request['Content-type']).first != "text/xml"
raise HTTPStatus::BadRequest
end
length = (request['Content-length'] || 0).to_i
raise HTTPStatus::LengthRequired unless length > 0
data = request.body
if data.nil? or data.size != length
raise HTTPStatus::BadRequest
end
resp = process(data)
if resp.nil? or resp.size <= 0
raise HTTPStatus::InternalServerError
end
response.status = 200
response['Content-Length'] = resp.size
response['Content-Type'] = "text/xml"
response.body = resp
end
end
end # module XMLRPC
=begin
= History
$Id$
=end

172
lib/xmlrpc/utils.rb Normal file
View file

@ -0,0 +1,172 @@
#
# Defines ParserWriterChooseMixin, which makes it possible to choose a
# different XML writer and/or XML parser then the default one.
# The Mixin is used in client.rb (class Client) and server.rb (class
# BasicServer)
#
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
#
# $Id$
#
module XMLRPC
#
# This module enables a user-class to be marshalled
# by XML-RPC for Ruby into a Hash, with one additional
# key/value pair "___class___" => ClassName
#
module Marshallable
def __get_instance_variables
instance_variables.collect {|var| [var[1..-1], eval(var)] }
end
def __set_instance_variable(key, value)
eval("@#$1 = value") if key =~ /^([\w_][\w_0-9]*)$/
end
end
module ParserWriterChooseMixin
def set_writer(writer)
@create = Create.new(writer)
self
end
def set_parser(parser)
@parser = parser
self
end
private
def create
# if set_writer was not already called then call it now
if @create.nil? then
set_writer(Config::DEFAULT_WRITER.new)
end
@create
end
def parser
# if set_parser was not already called then call it now
if @parser.nil? then
set_parser(Config::DEFAULT_PARSER.new)
end
@parser
end
end # module ParserWriterChooseMixin
module Service
#
# base class for Service Interface definitions, used
# by BasicServer#add_handler
#
class BasicInterface
attr_reader :prefix, :methods
def initialize(prefix)
@prefix = prefix
@methods = []
end
def add_method(sig, help=nil, meth_name=nil)
mname = nil
sig = [sig] if sig.kind_of? String
sig = sig.collect do |s|
name, si = parse_sig(s)
raise "Wrong signatures!" if mname != nil and name != mname
mname = name
si
end
@methods << [mname, meth_name || mname, sig, help]
end
private # ---------------------------------
def parse_sig(sig)
# sig is a String
if sig =~ /^\s*(\w+)\s+([^(]+)(\(([^)]*)\))?\s*$/
params = [$1]
name = $2.strip
$4.split(",").each {|i| params << i.strip} if $4 != nil
return name, params
else
raise "Syntax error in signature"
end
end
end # class BasicInterface
#
# class which wraps a Service Interface definition, used
# by BasicServer#add_handler
#
class Interface < BasicInterface
def initialize(prefix, &p)
raise "No interface specified" if p.nil?
super(prefix)
instance_eval &p
end
def get_methods(obj, delim=".")
prefix = @prefix + delim
@methods.collect { |name, meth, sig, help|
[prefix + name, obj.method(meth).to_proc, sig, help]
}
end
private # ---------------------------------
def meth(*a)
add_method(*a)
end
end # class Interface
class PublicInstanceMethodsInterface < BasicInterface
def initialize(prefix)
super(prefix)
end
def get_methods(obj, delim=".")
prefix = @prefix + delim
obj.class.public_instance_methods.collect { |name|
[prefix + name, obj.method(name).to_proc, nil, nil]
}
end
end
end # module Service
#
# short-form to create a Service::Interface
#
def self.interface(prefix, &p)
Service::Interface.new(prefix, &p)
end
# short-cut for creating a PublicInstanceMethodsInterface
def self.iPIMethods(prefix)
Service::PublicInstanceMethodsInterface.new(prefix)
end
module ParseContentType
def parse_content_type(str)
a, *b = str.split(";")
return a.strip, *b
end
end
end # module XMLRPC