rapuncel 0.0.4 → 0.0.5.RC1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +3 -0
- data/README.md +35 -24
- data/Rakefile +11 -11
- data/lib/rapuncel.rb +9 -17
- data/lib/rapuncel/adapters/net_http_adapter.rb +7 -5
- data/lib/rapuncel/base_64_string.rb +31 -0
- data/lib/rapuncel/client.rb +2 -2
- data/lib/rapuncel/connection.rb +35 -58
- data/lib/rapuncel/request.rb +1 -38
- data/lib/rapuncel/response.rb +17 -30
- data/lib/rapuncel/xml_rpc_deserializer.rb +110 -0
- data/lib/rapuncel/xml_rpc_serializer.rb +148 -0
- data/rapuncel.gemspec +3 -2
- data/spec/functional/client_spec.rb +53 -0
- data/spec/spec_helper.rb +48 -0
- data/{test → spec}/test_server.rb +0 -1
- 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/connection_spec.rb +22 -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 +24 -0
- data/spec/unit/proxy_spec.rb +29 -0
- data/spec/unit/request_spec.rb +28 -0
- data/{test/unit/response_test.rb → spec/unit/response_spec.rb} +29 -44
- data/spec/unit/string_spec.rb +35 -0
- data/spec/unit/time_spec.rb +20 -0
- metadata +77 -104
- data/lib/rapuncel/base.rb +0 -7
- data/lib/rapuncel/core_ext/array.rb +0 -23
- data/lib/rapuncel/core_ext/big_decimal.rb +0 -7
- data/lib/rapuncel/core_ext/boolean.rb +0 -29
- data/lib/rapuncel/core_ext/float.rb +0 -11
- data/lib/rapuncel/core_ext/hash.rb +0 -32
- data/lib/rapuncel/core_ext/integer.rb +0 -11
- data/lib/rapuncel/core_ext/nil.rb +0 -7
- data/lib/rapuncel/core_ext/object.rb +0 -49
- data/lib/rapuncel/core_ext/string.rb +0 -12
- data/lib/rapuncel/core_ext/symbol.rb +0 -7
- data/lib/rapuncel/core_ext/time.rb +0 -14
- data/rapuncel-0.0.3.gem +0 -0
- data/test/functional/client_test.rb +0 -54
- data/test/functional_test_helper.rb +0 -13
- data/test/test_helper.rb +0 -38
- data/test/unit/array_test.rb +0 -97
- data/test/unit/boolean_test.rb +0 -34
- data/test/unit/connection_test.rb +0 -29
- data/test/unit/float_test.rb +0 -23
- data/test/unit/hash_test.rb +0 -54
- data/test/unit/int_test.rb +0 -27
- data/test/unit/nil_test.rb +0 -16
- data/test/unit/object_test.rb +0 -83
- data/test/unit/proxy_test.rb +0 -52
- data/test/unit/request_test.rb +0 -34
- data/test/unit/string_test.rb +0 -40
- data/test/unit/time_test.rb +0 -23
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -3,8 +3,12 @@
|
|
3
3
|
Rapuncel ([wikipedia](http://en.wikipedia.org/wiki/Rapunzel)) is a simple and lightweight, but fast XML-RPC client library for ruby.
|
4
4
|
It's based on Nokogiri for XML parsing and thus provides a major performance improvement for large XML responses.
|
5
5
|
|
6
|
-
##
|
7
|
-
|
6
|
+
## I need your help
|
7
|
+
I currently have exactly 1 application for Rapuncel, and that's [Kangaroo](https://github.com/cice/kangARoo), i.e.
|
8
|
+
the OpenERP XMLRPC service, where it works absolutely fine. To improve Rapuncel i need your experience with
|
9
|
+
other services and their quirks.
|
10
|
+
|
11
|
+
This Readme is for the upcoming 0.1 release, 0.0.x Readme [here](https://github.com/cice/rapuncel/blob/be19d4427dba14dbc656de1d90501f9d42aa4ef8/README.md)
|
8
12
|
|
9
13
|
## Installation
|
10
14
|
|
@@ -53,16 +57,14 @@ _default_: /
|
|
53
57
|
Username for HTTP Authentication
|
54
58
|
_default_: _empty_
|
55
59
|
* **password**
|
56
|
-
|
60
|
+
Password for HTTP Authentication
|
57
61
|
_default_: _empty_
|
58
|
-
* **
|
59
|
-
HTTP
|
60
|
-
_default_:
|
61
|
-
* **
|
62
|
-
|
63
|
-
|
64
|
-
Allows you to modify the header key for API-Key auth
|
65
|
-
_default_: X-ApiKey
|
62
|
+
* **headers**
|
63
|
+
Hash to set additional HTTP headers for the request, e.g. to send an X-ApiKey header for authentication
|
64
|
+
_default_: {}
|
65
|
+
* **ssl**
|
66
|
+
Flag wether to use SSL
|
67
|
+
_default_: false
|
66
68
|
* **raise_on**
|
67
69
|
Lets you define the behavior on errors or faults, if set to _:fault_, _:error_ or _:both_,
|
68
70
|
an Exception will be raised if something goes wrong
|
@@ -92,23 +94,20 @@ Rapuncel supports natively following object-types (and all their subclasses):
|
|
92
94
|
* Float
|
93
95
|
* BigDecimal (treated like Float)
|
94
96
|
* Time, Time-like objects
|
97
|
+
* Base64
|
95
98
|
|
96
99
|
* Symbols are converted to Strings
|
97
|
-
|
100
|
+
* All Hashs have symbol keys
|
98
101
|
* All other objects are transformed into a Hash ('struct' in XMLRPC-speak) containing their instance variables as key-value-pairs.
|
99
102
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
def to_xml_rpc(builder = Rapuncel.get_builder)
|
104
|
-
self.to_s.to_xml_rpc(builder)
|
105
|
-
end
|
106
|
-
|
107
|
-
Of course you don't have to delegate to #to\_s, you just can use the Builder object directly
|
103
|
+
## Base64
|
104
|
+
If you want a string to be encoded as Base64 in your RPC call, just mark it:
|
108
105
|
|
109
|
-
|
110
|
-
|
111
|
-
|
106
|
+
proxy.some_method "my base64 encoded string".as_base64
|
107
|
+
|
108
|
+
Return values that arrive Base64 encoded, are instances of Rapuncel::Base64String,
|
109
|
+
which is a subclass of String, and therefore can be used as such, but allows you to differentiate
|
110
|
+
Base64 return values from normal String return values.
|
112
111
|
|
113
112
|
## Supported methods
|
114
113
|
You can use most methods via
|
@@ -130,13 +129,25 @@ note
|
|
130
129
|
|
131
130
|
will return a Rapuncel::Response object, use _call\_to\_ruby_ to get standard ruby objects
|
132
131
|
|
132
|
+
## Deserialization options
|
133
|
+
|
134
|
+
At the moment there are 2 options, to be set quite ugly as class attributes on Rapuncel::XmlRpcDeserializer,
|
135
|
+
which will definitely change.
|
136
|
+
|
137
|
+
1. **double\_as\_bigdecimal**
|
138
|
+
Deserialize all <double> tags as BigDecimal.
|
139
|
+
2. **hash\_keys\_as\_string**
|
140
|
+
Don't use Symbols as keys for deserialized <struct>, but Strings.
|
141
|
+
|
142
|
+
|
133
143
|
## Todo ?
|
134
144
|
|
135
145
|
* RDoc
|
136
146
|
* Extensive functional tests
|
147
|
+
* HTTP-Proxy support
|
137
148
|
* Async requests
|
138
|
-
* Base64 support (or rather a consistent concept for Base64)
|
139
149
|
* XMLRPC Extensions (pluggable support)
|
150
|
+
* How do i test SSL?
|
140
151
|
|
141
152
|
## What happens if something goes wrong?
|
142
153
|
### HTTP Errors / XMLRPC Faults
|
data/Rakefile
CHANGED
@@ -2,18 +2,7 @@ require 'rake'
|
|
2
2
|
require 'rake/testtask'
|
3
3
|
require 'rake/rdoctask'
|
4
4
|
|
5
|
-
desc 'Default: run unit tests.'
|
6
|
-
task :default => :test
|
7
|
-
|
8
|
-
desc 'Run Rapuncel Testsuite.'
|
9
|
-
Rake::TestTask.new(:test) do |t|
|
10
|
-
t.libs << 'lib'
|
11
|
-
t.libs << 'test'
|
12
|
-
t.pattern = 'test/**/*_test.rb'
|
13
|
-
t.verbose = true
|
14
|
-
end
|
15
5
|
|
16
|
-
#TODO: sdoc
|
17
6
|
desc 'Generate Rapuncel rdoc.'
|
18
7
|
Rake::RDocTask.new(:rdoc) do |rdoc|
|
19
8
|
rdoc.rdoc_dir = 'rdoc'
|
@@ -22,3 +11,14 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
22
11
|
rdoc.rdoc_files.include('README.md')
|
23
12
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
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
|
data/lib/rapuncel.rb
CHANGED
@@ -1,25 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
1
|
require 'active_support/core_ext/hash/keys'
|
4
|
-
|
5
2
|
require 'nokogiri'
|
6
3
|
|
7
|
-
|
4
|
+
module Rapuncel
|
5
|
+
BUILDER_OPTIONS = {:encoding => 'UTF-8'}
|
6
|
+
|
7
|
+
def self.get_builder options = {}
|
8
|
+
Nokogiri::XML::Builder.new options
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
8
12
|
require 'rapuncel/request'
|
9
13
|
require 'rapuncel/response'
|
10
14
|
require 'rapuncel/client'
|
11
15
|
require 'rapuncel/proxy'
|
12
16
|
|
13
|
-
|
14
|
-
require 'rapuncel/core_ext/object'
|
15
|
-
require 'rapuncel/core_ext/string'
|
16
|
-
require 'rapuncel/core_ext/symbol'
|
17
|
-
require 'rapuncel/core_ext/integer'
|
18
|
-
require 'rapuncel/core_ext/big_decimal'
|
19
|
-
require 'rapuncel/core_ext/float'
|
20
|
-
require 'rapuncel/core_ext/hash'
|
21
|
-
require 'rapuncel/core_ext/array'
|
22
|
-
require 'rapuncel/core_ext/boolean'
|
23
|
-
require 'rapuncel/core_ext/nil'
|
24
|
-
|
25
|
-
require 'rapuncel/core_ext/time'
|
17
|
+
require 'rapuncel/base_64_string'
|
@@ -1,11 +1,10 @@
|
|
1
1
|
require 'net/http'
|
2
|
-
require '
|
2
|
+
require 'net/https'
|
3
3
|
|
4
4
|
module Rapuncel
|
5
5
|
module Adapters
|
6
6
|
module NetHttpAdapter
|
7
|
-
|
8
|
-
|
7
|
+
# Small response wrapper
|
9
8
|
class HttpResponse
|
10
9
|
def initialize response
|
11
10
|
@response = response
|
@@ -24,10 +23,13 @@ module Rapuncel
|
|
24
23
|
end
|
25
24
|
end
|
26
25
|
|
26
|
+
# Dispatch a XMLRPC via HTTP and return a response object.
|
27
27
|
def send_method_call str
|
28
|
-
|
28
|
+
request = Net::HTTP.new connection.host, connection.port
|
29
|
+
request.use_ssl = connection.ssl?
|
30
|
+
request.basic_auth connection.user, connection.password if connection.auth?
|
29
31
|
|
30
|
-
HttpResponse.new
|
32
|
+
HttpResponse.new request.post(connection.path, str, connection.headers)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
@@ -0,0 +1,31 @@
|
|
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
|
+
def base64_encoded
|
14
|
+
if RUBY_VERSION =~ /^1\.9/
|
15
|
+
[self].pack 'm'
|
16
|
+
else
|
17
|
+
Base64.encode64 self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def decode_base64 string
|
23
|
+
if RUBY_VERSION =~ /^1\.9/
|
24
|
+
new string.unpack('m')[0]
|
25
|
+
else
|
26
|
+
new Base64.decode64 string
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/rapuncel/client.rb
CHANGED
@@ -17,7 +17,7 @@ module Rapuncel
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def initialize configuration = {}
|
20
|
-
@connection = Connection.
|
20
|
+
@connection = Connection.new configuration.except(:raise_on)
|
21
21
|
|
22
22
|
@raise_on_fault, @raise_on_error = case configuration[:raise_on]
|
23
23
|
when :fault
|
@@ -46,7 +46,7 @@ module Rapuncel
|
|
46
46
|
|
47
47
|
protected
|
48
48
|
def execute request
|
49
|
-
xml = request.
|
49
|
+
xml = XmlRpcSerializer.new(request).to_xml
|
50
50
|
|
51
51
|
Response.new send_method_call(xml)
|
52
52
|
end
|
data/lib/rapuncel/connection.rb
CHANGED
@@ -1,40 +1,24 @@
|
|
1
1
|
module Rapuncel
|
2
2
|
class Connection
|
3
|
-
|
4
|
-
configuration = configuration.symbolize_keys
|
5
|
-
|
6
|
-
case
|
7
|
-
when configuration[:user], configuration[:auth_method], configuration[:password]
|
8
|
-
AuthConnection.new configuration
|
9
|
-
when configuration[:api_key_header], configuration[:api_key]
|
10
|
-
ApiKeyAuthConnection.new configuration
|
11
|
-
else
|
12
|
-
new configuration
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
attr_accessor :host, :port, :path, :ssl, :headers
|
3
|
+
attr_accessor :host, :port, :path, :ssl, :user, :password
|
17
4
|
alias_method :ssl?, :ssl
|
18
5
|
|
19
6
|
def initialize configuration = {}
|
20
|
-
|
21
|
-
self.host = configuration[:host] || 'localhost'
|
22
|
-
self.port = configuration[:port] || '8080'
|
23
|
-
self.path = configuration[:path] || '/'
|
24
|
-
self.headers = configuration[:headers] || {}
|
25
|
-
|
26
|
-
if ssl = configuration[:ssl]
|
27
|
-
@ssl = true
|
28
|
-
#TODO
|
29
|
-
end
|
7
|
+
load_configuration configuration
|
30
8
|
end
|
31
9
|
|
32
10
|
def url
|
33
|
-
"
|
11
|
+
"#{protocol}://#{host}:#{port}#{path}"
|
34
12
|
end
|
35
13
|
|
36
14
|
def host= value
|
37
|
-
@host = value.to_s.sub /^http
|
15
|
+
@host = value.to_s.sub /^http(s)?\:\/\//, ''
|
16
|
+
|
17
|
+
if $1 == 's'
|
18
|
+
@ssl = true
|
19
|
+
end
|
20
|
+
|
21
|
+
@host
|
38
22
|
end
|
39
23
|
|
40
24
|
def path= value
|
@@ -44,43 +28,36 @@ module Rapuncel
|
|
44
28
|
|
45
29
|
@path = value
|
46
30
|
end
|
31
|
+
|
32
|
+
def headers= headers
|
33
|
+
@headers = {
|
34
|
+
'User-Agent' => 'Rapuncel, Ruby XMLRPC Client'
|
35
|
+
}.merge headers.stringify_keys
|
36
|
+
end
|
47
37
|
|
48
38
|
def headers
|
49
|
-
@headers.merge
|
39
|
+
@headers.merge 'Accept' => 'text/xml', 'content-type' => 'text/xml'
|
50
40
|
end
|
51
|
-
|
52
|
-
def
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
class AuthConnection < Connection
|
57
|
-
attr_accessor :auth_method, :user, :password
|
58
|
-
|
59
|
-
def initialize configuration = {}
|
60
|
-
super
|
61
|
-
|
62
|
-
@auth_method = auth_method || 'basic'
|
63
|
-
@user = user || ''
|
64
|
-
@password = configuration[:password] || ''
|
41
|
+
|
42
|
+
def protocol
|
43
|
+
ssl? ? 'https' : 'http'
|
65
44
|
end
|
66
|
-
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
class ApiKeyAuthConnection < Connection
|
71
|
-
attr_accessor :api_key_header, :api_key
|
72
|
-
|
73
|
-
def initialize configuration = {}
|
74
|
-
super
|
75
|
-
|
76
|
-
@api_key_header = configuration[:api_key_header] || "X-ApiKey"
|
77
|
-
@api_key = configuration[:api_key] || '' #DISCUSS: raise error ?
|
45
|
+
|
46
|
+
def auth?
|
47
|
+
!!user && !!password
|
78
48
|
end
|
79
|
-
|
80
|
-
|
81
|
-
|
49
|
+
|
50
|
+
protected
|
51
|
+
def load_configuration configuration
|
52
|
+
configuration = configuration.symbolize_keys
|
53
|
+
|
54
|
+
self.ssl = !!configuration[:ssl]
|
55
|
+
self.host = configuration[:host] || 'localhost'
|
56
|
+
self.port = configuration[:port] || '8080'
|
57
|
+
self.path = configuration[:path] || '/'
|
58
|
+
self.headers = configuration[:headers] || {}
|
59
|
+
self.user = configuration[:user]
|
60
|
+
self.password = configuration[:password]
|
82
61
|
end
|
83
|
-
|
84
|
-
def api_auth? ; true ; end
|
85
62
|
end
|
86
63
|
end
|
data/lib/rapuncel/request.rb
CHANGED
@@ -1,49 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'rapuncel/xml_rpc_serializer'
|
3
2
|
|
4
3
|
module Rapuncel
|
5
4
|
class Request
|
6
5
|
attr_accessor :method_name, :arguments
|
7
6
|
|
8
|
-
|
9
7
|
# Create a new XML-RPC request
|
10
8
|
def initialize method_name, *args
|
11
9
|
@method_name, @arguments = method_name, args
|
12
10
|
end
|
13
|
-
|
14
|
-
def to_xml_rpc builder = Rapuncel.get_builder
|
15
|
-
method_call! builder
|
16
|
-
|
17
|
-
builder.to_xml :encoding => 'UTF-8'
|
18
|
-
end
|
19
|
-
|
20
|
-
protected
|
21
|
-
def method_call! builder
|
22
|
-
|
23
|
-
builder.methodCall do |builder|
|
24
|
-
method_name! builder
|
25
|
-
params! builder
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def method_name! builder
|
30
|
-
builder.methodName method_name
|
31
|
-
end
|
32
|
-
|
33
|
-
def params! builder
|
34
|
-
builder.params do |builder|
|
35
|
-
arguments.each do |value|
|
36
|
-
param! builder, value
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def param! builder, value
|
42
|
-
builder.param do |builder|
|
43
|
-
builder.value do |builder|
|
44
|
-
value.to_xml_rpc builder
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
11
|
end
|
49
12
|
end
|
data/lib/rapuncel/response.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_support/core_ext/module/delegation'
|
2
|
+
require 'rapuncel/xml_rpc_deserializer'
|
2
3
|
|
3
4
|
module Rapuncel
|
4
5
|
class Response
|
@@ -6,7 +7,7 @@ module Rapuncel
|
|
6
7
|
class Fault < Exception ; end
|
7
8
|
class Error < Exception ; end
|
8
9
|
|
9
|
-
attr_accessor :http_response, :status, :status_code
|
10
|
+
attr_accessor :http_response, :status, :status_code, :response
|
10
11
|
|
11
12
|
delegate :body,
|
12
13
|
:to => :http_response
|
@@ -20,10 +21,16 @@ module Rapuncel
|
|
20
21
|
def evaluate_status
|
21
22
|
@status_code = http_response.code.to_i
|
22
23
|
|
23
|
-
@status =
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
@status = unless http_response.success?
|
25
|
+
'error'
|
26
|
+
else
|
27
|
+
deserialize_response
|
28
|
+
|
29
|
+
if Hash === response && response[:faultCode]
|
30
|
+
'fault'
|
31
|
+
else
|
32
|
+
'success'
|
33
|
+
end
|
27
34
|
end
|
28
35
|
end
|
29
36
|
|
@@ -40,31 +47,15 @@ module Rapuncel
|
|
40
47
|
end
|
41
48
|
|
42
49
|
def fault
|
43
|
-
|
44
|
-
@fault ||= begin
|
45
|
-
fault_node = parsed_body.xpath('/methodResponse/fault/value/struct').first
|
46
|
-
|
47
|
-
Hash.from_xml_rpc(fault_node).tap do |h|
|
48
|
-
h[:faultString] = h[:faultString].strip
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
50
|
+
fault? && @response
|
52
51
|
end
|
53
52
|
|
54
53
|
def error
|
55
|
-
|
56
|
-
@error ||= { :http_code => @status_code, :http_body => body }
|
57
|
-
end
|
54
|
+
error? && { :http_code => @status_code, :http_body => body }
|
58
55
|
end
|
59
56
|
|
60
57
|
def result
|
61
|
-
|
62
|
-
@result ||= begin
|
63
|
-
return_values = parsed_body.xpath('/methodResponse/params/param/value/*')
|
64
|
-
|
65
|
-
Object.from_xml_rpc return_values.first
|
66
|
-
end
|
67
|
-
end
|
58
|
+
success? && @response
|
68
59
|
end
|
69
60
|
|
70
61
|
def to_ruby
|
@@ -72,12 +63,8 @@ module Rapuncel
|
|
72
63
|
end
|
73
64
|
|
74
65
|
protected
|
75
|
-
def
|
76
|
-
@
|
77
|
-
end
|
78
|
-
|
79
|
-
def method_response_success?
|
80
|
-
parsed_body.xpath('/methodResponse/fault').empty?
|
66
|
+
def deserialize_response
|
67
|
+
@response = XmlRpcDeserializer.new(body).to_ruby
|
81
68
|
end
|
82
69
|
end
|
83
70
|
end
|