xmlrpc 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +56 -0
- data/README.md +58 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/xmlrpc.rb +293 -0
- data/lib/xmlrpc/base64.rb +63 -0
- data/lib/xmlrpc/client.rb +629 -0
- data/lib/xmlrpc/config.rb +39 -0
- data/lib/xmlrpc/create.rb +287 -0
- data/lib/xmlrpc/datetime.rb +130 -0
- data/lib/xmlrpc/marshal.rb +67 -0
- data/lib/xmlrpc/parser.rb +642 -0
- data/lib/xmlrpc/server.rb +708 -0
- data/lib/xmlrpc/utils.rb +172 -0
- data/xmlrpc.gemspec +26 -0
- metadata +107 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
#
|
3
|
+
# xmlrpc/base64.rb
|
4
|
+
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
|
5
|
+
#
|
6
|
+
# Released under the same term of license as Ruby.
|
7
|
+
|
8
|
+
module XMLRPC # :nodoc:
|
9
|
+
|
10
|
+
# This class is necessary for 'xmlrpc4r' to determine that a string should
|
11
|
+
# be transmitted base64-encoded and not as a raw-string.
|
12
|
+
#
|
13
|
+
# You can use XMLRPC::Base64 on the client and server-side as a
|
14
|
+
# parameter and/or return-value.
|
15
|
+
class Base64
|
16
|
+
|
17
|
+
# Creates a new XMLRPC::Base64 instance with string +str+ as the
|
18
|
+
# internal string. When +state+ is +:dec+ it assumes that the
|
19
|
+
# string +str+ is not in base64 format (perhaps already decoded),
|
20
|
+
# otherwise if +state+ is +:enc+ it decodes +str+
|
21
|
+
# and stores it as the internal string.
|
22
|
+
def initialize(str, state = :dec)
|
23
|
+
case state
|
24
|
+
when :enc
|
25
|
+
@str = Base64.decode(str)
|
26
|
+
when :dec
|
27
|
+
@str = str
|
28
|
+
else
|
29
|
+
raise ArgumentError, "wrong argument; either :enc or :dec"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns the decoded internal string.
|
34
|
+
def decoded
|
35
|
+
@str
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the base64 encoded internal string.
|
39
|
+
def encoded
|
40
|
+
Base64.encode(@str)
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
# Decodes string +str+ with base64 and returns that value.
|
45
|
+
def Base64.decode(str)
|
46
|
+
str.gsub(/\s+/, "").unpack("m")[0]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Encodes string +str+ with base64 and returns that value.
|
50
|
+
def Base64.encode(str)
|
51
|
+
[str].pack("m")
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
end # module XMLRPC
|
58
|
+
|
59
|
+
|
60
|
+
=begin
|
61
|
+
= History
|
62
|
+
$Id$
|
63
|
+
=end
|
@@ -0,0 +1,629 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
# xmlrpc/client.rb
|
3
|
+
# Copyright (C) 2001, 2002, 2003 by Michael Neumann (mneumann@ntecs.de)
|
4
|
+
#
|
5
|
+
# Released under the same term of license as Ruby.
|
6
|
+
#
|
7
|
+
# History
|
8
|
+
# $Id$
|
9
|
+
#
|
10
|
+
require "xmlrpc/parser"
|
11
|
+
require "xmlrpc/create"
|
12
|
+
require "xmlrpc/config"
|
13
|
+
require "xmlrpc/utils" # ParserWriterChooseMixin
|
14
|
+
require "net/http"
|
15
|
+
require "uri"
|
16
|
+
|
17
|
+
module XMLRPC # :nodoc:
|
18
|
+
|
19
|
+
# Provides remote procedure calls to a XML-RPC server.
|
20
|
+
#
|
21
|
+
# After setting the connection-parameters with XMLRPC::Client.new which
|
22
|
+
# creates a new XMLRPC::Client instance, you can execute a remote procedure
|
23
|
+
# by sending the XMLRPC::Client#call or XMLRPC::Client#call2
|
24
|
+
# message to this new instance.
|
25
|
+
#
|
26
|
+
# The given parameters indicate which method to call on the remote-side and
|
27
|
+
# of course the parameters for the remote procedure.
|
28
|
+
#
|
29
|
+
# require "xmlrpc/client"
|
30
|
+
#
|
31
|
+
# server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
|
32
|
+
# begin
|
33
|
+
# param = server.call("michael.add", 4, 5)
|
34
|
+
# puts "4 + 5 = #{param}"
|
35
|
+
# rescue XMLRPC::FaultException => e
|
36
|
+
# puts "Error:"
|
37
|
+
# puts e.faultCode
|
38
|
+
# puts e.faultString
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# or
|
42
|
+
#
|
43
|
+
# require "xmlrpc/client"
|
44
|
+
#
|
45
|
+
# server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
|
46
|
+
# ok, param = server.call2("michael.add", 4, 5)
|
47
|
+
# if ok then
|
48
|
+
# puts "4 + 5 = #{param}"
|
49
|
+
# else
|
50
|
+
# puts "Error:"
|
51
|
+
# puts param.faultCode
|
52
|
+
# puts param.faultString
|
53
|
+
# end
|
54
|
+
class Client
|
55
|
+
|
56
|
+
USER_AGENT = "XMLRPC::Client (Ruby #{RUBY_VERSION})"
|
57
|
+
|
58
|
+
include ParserWriterChooseMixin
|
59
|
+
include ParseContentType
|
60
|
+
|
61
|
+
|
62
|
+
# Creates an object which represents the remote XML-RPC server on the
|
63
|
+
# given +host+. If the server is CGI-based, +path+ is the
|
64
|
+
# path to the CGI-script, which will be called, otherwise (in the
|
65
|
+
# case of a standalone server) +path+ should be <tt>"/RPC2"</tt>.
|
66
|
+
# +port+ is the port on which the XML-RPC server listens.
|
67
|
+
#
|
68
|
+
# If +proxy_host+ is given, then a proxy server listening at
|
69
|
+
# +proxy_host+ is used. +proxy_port+ is the port of the
|
70
|
+
# proxy server.
|
71
|
+
#
|
72
|
+
# Default values for +host+, +path+ and +port+ are 'localhost', '/RPC2' and
|
73
|
+
# '80' respectively using SSL '443'.
|
74
|
+
#
|
75
|
+
# If +user+ and +password+ are given, each time a request is sent,
|
76
|
+
# an Authorization header is sent. Currently only Basic Authentication is
|
77
|
+
# implemented, no Digest.
|
78
|
+
#
|
79
|
+
# If +use_ssl+ is set to +true+, communication over SSL is enabled.
|
80
|
+
#
|
81
|
+
# Parameter +timeout+ is the time to wait for a XML-RPC response, defaults to 30.
|
82
|
+
def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil,
|
83
|
+
user=nil, password=nil, use_ssl=nil, timeout=nil)
|
84
|
+
|
85
|
+
@http_header_extra = nil
|
86
|
+
@http_last_response = nil
|
87
|
+
@cookie = nil
|
88
|
+
|
89
|
+
@host = host || "localhost"
|
90
|
+
@path = path || "/RPC2"
|
91
|
+
@proxy_host = proxy_host
|
92
|
+
@proxy_port = proxy_port
|
93
|
+
@proxy_host ||= 'localhost' if @proxy_port != nil
|
94
|
+
@proxy_port ||= 8080 if @proxy_host != nil
|
95
|
+
@use_ssl = use_ssl || false
|
96
|
+
@timeout = timeout || 30
|
97
|
+
|
98
|
+
if use_ssl
|
99
|
+
require "net/https"
|
100
|
+
@port = port || 443
|
101
|
+
else
|
102
|
+
@port = port || 80
|
103
|
+
end
|
104
|
+
|
105
|
+
@user, @password = user, password
|
106
|
+
|
107
|
+
set_auth
|
108
|
+
|
109
|
+
# convert ports to integers
|
110
|
+
@port = @port.to_i if @port != nil
|
111
|
+
@proxy_port = @proxy_port.to_i if @proxy_port != nil
|
112
|
+
|
113
|
+
# HTTP object for synchronous calls
|
114
|
+
@http = net_http(@host, @port, @proxy_host, @proxy_port)
|
115
|
+
@http.use_ssl = @use_ssl if @use_ssl
|
116
|
+
@http.read_timeout = @timeout
|
117
|
+
@http.open_timeout = @timeout
|
118
|
+
|
119
|
+
@parser = nil
|
120
|
+
@create = nil
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
class << self
|
125
|
+
|
126
|
+
# Creates an object which represents the remote XML-RPC server at the
|
127
|
+
# given +uri+. The URI should have a host, port, path, user and password.
|
128
|
+
# Example: https://user:password@host:port/path
|
129
|
+
#
|
130
|
+
# Raises an ArgumentError if the +uri+ is invalid,
|
131
|
+
# or if the protocol isn't http or https.
|
132
|
+
#
|
133
|
+
# If a +proxy+ is given it should be in the form of "host:port".
|
134
|
+
#
|
135
|
+
# The optional +timeout+ defaults to 30 seconds.
|
136
|
+
def new2(uri, proxy=nil, timeout=nil)
|
137
|
+
begin
|
138
|
+
url = URI(uri)
|
139
|
+
rescue URI::InvalidURIError => e
|
140
|
+
raise ArgumentError, e.message, e.backtrace
|
141
|
+
end
|
142
|
+
|
143
|
+
unless URI::HTTP === url
|
144
|
+
raise ArgumentError, "Wrong protocol specified. Only http or https allowed!"
|
145
|
+
end
|
146
|
+
|
147
|
+
proto = url.scheme
|
148
|
+
user = url.user
|
149
|
+
passwd = url.password
|
150
|
+
host = url.host
|
151
|
+
port = url.port
|
152
|
+
path = url.path.empty? ? nil : url.request_uri
|
153
|
+
|
154
|
+
proxy_host, proxy_port = (proxy || "").split(":")
|
155
|
+
proxy_port = proxy_port.to_i if proxy_port
|
156
|
+
|
157
|
+
self.new(host, path, port, proxy_host, proxy_port, user, passwd, (proto == "https"), timeout)
|
158
|
+
end
|
159
|
+
|
160
|
+
alias new_from_uri new2
|
161
|
+
|
162
|
+
# Receives a Hash and calls XMLRPC::Client.new
|
163
|
+
# with the corresponding values.
|
164
|
+
#
|
165
|
+
# The +hash+ parameter has following case-insensitive keys:
|
166
|
+
# * host
|
167
|
+
# * path
|
168
|
+
# * port
|
169
|
+
# * proxy_host
|
170
|
+
# * proxy_port
|
171
|
+
# * user
|
172
|
+
# * password
|
173
|
+
# * use_ssl
|
174
|
+
# * timeout
|
175
|
+
def new3(hash={})
|
176
|
+
|
177
|
+
# convert all keys into lowercase strings
|
178
|
+
h = {}
|
179
|
+
hash.each { |k,v| h[k.to_s.downcase] = v }
|
180
|
+
|
181
|
+
self.new(h['host'], h['path'], h['port'], h['proxy_host'], h['proxy_port'], h['user'], h['password'],
|
182
|
+
h['use_ssl'], h['timeout'])
|
183
|
+
end
|
184
|
+
|
185
|
+
alias new_from_hash new3
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
# Returns the Net::HTTP object for the client. If you want to
|
191
|
+
# change HTTP client options except header, cookie, timeout,
|
192
|
+
# user and password, use Net::HTTP directly.
|
193
|
+
#
|
194
|
+
# Since 2.1.0.
|
195
|
+
attr_reader :http
|
196
|
+
|
197
|
+
# Add additional HTTP headers to the request
|
198
|
+
attr_accessor :http_header_extra
|
199
|
+
|
200
|
+
# Returns the Net::HTTPResponse object of the last RPC.
|
201
|
+
attr_reader :http_last_response
|
202
|
+
|
203
|
+
# Get and set the HTTP Cookie header.
|
204
|
+
attr_accessor :cookie
|
205
|
+
|
206
|
+
|
207
|
+
# Return the corresponding attributes.
|
208
|
+
attr_reader :timeout, :user, :password
|
209
|
+
|
210
|
+
# Sets the Net::HTTP#read_timeout and Net::HTTP#open_timeout to
|
211
|
+
# +new_timeout+
|
212
|
+
def timeout=(new_timeout)
|
213
|
+
@timeout = new_timeout
|
214
|
+
@http.read_timeout = @timeout
|
215
|
+
@http.open_timeout = @timeout
|
216
|
+
end
|
217
|
+
|
218
|
+
# Changes the user for the Basic Authentication header to +new_user+
|
219
|
+
def user=(new_user)
|
220
|
+
@user = new_user
|
221
|
+
set_auth
|
222
|
+
end
|
223
|
+
|
224
|
+
# Changes the password for the Basic Authentication header to
|
225
|
+
# +new_password+
|
226
|
+
def password=(new_password)
|
227
|
+
@password = new_password
|
228
|
+
set_auth
|
229
|
+
end
|
230
|
+
|
231
|
+
# Invokes the method named +method+ with the parameters given by
|
232
|
+
# +args+ on the XML-RPC server.
|
233
|
+
#
|
234
|
+
# The +method+ parameter is converted into a String and should
|
235
|
+
# be a valid XML-RPC method-name.
|
236
|
+
#
|
237
|
+
# Each parameter of +args+ must be of one of the following types,
|
238
|
+
# where Hash, Struct and Array can contain any of these listed _types_:
|
239
|
+
#
|
240
|
+
# * Fixnum, Bignum
|
241
|
+
# * TrueClass, FalseClass, +true+, +false+
|
242
|
+
# * String, Symbol
|
243
|
+
# * Float
|
244
|
+
# * Hash, Struct
|
245
|
+
# * Array
|
246
|
+
# * Date, Time, XMLRPC::DateTime
|
247
|
+
# * XMLRPC::Base64
|
248
|
+
# * A Ruby object which class includes XMLRPC::Marshallable
|
249
|
+
# (only if Config::ENABLE_MARSHALLING is +true+).
|
250
|
+
# That object is converted into a hash, with one additional key/value
|
251
|
+
# pair <code>___class___</code> which contains the class name
|
252
|
+
# for restoring that object later.
|
253
|
+
#
|
254
|
+
# The method returns the return-value from the Remote Procedure Call.
|
255
|
+
#
|
256
|
+
# The type of the return-value is one of the types shown above.
|
257
|
+
#
|
258
|
+
# A Bignum is only allowed when it fits in 32-bit. A XML-RPC
|
259
|
+
# +dateTime.iso8601+ type is always returned as a XMLRPC::DateTime object.
|
260
|
+
# Struct is never returned, only a Hash, the same for a Symbol, where as a
|
261
|
+
# String is always returned. XMLRPC::Base64 is returned as a String from
|
262
|
+
# xmlrpc4r version 1.6.1 on.
|
263
|
+
#
|
264
|
+
# If the remote procedure returned a fault-structure, then a
|
265
|
+
# XMLRPC::FaultException exception is raised, which has two accessor-methods
|
266
|
+
# +faultCode+ an Integer, and +faultString+ a String.
|
267
|
+
def call(method, *args)
|
268
|
+
ok, param = call2(method, *args)
|
269
|
+
if ok
|
270
|
+
param
|
271
|
+
else
|
272
|
+
raise param
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
# The difference between this method and XMLRPC::Client#call is, that
|
277
|
+
# this method will <b>NOT</b> raise a XMLRPC::FaultException exception.
|
278
|
+
#
|
279
|
+
# The method returns an array of two values. The first value indicates if
|
280
|
+
# the second value is +true+ or an XMLRPC::FaultException.
|
281
|
+
#
|
282
|
+
# Both are explained in XMLRPC::Client#call.
|
283
|
+
#
|
284
|
+
# Simple to remember: The "2" in "call2" denotes the number of values it returns.
|
285
|
+
def call2(method, *args)
|
286
|
+
request = create().methodCall(method, *args)
|
287
|
+
data = do_rpc(request, false)
|
288
|
+
parser().parseMethodResponse(data)
|
289
|
+
end
|
290
|
+
|
291
|
+
# Similar to XMLRPC::Client#call, however can be called concurrently and
|
292
|
+
# use a new connection for each request. In contrast to the corresponding
|
293
|
+
# method without the +_async+ suffix, which use connect-alive (one
|
294
|
+
# connection for all requests).
|
295
|
+
#
|
296
|
+
# Note, that you have to use Thread to call these methods concurrently.
|
297
|
+
# The following example calls two methods concurrently:
|
298
|
+
#
|
299
|
+
# Thread.new {
|
300
|
+
# p client.call_async("michael.add", 4, 5)
|
301
|
+
# }
|
302
|
+
#
|
303
|
+
# Thread.new {
|
304
|
+
# p client.call_async("michael.div", 7, 9)
|
305
|
+
# }
|
306
|
+
#
|
307
|
+
def call_async(method, *args)
|
308
|
+
ok, param = call2_async(method, *args)
|
309
|
+
if ok
|
310
|
+
param
|
311
|
+
else
|
312
|
+
raise param
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Same as XMLRPC::Client#call2, but can be called concurrently.
|
317
|
+
#
|
318
|
+
# See also XMLRPC::Client#call_async
|
319
|
+
def call2_async(method, *args)
|
320
|
+
request = create().methodCall(method, *args)
|
321
|
+
data = do_rpc(request, true)
|
322
|
+
parser().parseMethodResponse(data)
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
# You can use this method to execute several methods on a XMLRPC server
|
327
|
+
# which support the multi-call extension.
|
328
|
+
#
|
329
|
+
# s.multicall(
|
330
|
+
# ['michael.add', 3, 4],
|
331
|
+
# ['michael.sub', 4, 5]
|
332
|
+
# )
|
333
|
+
# # => [7, -1]
|
334
|
+
def multicall(*methods)
|
335
|
+
ok, params = multicall2(*methods)
|
336
|
+
if ok
|
337
|
+
params
|
338
|
+
else
|
339
|
+
raise params
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Same as XMLRPC::Client#multicall, but returns two parameters instead of
|
344
|
+
# raising an XMLRPC::FaultException.
|
345
|
+
#
|
346
|
+
# See XMLRPC::Client#call2
|
347
|
+
def multicall2(*methods)
|
348
|
+
gen_multicall(methods, false)
|
349
|
+
end
|
350
|
+
|
351
|
+
# Similar to XMLRPC::Client#multicall, however can be called concurrently and
|
352
|
+
# use a new connection for each request. In contrast to the corresponding
|
353
|
+
# method without the +_async+ suffix, which use connect-alive (one
|
354
|
+
# connection for all requests).
|
355
|
+
#
|
356
|
+
# Note, that you have to use Thread to call these methods concurrently.
|
357
|
+
# The following example calls two methods concurrently:
|
358
|
+
#
|
359
|
+
# Thread.new {
|
360
|
+
# p client.multicall_async("michael.add", 4, 5)
|
361
|
+
# }
|
362
|
+
#
|
363
|
+
# Thread.new {
|
364
|
+
# p client.multicall_async("michael.div", 7, 9)
|
365
|
+
# }
|
366
|
+
#
|
367
|
+
def multicall_async(*methods)
|
368
|
+
ok, params = multicall2_async(*methods)
|
369
|
+
if ok
|
370
|
+
params
|
371
|
+
else
|
372
|
+
raise params
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# Same as XMLRPC::Client#multicall2, but can be called concurrently.
|
377
|
+
#
|
378
|
+
# See also XMLRPC::Client#multicall_async
|
379
|
+
def multicall2_async(*methods)
|
380
|
+
gen_multicall(methods, true)
|
381
|
+
end
|
382
|
+
|
383
|
+
|
384
|
+
# Returns an object of class XMLRPC::Client::Proxy, initialized with
|
385
|
+
# +prefix+ and +args+.
|
386
|
+
#
|
387
|
+
# A proxy object returned by this method behaves like XMLRPC::Client#call,
|
388
|
+
# i.e. a call on that object will raise a XMLRPC::FaultException when a
|
389
|
+
# fault-structure is returned by that call.
|
390
|
+
def proxy(prefix=nil, *args)
|
391
|
+
Proxy.new(self, prefix, args, :call)
|
392
|
+
end
|
393
|
+
|
394
|
+
# Almost the same like XMLRPC::Client#proxy only that a call on the returned
|
395
|
+
# XMLRPC::Client::Proxy object will return two parameters.
|
396
|
+
#
|
397
|
+
# See XMLRPC::Client#call2
|
398
|
+
def proxy2(prefix=nil, *args)
|
399
|
+
Proxy.new(self, prefix, args, :call2)
|
400
|
+
end
|
401
|
+
|
402
|
+
# Similar to XMLRPC::Client#proxy, however can be called concurrently and
|
403
|
+
# use a new connection for each request. In contrast to the corresponding
|
404
|
+
# method without the +_async+ suffix, which use connect-alive (one
|
405
|
+
# connection for all requests).
|
406
|
+
#
|
407
|
+
# Note, that you have to use Thread to call these methods concurrently.
|
408
|
+
# The following example calls two methods concurrently:
|
409
|
+
#
|
410
|
+
# Thread.new {
|
411
|
+
# p client.proxy_async("michael.add", 4, 5)
|
412
|
+
# }
|
413
|
+
#
|
414
|
+
# Thread.new {
|
415
|
+
# p client.proxy_async("michael.div", 7, 9)
|
416
|
+
# }
|
417
|
+
#
|
418
|
+
def proxy_async(prefix=nil, *args)
|
419
|
+
Proxy.new(self, prefix, args, :call_async)
|
420
|
+
end
|
421
|
+
|
422
|
+
# Same as XMLRPC::Client#proxy2, but can be called concurrently.
|
423
|
+
#
|
424
|
+
# See also XMLRPC::Client#proxy_async
|
425
|
+
def proxy2_async(prefix=nil, *args)
|
426
|
+
Proxy.new(self, prefix, args, :call2_async)
|
427
|
+
end
|
428
|
+
|
429
|
+
|
430
|
+
private
|
431
|
+
|
432
|
+
def net_http(host, port, proxy_host, proxy_port)
|
433
|
+
Net::HTTP.new host, port, proxy_host, proxy_port
|
434
|
+
end
|
435
|
+
|
436
|
+
def dup_net_http
|
437
|
+
http = net_http(@http.address,
|
438
|
+
@http.port,
|
439
|
+
@http.proxy_address,
|
440
|
+
@http.proxy_port)
|
441
|
+
http.proxy_user = @http.proxy_user
|
442
|
+
http.proxy_pass = @http.proxy_pass
|
443
|
+
if @http.use_ssl?
|
444
|
+
http.use_ssl = true
|
445
|
+
Net::HTTP::SSL_ATTRIBUTES.each do |attribute|
|
446
|
+
http.__send__("#{attribute}=", @http.__send__(attribute))
|
447
|
+
end
|
448
|
+
end
|
449
|
+
http.read_timeout = @http.read_timeout
|
450
|
+
http.open_timeout = @http.open_timeout
|
451
|
+
http
|
452
|
+
end
|
453
|
+
|
454
|
+
def set_auth
|
455
|
+
if @user.nil?
|
456
|
+
@auth = nil
|
457
|
+
else
|
458
|
+
a = "#@user"
|
459
|
+
a << ":#@password" if @password != nil
|
460
|
+
@auth = "Basic " + [a].pack("m0")
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def do_rpc(request, async=false)
|
465
|
+
header = {
|
466
|
+
"User-Agent" => USER_AGENT,
|
467
|
+
"Content-Type" => "text/xml; charset=utf-8",
|
468
|
+
"Content-Length" => request.bytesize.to_s,
|
469
|
+
"Connection" => (async ? "close" : "keep-alive")
|
470
|
+
}
|
471
|
+
|
472
|
+
header["Cookie"] = @cookie if @cookie
|
473
|
+
header.update(@http_header_extra) if @http_header_extra
|
474
|
+
|
475
|
+
if @auth != nil
|
476
|
+
# add authorization header
|
477
|
+
header["Authorization"] = @auth
|
478
|
+
end
|
479
|
+
|
480
|
+
resp = nil
|
481
|
+
@http_last_response = nil
|
482
|
+
|
483
|
+
if async
|
484
|
+
# use a new HTTP object for each call
|
485
|
+
http = dup_net_http
|
486
|
+
|
487
|
+
# post request
|
488
|
+
http.start {
|
489
|
+
resp = http.request_post(@path, request, header)
|
490
|
+
}
|
491
|
+
else
|
492
|
+
# reuse the HTTP object for each call => connection alive is possible
|
493
|
+
# we must start connection explicitly first time so that http.request
|
494
|
+
# does not assume that we don't want keepalive
|
495
|
+
@http.start if not @http.started?
|
496
|
+
|
497
|
+
# post request
|
498
|
+
resp = @http.request_post(@path, request, header)
|
499
|
+
end
|
500
|
+
|
501
|
+
@http_last_response = resp
|
502
|
+
|
503
|
+
data = resp.body
|
504
|
+
|
505
|
+
if resp.code == "401"
|
506
|
+
# Authorization Required
|
507
|
+
raise "Authorization failed.\nHTTP-Error: #{resp.code} #{resp.message}"
|
508
|
+
elsif resp.code[0,1] != "2"
|
509
|
+
raise "HTTP-Error: #{resp.code} #{resp.message}"
|
510
|
+
end
|
511
|
+
|
512
|
+
# assume text/xml on instances where Content-Type header is not set
|
513
|
+
ct_expected = resp["Content-Type"] || 'text/xml'
|
514
|
+
ct = parse_content_type(ct_expected).first
|
515
|
+
if ct != "text/xml"
|
516
|
+
if ct == "text/html"
|
517
|
+
raise "Wrong content-type (received '#{ct}' but expected 'text/xml'): \n#{data}"
|
518
|
+
else
|
519
|
+
raise "Wrong content-type (received '#{ct}' but expected 'text/xml')"
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
expected = resp["Content-Length"] || "<unknown>"
|
524
|
+
if data.nil? or data.bytesize == 0
|
525
|
+
raise "Wrong size. Was #{data.bytesize}, should be #{expected}"
|
526
|
+
end
|
527
|
+
|
528
|
+
parse_set_cookies(resp.get_fields("Set-Cookie"))
|
529
|
+
|
530
|
+
return data
|
531
|
+
end
|
532
|
+
|
533
|
+
def parse_set_cookies(set_cookies)
|
534
|
+
return if set_cookies.nil?
|
535
|
+
return if set_cookies.empty?
|
536
|
+
require 'webrick/cookie'
|
537
|
+
pairs = {}
|
538
|
+
set_cookies.each do |set_cookie|
|
539
|
+
cookie = WEBrick::Cookie.parse_set_cookie(set_cookie)
|
540
|
+
pairs.delete(cookie.name)
|
541
|
+
pairs[cookie.name] = cookie.value
|
542
|
+
end
|
543
|
+
cookies = pairs.collect do |name, value|
|
544
|
+
WEBrick::Cookie.new(name, value).to_s
|
545
|
+
end
|
546
|
+
@cookie = cookies.join("; ")
|
547
|
+
end
|
548
|
+
|
549
|
+
def gen_multicall(methods=[], async=false)
|
550
|
+
meth = :call2
|
551
|
+
meth = :call2_async if async
|
552
|
+
|
553
|
+
ok, params = self.send(meth, "system.multicall",
|
554
|
+
methods.collect {|m| {'methodName' => m[0], 'params' => m[1..-1]} }
|
555
|
+
)
|
556
|
+
|
557
|
+
if ok
|
558
|
+
params = params.collect do |param|
|
559
|
+
if param.is_a? Array
|
560
|
+
param[0]
|
561
|
+
elsif param.is_a? Hash
|
562
|
+
XMLRPC::FaultException.new(param["faultCode"], param["faultString"])
|
563
|
+
else
|
564
|
+
raise "Wrong multicall return value"
|
565
|
+
end
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
return ok, params
|
570
|
+
end
|
571
|
+
|
572
|
+
|
573
|
+
|
574
|
+
# XML-RPC calls look nicer!
|
575
|
+
#
|
576
|
+
# You can call any method onto objects of that class - the object handles
|
577
|
+
# XMLRPC::Client::Proxy#method_missing and will forward the method call to
|
578
|
+
# a XML-RPC server.
|
579
|
+
#
|
580
|
+
# Don't use this class directly, instead use the public instance method
|
581
|
+
# XMLRPC::Client#proxy or XMLRPC::Client#proxy2.
|
582
|
+
#
|
583
|
+
# require "xmlrpc/client"
|
584
|
+
#
|
585
|
+
# server = XMLRPC::Client.new("www.ruby-lang.org", "/RPC2", 80)
|
586
|
+
#
|
587
|
+
# michael = server.proxy("michael")
|
588
|
+
# michael2 = server.proxy("michael", 4)
|
589
|
+
#
|
590
|
+
# # both calls should return the same value '9'.
|
591
|
+
# p michael.add(4,5)
|
592
|
+
# p michael2.add(5)
|
593
|
+
class Proxy
|
594
|
+
|
595
|
+
# Creates an object which provides XMLRPC::Client::Proxy#method_missing.
|
596
|
+
#
|
597
|
+
# The given +server+ must be an instance of XMLRPC::Client, which is the
|
598
|
+
# XML-RPC server to be used for a XML-RPC call.
|
599
|
+
#
|
600
|
+
# +prefix+ and +delim+ will be prepended to the method name called onto this object.
|
601
|
+
#
|
602
|
+
# An optional parameter +meth+ is the method to use for a RPC.
|
603
|
+
# It can be either, call, call2, call_async, call2_async
|
604
|
+
#
|
605
|
+
# +args+ are arguments which are automatically given to every XML-RPC
|
606
|
+
# call before being provided through +method_missing+.
|
607
|
+
def initialize(server, prefix, args=[], meth=:call, delim=".")
|
608
|
+
@server = server
|
609
|
+
@prefix = prefix ? prefix + delim : ""
|
610
|
+
@args = args
|
611
|
+
@meth = meth
|
612
|
+
end
|
613
|
+
|
614
|
+
# Every method call is forwarded to the XML-RPC server defined in
|
615
|
+
# XMLRPC::Client::Proxy#new.
|
616
|
+
#
|
617
|
+
# Note: Inherited methods from class Object cannot be used as XML-RPC
|
618
|
+
# names, because they get around +method_missing+.
|
619
|
+
def method_missing(mid, *args)
|
620
|
+
pre = @prefix + mid.to_s
|
621
|
+
arg = @args + args
|
622
|
+
@server.send(@meth, pre, *arg)
|
623
|
+
end
|
624
|
+
|
625
|
+
end # class Proxy
|
626
|
+
|
627
|
+
end # class Client
|
628
|
+
|
629
|
+
end # module XMLRPC
|