diff options
author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 |
---|---|---|
committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 |
commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/xmlrpc/client.rb |
Fresh start
Diffstat (limited to 'jni/ruby/lib/xmlrpc/client.rb')
-rw-r--r-- | jni/ruby/lib/xmlrpc/client.rb | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/jni/ruby/lib/xmlrpc/client.rb b/jni/ruby/lib/xmlrpc/client.rb new file mode 100644 index 0000000..34543b0 --- /dev/null +++ b/jni/ruby/lib/xmlrpc/client.rb @@ -0,0 +1,614 @@ +# xmlrpc/client.rb +# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de) +# +# Released under the same term of license as Ruby. +# +# History +# $Id: client.rb 46311 2014-06-02 00:43:29Z tmm1 $ +# +require "xmlrpc/parser" +require "xmlrpc/create" +require "xmlrpc/config" +require "xmlrpc/utils" # ParserWriterChooseMixin +require "net/http" +require "uri" + +module XMLRPC # :nodoc: + + # 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 XMLRPC::Client#call or 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. + # + # 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 + class Client + + USER_AGENT = "XMLRPC::Client (Ruby #{RUBY_VERSION})" + + include ParserWriterChooseMixin + include ParseContentType + + + # Creates an object which represents the remote XML-RPC server on the + # given +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 <tt>"/RPC2"</tt>. + # +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 sent, + # an Authorization header is sent. Currently only Basic Authentication is + # implemented, no Digest. + # + # If +use_ssl+ is set to +true+, communication over SSL is enabled. + # + # Parameter +timeout+ is the time to wait for a XML-RPC response, defaults to 30. + def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil, + user=nil, password=nil, use_ssl=nil, timeout=nil) + + @http_header_extra = nil + @http_last_response = nil + @cookie = 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 + @http = net_http(@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 + + + class << self + + # Creates an object which represents the remote XML-RPC server at the + # given +uri+. The URI should have a host, port, path, user and password. + # Example: https://user:password@host:port/path + # + # Raises an ArgumentError if the +uri+ is invalid, + # or if the protocol isn't http or https. + # + # If a +proxy+ is given it should be in the form of "host:port". + # + # The optional +timeout+ defaults to 30 seconds. + def new2(uri, proxy=nil, timeout=nil) + begin + url = URI(uri) + rescue URI::InvalidURIError => e + raise ArgumentError, e.message, e.backtrace + end + + unless URI::HTTP === url + raise ArgumentError, "Wrong protocol specified. Only http or https allowed!" + end + + proto = url.scheme + user = url.user + passwd = url.password + host = url.host + port = url.port + path = url.path.empty? ? nil : url.request_uri + + proxy_host, proxy_port = (proxy || "").split(":") + proxy_port = proxy_port.to_i if proxy_port + + self.new(host, path, port, proxy_host, proxy_port, user, passwd, (proto == "https"), timeout) + end + + alias new_from_uri new2 + + # Receives a Hash and calls XMLRPC::Client.new + # with the corresponding values. + # + # The +hash+ parameter has following case-insensitive keys: + # * host + # * path + # * port + # * proxy_host + # * proxy_port + # * user + # * password + # * use_ssl + # * timeout + def 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 + + alias new_from_hash new3 + + end + + + # Returns the Net::HTTP object for the client. If you want to + # change HTTP client options except header, cookie, timeout, + # user and password, use Net::HTTP directly. + # + # Since 2.1.0. + attr_reader :http + + # Add additional HTTP headers to the request + attr_accessor :http_header_extra + + # Returns the Net::HTTPResponse object of the last RPC. + attr_reader :http_last_response + + # Get and set the HTTP Cookie header. + attr_accessor :cookie + + + # Return the corresponding attributes. + attr_reader :timeout, :user, :password + + # Sets the Net::HTTP#read_timeout and Net::HTTP#open_timeout to + # +new_timeout+ + def timeout=(new_timeout) + @timeout = new_timeout + @http.read_timeout = @timeout + @http.open_timeout = @timeout + end + + # Changes the user for the Basic Authentication header to +new_user+ + def user=(new_user) + @user = new_user + set_auth + end + + # Changes the password for the Basic Authentication header to + # +new_password+ + def password=(new_password) + @password = new_password + set_auth + end + + # Invokes the method named +method+ with the parameters given by + # +args+ on the XML-RPC server. + # + # The +method+ parameter 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_MARSHALLING is +true+). + # That object is converted into a hash, with one additional key/value + # pair <code>___class___</code> which contains the class name + # for restoring that object later. + # + # The method returns the return-value from the Remote Procedure Call. + # + # The type of the return-value is one of the types shown above. + # + # A Bignum is only allowed when it fits in 32-bit. A XML-RPC + # +dateTime.iso8601+ type is always returned as a XMLRPC::DateTime object. + # Struct is never returned, only a Hash, the same for a Symbol, where as a + # String is always returned. 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+ an Integer, and +faultString+ a String. + def call(method, *args) + ok, param = call2(method, *args) + if ok + param + else + raise param + end + end + + # The difference between this method and XMLRPC::Client#call is, that + # this method will <b>NOT</b> raise a XMLRPC::FaultException exception. + # + # The method returns an array of two values. The first value indicates if + # the second value is +true+ or an XMLRPC::FaultException. + # + # Both are explained in XMLRPC::Client#call. + # + # Simple to remember: The "2" in "call2" denotes the number of values it returns. + def call2(method, *args) + request = create().methodCall(method, *args) + data = do_rpc(request, false) + parser().parseMethodResponse(data) + end + + # Similar to XMLRPC::Client#call, however can be called concurrently and + # use a new connection for each request. In contrast to the corresponding + # method without the +_async+ suffix, which use connect-alive (one + # connection for all requests). + # + # Note, that you have to use Thread 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) + # } + # + def call_async(method, *args) + ok, param = call2_async(method, *args) + if ok + param + else + raise param + end + end + + # Same as XMLRPC::Client#call2, but can be called concurrently. + # + # See also XMLRPC::Client#call_async + def call2_async(method, *args) + request = create().methodCall(method, *args) + data = do_rpc(request, true) + parser().parseMethodResponse(data) + end + + + # You can use this method to execute several methods on a XMLRPC server + # which support the multi-call extension. + # + # s.multicall( + # ['michael.add', 3, 4], + # ['michael.sub', 4, 5] + # ) + # # => [7, -1] + def multicall(*methods) + ok, params = multicall2(*methods) + if ok + params + else + raise params + end + end + + # Same as XMLRPC::Client#multicall, but returns two parameters instead of + # raising an XMLRPC::FaultException. + # + # See XMLRPC::Client#call2 + def multicall2(*methods) + gen_multicall(methods, false) + end + + # Similar to XMLRPC::Client#multicall, however can be called concurrently and + # use a new connection for each request. In contrast to the corresponding + # method without the +_async+ suffix, which use connect-alive (one + # connection for all requests). + # + # Note, that you have to use Thread to call these methods concurrently. + # The following example calls two methods concurrently: + # + # Thread.new { + # p client.multicall_async("michael.add", 4, 5) + # } + # + # Thread.new { + # p client.multicall_async("michael.div", 7, 9) + # } + # + def multicall_async(*methods) + ok, params = multicall2_async(*methods) + if ok + params + else + raise params + end + end + + # Same as XMLRPC::Client#multicall2, but can be called concurrently. + # + # See also XMLRPC::Client#multicall_async + def multicall2_async(*methods) + gen_multicall(methods, true) + end + + + # 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. + def proxy(prefix=nil, *args) + Proxy.new(self, prefix, args, :call) + end + + # Almost the same like XMLRPC::Client#proxy only that a call on the returned + # XMLRPC::Client::Proxy object will return two parameters. + # + # See XMLRPC::Client#call2 + def proxy2(prefix=nil, *args) + Proxy.new(self, prefix, args, :call2) + end + + # Similar to XMLRPC::Client#proxy, however can be called concurrently and + # use a new connection for each request. In contrast to the corresponding + # method without the +_async+ suffix, which use connect-alive (one + # connection for all requests). + # + # Note, that you have to use Thread to call these methods concurrently. + # The following example calls two methods concurrently: + # + # Thread.new { + # p client.proxy_async("michael.add", 4, 5) + # } + # + # Thread.new { + # p client.proxy_async("michael.div", 7, 9) + # } + # + def proxy_async(prefix=nil, *args) + Proxy.new(self, prefix, args, :call_async) + end + + # Same as XMLRPC::Client#proxy2, but can be called concurrently. + # + # See also XMLRPC::Client#proxy_async + def proxy2_async(prefix=nil, *args) + Proxy.new(self, prefix, args, :call2_async) + end + + + private + + def net_http(host, port, proxy_host, proxy_port) + Net::HTTP.new host, port, proxy_host, proxy_port + end + + def set_auth + if @user.nil? + @auth = nil + else + a = "#@user" + a << ":#@password" if @password != nil + @auth = "Basic " + [a].pack("m0") + end + end + + def do_rpc(request, async=false) + header = { + "User-Agent" => USER_AGENT, + "Content-Type" => "text/xml; charset=utf-8", + "Content-Length" => request.bytesize.to_s, + "Connection" => (async ? "close" : "keep-alive") + } + + header["Cookie"] = @cookie if @cookie + header.update(@http_header_extra) if @http_header_extra + + if @auth != nil + # add authorization header + header["Authorization"] = @auth + end + + resp = nil + @http_last_response = nil + + if async + # use a new HTTP object for each call + http = net_http(@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.request_post(@path, request, header) + } + else + # reuse the HTTP object for each call => connection alive is possible + # we must start connection explicitly first time so that http.request + # does not assume that we don't want keepalive + @http.start if not @http.started? + + # post request + resp = @http.request_post(@path, request, header) + end + + @http_last_response = resp + + 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 + + # assume text/xml on instances where Content-Type header is not set + ct_expected = resp["Content-Type"] || 'text/xml' + ct = parse_content_type(ct_expected).first + if ct != "text/xml" + if ct == "text/html" + raise "Wrong content-type (received '#{ct}' but expected 'text/xml'): \n#{data}" + else + raise "Wrong content-type (received '#{ct}' but expected 'text/xml')" + end + end + + expected = resp["Content-Length"] || "<unknown>" + if data.nil? or data.bytesize == 0 + raise "Wrong size. Was #{data.bytesize}, should be #{expected}" + end + + parse_set_cookies(resp.get_fields("Set-Cookie")) + + return data + end + + def parse_set_cookies(set_cookies) + return if set_cookies.nil? + return if set_cookies.empty? + require 'webrick/cookie' + pairs = {} + set_cookies.each do |set_cookie| + cookie = WEBrick::Cookie.parse_set_cookie(set_cookie) + pairs.delete(cookie.name) + pairs[cookie.name] = cookie.value + end + cookies = pairs.collect do |name, value| + WEBrick::Cookie.new(name, value).to_s + end + @cookie = cookies.join("; ") + 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 + + + + # XML-RPC calls look nicer! + # + # You can call any method onto objects of that class - the object handles + # XMLRPC::Client::Proxy#method_missing and will forward the method call to + # a XML-RPC server. + # + # Don't use this class directly, instead use the public instance method + # XMLRPC::Client#proxy or XMLRPC::Client#proxy2. + # + # 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) + class Proxy + + # Creates an object which provides XMLRPC::Client::Proxy#method_missing. + # + # The given +server+ must be an instance of XMLRPC::Client, which is the + # XML-RPC server to be used for a XML-RPC call. + # + # +prefix+ and +delim+ will be prepended to the method name called onto this object. + # + # An optional parameter +meth+ is the method to use for a RPC. + # It can be either, call, call2, call_async, call2_async + # + # +args+ are arguments which are automatically given to every XML-RPC + # call before being provided through +method_missing+. + def initialize(server, prefix, args=[], meth=:call, delim=".") + @server = server + @prefix = prefix ? prefix + delim : "" + @args = args + @meth = meth + end + + # Every method call is forwarded to the XML-RPC server defined in + # XMLRPC::Client::Proxy#new. + # + # Note: Inherited methods from class Object cannot be used as XML-RPC + # names, because they get around +method_missing+. + 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 + |