socialcast-rapuncel 0.0.7.RC1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/MIT-LICENSE +20 -0
- data/README.md +209 -0
- data/Rakefile +24 -0
- data/lib/rapuncel/adapters/curb_adapter.rb +43 -0
- data/lib/rapuncel/adapters/net_http_adapter.rb +60 -0
- data/lib/rapuncel/base_64_string.rb +36 -0
- data/lib/rapuncel/client/logging.rb +83 -0
- data/lib/rapuncel/client.rb +117 -0
- data/lib/rapuncel/connection.rb +65 -0
- data/lib/rapuncel/proxy.rb +91 -0
- data/lib/rapuncel/request.rb +10 -0
- data/lib/rapuncel/response.rb +69 -0
- data/lib/rapuncel/xml_rpc/deserializer.rb +116 -0
- data/lib/rapuncel/xml_rpc/serializer.rb +153 -0
- data/lib/rapuncel.rb +17 -0
- data/rapuncel-0.0.6.RC3.gem +0 -0
- data/rapuncel.gemspec +24 -0
- data/spec/functional/client_spec.rb +53 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/test_server.rb +30 -0
- data/spec/unit/array_spec.rb +41 -0
- data/spec/unit/base64_spec.rb +30 -0
- data/spec/unit/boolean_spec.rb +39 -0
- data/spec/unit/client_spec.rb +43 -0
- data/spec/unit/connection_spec.rb +22 -0
- data/spec/unit/custom_serialization_spec.rb +23 -0
- data/spec/unit/float_spec.rb +38 -0
- data/spec/unit/hash_spec.rb +54 -0
- data/spec/unit/int_spec.rb +23 -0
- data/spec/unit/nil_spec.rb +7 -0
- data/spec/unit/object_spec.rb +45 -0
- data/spec/unit/proxy_spec.rb +29 -0
- data/spec/unit/request_spec.rb +28 -0
- data/spec/unit/response_spec.rb +85 -0
- data/spec/unit/string_spec.rb +35 -0
- data/spec/unit/time_spec.rb +20 -0
- metadata +140 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rapuncel (0.0.6.RC3)
|
5
|
+
activesupport (>= 3.0.0)
|
6
|
+
nokogiri
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: http://www.rubygems.org/
|
10
|
+
specs:
|
11
|
+
activesupport (3.1.3)
|
12
|
+
multi_json (~> 1.0)
|
13
|
+
diff-lcs (1.1.2)
|
14
|
+
multi_json (1.0.4)
|
15
|
+
nokogiri (1.5.0)
|
16
|
+
rake (0.9.2)
|
17
|
+
rspec (2.6.0)
|
18
|
+
rspec-core (~> 2.6.0)
|
19
|
+
rspec-expectations (~> 2.6.0)
|
20
|
+
rspec-mocks (~> 2.6.0)
|
21
|
+
rspec-core (2.6.4)
|
22
|
+
rspec-expectations (2.6.0)
|
23
|
+
diff-lcs (~> 1.1.2)
|
24
|
+
rspec-mocks (2.6.0)
|
25
|
+
|
26
|
+
PLATFORMS
|
27
|
+
ruby
|
28
|
+
|
29
|
+
DEPENDENCIES
|
30
|
+
rake
|
31
|
+
rapuncel!
|
32
|
+
rspec (>= 2.6.0)
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Kayoom GmbH
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
# Rapuncel - Simple XML-RPC Client
|
2
|
+
[![Build Status](https://secure.travis-ci.org/cice/rapuncel.png)](http://travis-ci.org/cice/rapuncel)
|
3
|
+
|
4
|
+
Rapuncel ([wikipedia](http://en.wikipedia.org/wiki/Rapunzel)) is a simple and lightweight, but fast XML-RPC client library for ruby.
|
5
|
+
It's based on Nokogiri for XML parsing and thus provides a major performance improvement for large XML responses.
|
6
|
+
|
7
|
+
## I need your help
|
8
|
+
I currently have exactly 1 application for Rapuncel, and that's [Kangaroo](https://github.com/kayoom/kangaroo), i.e.
|
9
|
+
the OpenERP XMLRPC service, where it works absolutely fine. To improve Rapuncel i need your experience with
|
10
|
+
other services and their quirks. Just open a feature request, file a bug report, send me a message.
|
11
|
+
|
12
|
+
## Installation
|
13
|
+
|
14
|
+
### Rails
|
15
|
+
Add this to your Gemfile:
|
16
|
+
|
17
|
+
gem 'rapuncel'
|
18
|
+
|
19
|
+
Run
|
20
|
+
|
21
|
+
bundle install
|
22
|
+
|
23
|
+
and you're good to go.
|
24
|
+
|
25
|
+
### Other Ruby / IRB
|
26
|
+
Install it as gem:
|
27
|
+
|
28
|
+
gem install rapuncel
|
29
|
+
|
30
|
+
Require **rubygems** and **rapuncel**
|
31
|
+
|
32
|
+
require 'rubygems'
|
33
|
+
require 'rapuncel'
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
### Initialize client
|
38
|
+
Usage is pretty straightforward, Rapuncel provides a method proxy to send calls to your XMLRPC service like you would to a normal ruby
|
39
|
+
object.
|
40
|
+
First you have to create a client with the connection details, e.g.
|
41
|
+
|
42
|
+
client = Rapuncel::Client.new :host => 'localhost', :port => 8080, :path => '/xmlrpc'
|
43
|
+
|
44
|
+
Available options are:
|
45
|
+
|
46
|
+
* **host**
|
47
|
+
hostname or ip-address,
|
48
|
+
_default_: `localhost`
|
49
|
+
* **port**
|
50
|
+
port where your XMLRPC service is listening,
|
51
|
+
_default_: `8080`
|
52
|
+
* **path**
|
53
|
+
path to the service,
|
54
|
+
_default_: `/`
|
55
|
+
* **user**
|
56
|
+
Username for HTTP Authentication
|
57
|
+
_default_: _empty_
|
58
|
+
* **password**
|
59
|
+
Password for HTTP Authentication
|
60
|
+
_default_: _empty_
|
61
|
+
* **headers**
|
62
|
+
Hash to set additional HTTP headers for the request, e.g. to send an X-ApiKey header for authentication
|
63
|
+
_default_: `{}`
|
64
|
+
* **ssl**
|
65
|
+
Flag wether to use SSL
|
66
|
+
_default_: `false`
|
67
|
+
* **raise_on**
|
68
|
+
Lets you define the behavior on errors or faults, if set to `:fault`, `:error` or `:both`,
|
69
|
+
an Exception will be raised if something goes wrong
|
70
|
+
* **serialization**
|
71
|
+
Use your own (extended) (De)Serializers. See Custom Serialization
|
72
|
+
_default_: `Rapuncel::XmlRpc`
|
73
|
+
|
74
|
+
### Get a proxy object and ...
|
75
|
+
A proxy object receives ruby method calls, redirects them to your XMLRPC service and returns the response as ruby objects!
|
76
|
+
|
77
|
+
proxy = client.proxy
|
78
|
+
|
79
|
+
# suppose your XMLRPC service has a method exposed 'concat_string(string1, string2)'
|
80
|
+
proxy.concat_string "foo", "bar"
|
81
|
+
-> "foobar"
|
82
|
+
|
83
|
+
# if you need to access specific interfaces on your service, e.g. 'string.concat(string1, string2)'
|
84
|
+
proxy = client.proxy_for 'string'
|
85
|
+
proxy.concat 'foo', 'bar'
|
86
|
+
-> 'foobar'
|
87
|
+
|
88
|
+
## Supported objects
|
89
|
+
Rapuncel supports natively following object-types (and all their subclasses):
|
90
|
+
|
91
|
+
* Integer
|
92
|
+
* String
|
93
|
+
* Array
|
94
|
+
* Hash
|
95
|
+
* TrueClass, FalseClass
|
96
|
+
* Float / Double
|
97
|
+
* BigDecimal (treated like Float, unless you set `double_as_bigdecimal` to true)
|
98
|
+
* Time, Time-like objects
|
99
|
+
* Base64
|
100
|
+
|
101
|
+
* Symbols are converted to Strings
|
102
|
+
* All Hashs have symbol keys
|
103
|
+
* All other objects are transformed into a Hash ('struct' in XMLRPC-speak) containing their instance variables as key-value-pairs.
|
104
|
+
|
105
|
+
## Base64
|
106
|
+
If you want a string to be encoded as Base64 in your RPC call, just mark it:
|
107
|
+
|
108
|
+
proxy.some_method "my base64 encoded string".as_base64
|
109
|
+
|
110
|
+
Return values that arrive Base64 encoded, are instances of Rapuncel::Base64String,
|
111
|
+
which is a subclass of String, and therefore can be used as such, but allows you to differentiate
|
112
|
+
Base64 return values from normal String return values.
|
113
|
+
|
114
|
+
## Supported methods
|
115
|
+
You can use most methods via
|
116
|
+
|
117
|
+
proxy.methodname 'a', 1, [:a, :b], :a => :d
|
118
|
+
|
119
|
+
However methods starting with \_\_, or ending with a bang \! or a question mark ? are not supported. To call those methods you can always
|
120
|
+
use
|
121
|
+
|
122
|
+
proxy.call! 'methodname', *args
|
123
|
+
|
124
|
+
or via
|
125
|
+
|
126
|
+
client.call_to_ruby 'methodname', *args
|
127
|
+
|
128
|
+
note
|
129
|
+
|
130
|
+
client.call 'methodname', *args
|
131
|
+
|
132
|
+
will return a Rapuncel::Response object, use _call\_to\_ruby_ to get a parsed result
|
133
|
+
|
134
|
+
## Deserialization options
|
135
|
+
|
136
|
+
At the moment there are 2 options, to be set quite ugly as class attributes on Rapuncel::XmlRpc::Deserializer,
|
137
|
+
which will definitely change.
|
138
|
+
|
139
|
+
1. **double\_as\_bigdecimal**
|
140
|
+
Deserialize all `<double>` tags as BigDecimal.
|
141
|
+
2. **hash\_keys\_as\_string**
|
142
|
+
Don't use Symbols as keys for deserialized `<struct>`, but Strings.
|
143
|
+
|
144
|
+
## Custom Serialization
|
145
|
+
|
146
|
+
module MySpecialSerialization
|
147
|
+
class Serializer < Rapuncel::XmlRpc::Serializer
|
148
|
+
end
|
149
|
+
class Deserializer < Rapuncel::XmlRpc::Deserializer
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
client = Rapuncel::Client.new :serialization => MySpecialSerialization
|
154
|
+
# or :serialization => 'MySpecialSerialization'
|
155
|
+
|
156
|
+
## Todo ?
|
157
|
+
|
158
|
+
* RDoc
|
159
|
+
* Extensive functional tests
|
160
|
+
* HTTP-Proxy support
|
161
|
+
* Async requests
|
162
|
+
* XMLRPC Extensions (pluggable support)
|
163
|
+
* Apache vendor extensions
|
164
|
+
* How do i test SSL?
|
165
|
+
|
166
|
+
## What happens if something goes wrong?
|
167
|
+
### HTTP Errors / XMLRPC Faults
|
168
|
+
See Usage -> configuration -> raise\_on switch
|
169
|
+
### Malformed XML/XMLRPC
|
170
|
+
Rapuncel will most likely fail hard.
|
171
|
+
|
172
|
+
## Changelog
|
173
|
+
|
174
|
+
* **0.0.5**
|
175
|
+
* Refactored serialization, preparation for pluggable extensions
|
176
|
+
* Deserialization option "double\_as\_bigdecimal"
|
177
|
+
* Deserialization option "hash\_keys\_as\_string"
|
178
|
+
* base64 support
|
179
|
+
* Object#to\_xmlrpc now should expect a XmlRpc::Serializer instance,
|
180
|
+
not a Builder (you can access the Builder directly via XmlRpc::Serializer#builder)
|
181
|
+
|
182
|
+
## Open Source
|
183
|
+
|
184
|
+
### License
|
185
|
+
|
186
|
+
Copyright (c) 2011 Kayoom GmbH
|
187
|
+
|
188
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
189
|
+
a copy of this software and associated documentation files (the
|
190
|
+
"Software"), to deal in the Software without restriction, including
|
191
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
192
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
193
|
+
permit persons to whom the Software is furnished to do so, subject to
|
194
|
+
the following conditions:
|
195
|
+
|
196
|
+
The above copyright notice and this permission notice shall be
|
197
|
+
included in all copies or substantial portions of the Software.
|
198
|
+
|
199
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
200
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
201
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
202
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
203
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
204
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
205
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
206
|
+
|
207
|
+
### Contribution
|
208
|
+
|
209
|
+
Pull requests are very welcome!
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
|
6
|
+
desc 'Generate Rapuncel rdoc.'
|
7
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
8
|
+
rdoc.rdoc_dir = 'rdoc'
|
9
|
+
rdoc.title = 'Rapuncel'
|
10
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
11
|
+
rdoc.rdoc_files.include('README.md')
|
12
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'rspec/core/rake_task'
|
17
|
+
desc 'Run RSpec suite.'
|
18
|
+
RSpec::Core::RakeTask.new('spec')
|
19
|
+
rescue LoadError
|
20
|
+
puts "RSpec is not available. In order to run specs, you must: gem install rspec"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'Default: run unit tests.'
|
24
|
+
task :default => :spec
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'curb'
|
2
|
+
|
3
|
+
module Rapuncel
|
4
|
+
module Adapters
|
5
|
+
module CurbAdapter
|
6
|
+
# Small response wrapper
|
7
|
+
class HttpResponse
|
8
|
+
def initialize body, code
|
9
|
+
@body, @code = body, code
|
10
|
+
end
|
11
|
+
|
12
|
+
def success?
|
13
|
+
!(@code =~ /^2/)
|
14
|
+
end
|
15
|
+
|
16
|
+
def body
|
17
|
+
@body
|
18
|
+
end
|
19
|
+
|
20
|
+
def code
|
21
|
+
@code
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Dispatch a XMLRPC via HTTP and return a response object.
|
26
|
+
def send_method_call str
|
27
|
+
@curb ||= Curl::Easy.new "#{connection.protocol}://#{connection.host}:#{connection.port}#{connection.path}"
|
28
|
+
|
29
|
+
if connection.auth?
|
30
|
+
@curb.username = connection.user
|
31
|
+
@curb.password = connection.password
|
32
|
+
@curb.enable_cookies = true
|
33
|
+
@curb.cookiejar = connection.cookie_file_path
|
34
|
+
end
|
35
|
+
@curb.post_body = str
|
36
|
+
@curb.headers = @curb.headers.merge connection.headers
|
37
|
+
@curb.perform
|
38
|
+
|
39
|
+
HttpResponse.new @curb.body_str, @curb.response_code
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'cookiejar'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Rapuncel
|
7
|
+
module Adapters
|
8
|
+
module NetHttpAdapter
|
9
|
+
# Small response wrapper
|
10
|
+
class HttpResponse
|
11
|
+
def initialize response
|
12
|
+
@response = response
|
13
|
+
end
|
14
|
+
|
15
|
+
def success?
|
16
|
+
@response.is_a? Net::HTTPOK
|
17
|
+
end
|
18
|
+
|
19
|
+
def body
|
20
|
+
@response.body
|
21
|
+
end
|
22
|
+
|
23
|
+
def code
|
24
|
+
@response.code
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Dispatch a XMLRPC via HTTP and return a response object.
|
29
|
+
def send_method_call str
|
30
|
+
cookie_jar = if File.exists?(connection.cookie_file_path)
|
31
|
+
File.open(connection.cookie_file_path, 'r') do |file|
|
32
|
+
contents = file.read
|
33
|
+
if contents.empty?
|
34
|
+
CookieJar::Jar.new
|
35
|
+
else
|
36
|
+
CookieJar::Jar.json_create(JSON.parse(contents))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
else
|
40
|
+
CookieJar::Jar.new
|
41
|
+
end
|
42
|
+
cookie_header = {}
|
43
|
+
cookie_header['Cookie'] = cookie_jar.get_cookie_header("#{connection.ssl? ? 'https' : 'http'}://#{connection.host}/")
|
44
|
+
|
45
|
+
request = Net::HTTP::Post.new(connection.path, connection.headers.merge(cookie_header))
|
46
|
+
request.basic_auth connection.user, connection.password if connection.auth?
|
47
|
+
request.body= str
|
48
|
+
|
49
|
+
http = Net::HTTP.new(connection.host, connection.port)
|
50
|
+
http.use_ssl = connection.ssl?
|
51
|
+
http.set_debug_output(STDOUT)
|
52
|
+
response = http.request(request)
|
53
|
+
cookie_jar.set_cookie("#{connection.ssl? ? 'https' : 'http'}://#{connection.host}/", response.header['Set-Cookie'])
|
54
|
+
|
55
|
+
File.open(connection.cookie_file_path, 'w'){ |file| file.write(cookie_jar.to_json) }
|
56
|
+
HttpResponse.new response
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
if RUBY_VERSION =~ /^1\.8/
|
2
|
+
require 'base64'
|
3
|
+
end
|
4
|
+
|
5
|
+
class String
|
6
|
+
def as_base64
|
7
|
+
Rapuncel::Base64String.new self
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Rapuncel
|
12
|
+
class Base64String < String
|
13
|
+
|
14
|
+
if RUBY_VERSION =~ /^1\.9/
|
15
|
+
|
16
|
+
def base64_encoded
|
17
|
+
[self].pack 'm'
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.decode_base64 string
|
21
|
+
new string.unpack('m')[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
else
|
25
|
+
|
26
|
+
def base64_encoded
|
27
|
+
Base64.encode64 self
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.decode_base64 string
|
31
|
+
new Base64.decode64 string
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module Rapuncel
|
4
|
+
module Client::Logging
|
5
|
+
attr_accessor :logger
|
6
|
+
|
7
|
+
def log_level
|
8
|
+
@logger.try :level
|
9
|
+
end
|
10
|
+
|
11
|
+
def log_level= log_level
|
12
|
+
@logger && @logger.level = log_level
|
13
|
+
end
|
14
|
+
|
15
|
+
def logger= logger
|
16
|
+
@logger = case logger
|
17
|
+
when Logger
|
18
|
+
logger
|
19
|
+
when IO
|
20
|
+
Logger.new logger
|
21
|
+
when String, Symbol
|
22
|
+
Logger.new ActiveSupport::Inflector.constantize(logger.to_s.upcase)
|
23
|
+
else
|
24
|
+
Logger.new STDOUT
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
def initialize_logging logger, log_level
|
30
|
+
self.logger = logger
|
31
|
+
if log_level
|
32
|
+
self.log_level = log_level
|
33
|
+
end
|
34
|
+
|
35
|
+
@logger.info { with_log_prefix "Initialized." }
|
36
|
+
@logger.debug { with_log_prefix "Using (De)Serializer: #{serialization.to_s}" }
|
37
|
+
end
|
38
|
+
|
39
|
+
def _call name, *args
|
40
|
+
if logger.debug?
|
41
|
+
logger.debug { with_log_prefix "Calling RPC Method \"#{name}\" with #{args.map(&:inspect).join(', ')}" }
|
42
|
+
else
|
43
|
+
logger.info { with_log_prefix "Calling RPC Method \"#{name}\" with #{args.length} arguments." }
|
44
|
+
end
|
45
|
+
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
def send_method_call xml
|
50
|
+
logger.debug { with_log_prefix "Sending XML:\n #{xml}" }
|
51
|
+
|
52
|
+
super
|
53
|
+
end
|
54
|
+
|
55
|
+
def execute request
|
56
|
+
super.tap do |response|
|
57
|
+
case
|
58
|
+
when response.success?
|
59
|
+
logger.debug { "Received XML-Response: \n #{response.body}" }
|
60
|
+
when response.fault?
|
61
|
+
level = raise_on_fault ? Logger::ERROR : Logger::WARN
|
62
|
+
logger.add(level) { "Received XML-Fault: \n #{response.body}" }
|
63
|
+
when response.error?
|
64
|
+
level = raise_on_error ? Logger::ERROR : Logger::WARN
|
65
|
+
logger.add(level) { "HTTP Error: #{response.status}\n #{response.body}" }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def log_host_config
|
72
|
+
"#{connection.host}:#{connection.port}#{connection.path}"
|
73
|
+
end
|
74
|
+
|
75
|
+
def with_log_prefix message
|
76
|
+
"#{log_prefix} #{message}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def log_prefix
|
80
|
+
@log_prefix ||= "[XML-RPC@#{log_host_config}]"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
begin
|
2
|
+
require 'rapuncel/adapters/curb_adapter'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rapuncel/adapters/net_http_adapter'
|
5
|
+
end
|
6
|
+
require 'rapuncel/connection'
|
7
|
+
require 'rapuncel/xml_rpc/serializer'
|
8
|
+
require 'rapuncel/xml_rpc/deserializer'
|
9
|
+
require 'active_support/core_ext/hash/except'
|
10
|
+
require 'active_support/inflector/methods'
|
11
|
+
require 'active_support/deprecation'
|
12
|
+
|
13
|
+
module Rapuncel
|
14
|
+
class Client
|
15
|
+
autoload :Logging, 'rapuncel/client/logging'
|
16
|
+
|
17
|
+
attr_accessor :connection, :raise_on_fault, :raise_on_error
|
18
|
+
|
19
|
+
if defined?(Adapters::CurbAdapter)
|
20
|
+
include Adapters::CurbAdapter
|
21
|
+
else
|
22
|
+
include Adapters::NetHttpAdapter
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize configuration = {}
|
26
|
+
@connection = Connection.new configuration.except(:raise_on, :serialization)
|
27
|
+
@serialization = configuration[:serialization]
|
28
|
+
|
29
|
+
@raise_on_fault, @raise_on_error = case configuration[:raise_on]
|
30
|
+
when :fault
|
31
|
+
[true, false]
|
32
|
+
when :error
|
33
|
+
[false, true]
|
34
|
+
when :both
|
35
|
+
[true, true]
|
36
|
+
else
|
37
|
+
[false, false]
|
38
|
+
end
|
39
|
+
|
40
|
+
if logger = configuration[:logger]
|
41
|
+
extend Logging
|
42
|
+
initialize_logging logger, configuration[:log_level]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def proxy_for interface = nil
|
47
|
+
if interface.nil?
|
48
|
+
@default_proxy ||= Proxy.new self, nil
|
49
|
+
else
|
50
|
+
@proxies ||= Hash.new do |hash, key|
|
51
|
+
hash[key] = Proxy.new self, key
|
52
|
+
end
|
53
|
+
|
54
|
+
@proxies[interface.to_s]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def proxy
|
59
|
+
proxy_for nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# Dispatch a method call and return the response as Rapuncel::Response object.
|
63
|
+
def call name, *args
|
64
|
+
ActiveSupport::Deprecation.warn "Using #call is deprecated, please use #call_to_ruby instead.", caller
|
65
|
+
_call name, *args
|
66
|
+
end
|
67
|
+
|
68
|
+
# Dispatch a method call and return the response as parsed ruby.
|
69
|
+
def call_to_ruby name, *args
|
70
|
+
response = _call name, *args
|
71
|
+
|
72
|
+
raise_on_fault && response.fault? && raise_fault(response)
|
73
|
+
raise_on_error && response.error? && raise_error(response)
|
74
|
+
|
75
|
+
response.to_ruby
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
def _call name, *args
|
80
|
+
execute Request.new(name, *args)
|
81
|
+
end
|
82
|
+
|
83
|
+
def execute request
|
84
|
+
xml = serializer[request]
|
85
|
+
|
86
|
+
Response.new send_method_call(xml), deserializer
|
87
|
+
end
|
88
|
+
|
89
|
+
def serialization
|
90
|
+
case @serialization
|
91
|
+
when Module
|
92
|
+
@serialization
|
93
|
+
when String, Symbol
|
94
|
+
ActiveSupport::Inflector.constantize(@serialization.to_s)
|
95
|
+
else
|
96
|
+
XmlRpc
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def serializer
|
101
|
+
@serializer ||= serialization.const_get 'Serializer'
|
102
|
+
end
|
103
|
+
|
104
|
+
def deserializer
|
105
|
+
@deserializer ||= serialization.const_get 'Deserializer'
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
def raise_fault response
|
110
|
+
raise(Response::Fault, response.fault[:faultCode], response.fault[:faultString].split("\n"))
|
111
|
+
end
|
112
|
+
|
113
|
+
def raise_error response
|
114
|
+
raise(Response::Error, "HTTP Error: #{response.error.inspect}")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Rapuncel
|
2
|
+
class Connection
|
3
|
+
attr_accessor :host, :port, :path, :ssl, :user, :password, :cookie_file_path
|
4
|
+
alias_method :ssl?, :ssl
|
5
|
+
|
6
|
+
def initialize configuration = {}
|
7
|
+
load_configuration configuration
|
8
|
+
end
|
9
|
+
|
10
|
+
def url
|
11
|
+
"#{protocol}://#{host}:#{port}#{path}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def host= value
|
15
|
+
@host = value.to_s.sub /^http(s)?\:\/\//, ''
|
16
|
+
|
17
|
+
if $1 == 's'
|
18
|
+
@ssl = true
|
19
|
+
end
|
20
|
+
|
21
|
+
@host
|
22
|
+
end
|
23
|
+
|
24
|
+
def path= value
|
25
|
+
unless value =~ /^\//
|
26
|
+
value = "/" + value
|
27
|
+
end
|
28
|
+
|
29
|
+
@path = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def headers= headers
|
33
|
+
@headers = {
|
34
|
+
'User-Agent' => 'Rapuncel, Ruby XMLRPC Client'
|
35
|
+
}.merge headers.stringify_keys
|
36
|
+
end
|
37
|
+
|
38
|
+
def headers
|
39
|
+
@headers.merge 'Accept' => 'text/xml', 'content-type' => 'text/xml'
|
40
|
+
end
|
41
|
+
|
42
|
+
def protocol
|
43
|
+
ssl? ? 'https' : 'http'
|
44
|
+
end
|
45
|
+
alias_method :scheme, :protocol
|
46
|
+
|
47
|
+
def auth?
|
48
|
+
!!user && !!password
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
def load_configuration configuration
|
53
|
+
configuration = configuration.symbolize_keys
|
54
|
+
|
55
|
+
self.ssl = !!configuration[:ssl]
|
56
|
+
self.host = configuration[:host] || 'localhost'
|
57
|
+
self.port = configuration[:port] || '8080'
|
58
|
+
self.path = configuration[:path] || '/'
|
59
|
+
self.headers = configuration[:headers] || {}
|
60
|
+
self.user = configuration[:user]
|
61
|
+
self.password = configuration[:password]
|
62
|
+
self.cookie_file_path = configuration[:cookie_file_path]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|