rapuncel 0.0.1.alpha
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.
- data/MIT-LICENSE +20 -0
- data/README.md +171 -0
- data/Rakefile +24 -0
- data/lib/rapuncel.rb +25 -0
- data/lib/rapuncel/adapters/net_http_adapter.rb +34 -0
- data/lib/rapuncel/base.rb +7 -0
- data/lib/rapuncel/client.rb +54 -0
- data/lib/rapuncel/connection.rb +75 -0
- data/lib/rapuncel/core_ext/array.rb +23 -0
- data/lib/rapuncel/core_ext/big_decimal.rb +7 -0
- data/lib/rapuncel/core_ext/boolean.rb +29 -0
- data/lib/rapuncel/core_ext/float.rb +11 -0
- data/lib/rapuncel/core_ext/hash.rb +32 -0
- data/lib/rapuncel/core_ext/integer.rb +11 -0
- data/lib/rapuncel/core_ext/nil.rb +7 -0
- data/lib/rapuncel/core_ext/object.rb +49 -0
- data/lib/rapuncel/core_ext/string.rb +12 -0
- data/lib/rapuncel/core_ext/symbol.rb +7 -0
- data/lib/rapuncel/core_ext/time.rb +14 -0
- data/lib/rapuncel/proxy.rb +82 -0
- data/lib/rapuncel/request.rb +49 -0
- data/lib/rapuncel/response.rb +92 -0
- data/rapuncel-0.0.1.preview.gem +0 -0
- data/rapuncel.gemspec +22 -0
- data/test/functional/client_test.rb +54 -0
- data/test/functional_test_helper.rb +13 -0
- data/test/test_helper.rb +38 -0
- data/test/test_server.rb +28 -0
- data/test/unit/array_test.rb +96 -0
- data/test/unit/boolean_test.rb +34 -0
- data/test/unit/connection_test.rb +17 -0
- data/test/unit/float_test.rb +23 -0
- data/test/unit/hash_test.rb +54 -0
- data/test/unit/int_test.rb +27 -0
- data/test/unit/nil_test.rb +16 -0
- data/test/unit/object_test.rb +83 -0
- data/test/unit/proxy_test.rb +52 -0
- data/test/unit/request_test.rb +34 -0
- data/test/unit/response_test.rb +100 -0
- data/test/unit/string_test.rb +40 -0
- data/test/unit/time_test.rb +23 -0
- metadata +154 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 ['Marian Theisen', 'Michael Eickenberg']
|
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,171 @@
|
|
1
|
+
# Rapuncel - Simple XML-RPC Client
|
2
|
+
|
3
|
+
Rapuncel ([wikipedia](http://en.wikipedia.org/wiki/Rapunzel)) is a simple and lightweight, but fast XML-RPC client library for ruby.
|
4
|
+
It's based on Nokogiri for XML parsing and thus provides a major performance improvement for large XML responses.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
### Rails
|
9
|
+
Add this to your Gemfile:
|
10
|
+
|
11
|
+
gem 'rapuncel'
|
12
|
+
|
13
|
+
Run
|
14
|
+
|
15
|
+
bundle install
|
16
|
+
|
17
|
+
and you're good to go.
|
18
|
+
|
19
|
+
### Other Ruby / IRB
|
20
|
+
Install it as gem:
|
21
|
+
|
22
|
+
gem install rapuncel
|
23
|
+
|
24
|
+
Require **rubygems** and **rapuncel**
|
25
|
+
|
26
|
+
require 'rubygems'
|
27
|
+
require 'rapuncel'
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
### Initialize client
|
32
|
+
Usage is pretty straightforward, Rapuncel provides a method proxy to send calls to your XMLRPC service like you would to a normal ruby
|
33
|
+
object.
|
34
|
+
First you have to create a client with the connection details, e.g.
|
35
|
+
|
36
|
+
client = Rapuncel::Client.new :host => 'localhost', :port => 8080, :path => '/xmlrpc'
|
37
|
+
|
38
|
+
Available options are:
|
39
|
+
|
40
|
+
* **host**
|
41
|
+
hostname or ip-address,
|
42
|
+
_default_: localhost
|
43
|
+
* **port**
|
44
|
+
port where your XMLRPC service is listening,
|
45
|
+
_default_: 8080
|
46
|
+
* **path**
|
47
|
+
path to the service,
|
48
|
+
_default_: /
|
49
|
+
* **user**
|
50
|
+
Username for HTTP Authentication
|
51
|
+
_default_: _empty_
|
52
|
+
* **password**
|
53
|
+
Username for HTTP Authentication
|
54
|
+
_default_: _empty_
|
55
|
+
* **auth\_method**
|
56
|
+
HTTP Auth method
|
57
|
+
_default_: basic **IF** user or password is set
|
58
|
+
* **api\_key**
|
59
|
+
If set, sends all request with a X-ApiKey: _api\_key_ header
|
60
|
+
* **api\_key\_header**
|
61
|
+
Allows you to modify the header key for API-Key auth
|
62
|
+
_default_: X-ApiKey
|
63
|
+
* **raise_on**
|
64
|
+
Lets you define the behavior on errors or faults, if set to _:fault_, _:error_ or _:both_,
|
65
|
+
an Exception will be raised if something goes wrong
|
66
|
+
|
67
|
+
### Get a proxy object and ...
|
68
|
+
A proxy object receives ruby method calls, redirects them to your XMLRPC service and returns the response as ruby objects!
|
69
|
+
|
70
|
+
proxy = client.proxy
|
71
|
+
|
72
|
+
# suppose your XMLRPC service has a method exposed 'concat_string(string1, string2)'
|
73
|
+
proxy.concat_string "foo", "bar"
|
74
|
+
-> "foobar"
|
75
|
+
|
76
|
+
# if you need to access specific interfaces on your service, e.g. 'string.concat(string1, string2)'
|
77
|
+
proxy = client.proxy_for 'string'
|
78
|
+
proxy.concat 'foo', 'bar'
|
79
|
+
-> 'foobar'
|
80
|
+
|
81
|
+
## Supported objects
|
82
|
+
Rapuncel supports natively following object-types (and all their subclasses):
|
83
|
+
|
84
|
+
* Integer
|
85
|
+
* String
|
86
|
+
* Array
|
87
|
+
* Hash
|
88
|
+
* TrueClass, FalseClass
|
89
|
+
* Float
|
90
|
+
* BigDecimal (treated like Float)
|
91
|
+
* Time, Time-like objects
|
92
|
+
|
93
|
+
* Symbols are converted to Strings
|
94
|
+
|
95
|
+
* All other objects are transformed into a Hash ('struct' in XMLRPC-speak) containing their instance variables as key-value-pairs.
|
96
|
+
|
97
|
+
### Can i customize this behavior for my objects?
|
98
|
+
Yes you can, and it's dead simple, just override _to\_xml\_rpc_ with following signature (this is taken from Symbol#to\_xml\_rpc):
|
99
|
+
|
100
|
+
def to_xml_rpc(builder = Rapuncel.get_builder)
|
101
|
+
self.to_s.to_xml_rpc(builder)
|
102
|
+
end
|
103
|
+
|
104
|
+
Of course you don't have to delegate to #to\_s, you just can use the Builder object directly
|
105
|
+
|
106
|
+
def to_xml_rpc(builder = Rapuncel.get_builder)
|
107
|
+
builder.string(self.to_s)
|
108
|
+
end
|
109
|
+
|
110
|
+
## Supported methods
|
111
|
+
You can use most methods via
|
112
|
+
|
113
|
+
proxy.methodname *args
|
114
|
+
|
115
|
+
However methods starting with \_\_, or ending with a bang \! or a question mark ? are not supported. To call those methods you can always
|
116
|
+
use
|
117
|
+
|
118
|
+
proxy.call! 'methodname', *args
|
119
|
+
|
120
|
+
or via
|
121
|
+
|
122
|
+
client.call_to_ruby 'methodname', *args
|
123
|
+
|
124
|
+
note
|
125
|
+
|
126
|
+
client.call 'methodname', *args
|
127
|
+
|
128
|
+
will return a Rapuncel::Response object, use _call\_to\_ruby_ to get standard ruby objects
|
129
|
+
|
130
|
+
## Todo ?
|
131
|
+
|
132
|
+
* RDoc
|
133
|
+
* Extensive functional tests
|
134
|
+
* Async requests
|
135
|
+
* Base64 support (or rather a consistent concept for Base64)
|
136
|
+
* XMLRPC Extensions (pluggable support)
|
137
|
+
|
138
|
+
## What happens if something goes wrong?
|
139
|
+
### HTTP Errors / XMLRPC Faults
|
140
|
+
See Usage -> configuration -> raise\_on switch
|
141
|
+
### Malformed XML/XMLRPC
|
142
|
+
Rapuncel will most likely fail hard.
|
143
|
+
|
144
|
+
## Open Source
|
145
|
+
|
146
|
+
### License
|
147
|
+
|
148
|
+
Copyright (c) 2010 ['Marian Theisen', 'Michael Eickenberg']
|
149
|
+
|
150
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
151
|
+
a copy of this software and associated documentation files (the
|
152
|
+
"Software"), to deal in the Software without restriction, including
|
153
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
154
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
155
|
+
permit persons to whom the Software is furnished to do so, subject to
|
156
|
+
the following conditions:
|
157
|
+
|
158
|
+
The above copyright notice and this permission notice shall be
|
159
|
+
included in all copies or substantial portions of the Software.
|
160
|
+
|
161
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
162
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
163
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
164
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
165
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
166
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
167
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
168
|
+
|
169
|
+
### Contribution
|
170
|
+
|
171
|
+
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
|
+
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
|
+
|
16
|
+
#TODO: sdoc
|
17
|
+
desc 'Generate Rapuncel rdoc.'
|
18
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
19
|
+
rdoc.rdoc_dir = 'rdoc'
|
20
|
+
rdoc.title = 'Rapuncel'
|
21
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
22
|
+
rdoc.rdoc_files.include('README.md')
|
23
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
24
|
+
end
|
data/lib/rapuncel.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
require 'active_support/core_ext/hash/keys'
|
4
|
+
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
require 'rapuncel/base'
|
8
|
+
require 'rapuncel/request'
|
9
|
+
require 'rapuncel/response'
|
10
|
+
require 'rapuncel/client'
|
11
|
+
require 'rapuncel/proxy'
|
12
|
+
|
13
|
+
# Ruby Core extensions:
|
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'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'active_support/memoizable'
|
3
|
+
|
4
|
+
module Rapuncel
|
5
|
+
module Adapters
|
6
|
+
module NetHttpAdapter
|
7
|
+
extend ActiveSupport::Memoizable
|
8
|
+
|
9
|
+
class HttpResponse
|
10
|
+
def initialize response
|
11
|
+
@response = response
|
12
|
+
end
|
13
|
+
|
14
|
+
def success?
|
15
|
+
@response.is_a? Net::HTTPOK
|
16
|
+
end
|
17
|
+
|
18
|
+
def body
|
19
|
+
@response.body
|
20
|
+
end
|
21
|
+
|
22
|
+
def code
|
23
|
+
@response.code
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_method_call str
|
28
|
+
req = Net::HTTP.new connection.host, connection.port
|
29
|
+
|
30
|
+
HttpResponse.new req.post(connection.path, str, connection.headers.stringify_keys)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rapuncel/adapters/net_http_adapter'
|
2
|
+
require 'rapuncel/connection'
|
3
|
+
require 'active_support/core_ext/hash/except'
|
4
|
+
|
5
|
+
module Rapuncel
|
6
|
+
class Client
|
7
|
+
attr_accessor :connection, :raise_on_fault, :raise_on_error
|
8
|
+
|
9
|
+
include Adapters::NetHttpAdapter
|
10
|
+
|
11
|
+
def proxy_for interface
|
12
|
+
Proxy.new self, interface
|
13
|
+
end
|
14
|
+
|
15
|
+
def proxy
|
16
|
+
proxy_for nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize configuration = {}
|
20
|
+
@connection = Connection.build configuration.except(:raise_on)
|
21
|
+
|
22
|
+
@raise_on_fault, @raise_on_error = case configuration[:raise_on]
|
23
|
+
when :fault
|
24
|
+
[true, false]
|
25
|
+
when :error
|
26
|
+
[false, true]
|
27
|
+
when :both
|
28
|
+
[true, true]
|
29
|
+
else
|
30
|
+
[false, false]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def call name, *args
|
35
|
+
execute Request.new(name, *args)
|
36
|
+
end
|
37
|
+
|
38
|
+
def call_to_ruby name, *args
|
39
|
+
response = call name, *args
|
40
|
+
|
41
|
+
raise_on_fault && response.fault? && raise(response.fault)
|
42
|
+
raise_on_error && response.error? && raise(response.error)
|
43
|
+
|
44
|
+
response.to_ruby
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
def execute request
|
49
|
+
xml = request.to_xml_rpc
|
50
|
+
|
51
|
+
Response.new send_method_call(xml)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Rapuncel
|
2
|
+
class Connection
|
3
|
+
def self.build configuration = {}
|
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
|
17
|
+
alias_method :ssl?, :ssl
|
18
|
+
|
19
|
+
def initialize configuration = {}
|
20
|
+
|
21
|
+
@host = configuration[:host] || 'localhost'
|
22
|
+
@port = configuration[:port] || '8080'
|
23
|
+
@path = configuration[:path] || '/'
|
24
|
+
@headers = configuration[:headers] || {}
|
25
|
+
|
26
|
+
|
27
|
+
if ssl = configuration[:ssl]
|
28
|
+
@ssl = true
|
29
|
+
#TODO
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def url
|
34
|
+
"http://#{host}:#{port}#{path}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def headers
|
38
|
+
@headers.merge :Accept => 'text/xml', :'content-type' => 'text/xml'
|
39
|
+
end
|
40
|
+
|
41
|
+
def http_auth? ; false ; end
|
42
|
+
def api_auth? ; false ; end
|
43
|
+
end
|
44
|
+
|
45
|
+
class AuthConnection < Connection
|
46
|
+
attr_accessor :auth_method, :user, :password
|
47
|
+
|
48
|
+
def initialize configuration = {}
|
49
|
+
super
|
50
|
+
|
51
|
+
@auth_method = auth_method || 'basic'
|
52
|
+
@user = user || ''
|
53
|
+
@password = configuration[:password] || ''
|
54
|
+
end
|
55
|
+
|
56
|
+
def http_auth? ; true ; end
|
57
|
+
end
|
58
|
+
|
59
|
+
class ApiKeyAuthConnection < Connection
|
60
|
+
attr_accessor :api_key_header, :api_key
|
61
|
+
|
62
|
+
def initialize configuration = {}
|
63
|
+
super
|
64
|
+
|
65
|
+
@api_key_header = configuration[:api_key_header] || "X-ApiKey"
|
66
|
+
@api_key = configuration[:api_key] || '' #DISCUSS: raise error ?
|
67
|
+
end
|
68
|
+
|
69
|
+
def headers
|
70
|
+
super.merge api_key_header => api_key
|
71
|
+
end
|
72
|
+
|
73
|
+
def api_auth? ; true ; end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Array
|
2
|
+
def to_xml_rpc b = Rapuncel.get_builder
|
3
|
+
b.array do |b|
|
4
|
+
b.data do |b|
|
5
|
+
each do |array_entry|
|
6
|
+
b.value do |b|
|
7
|
+
array_entry.to_xml_rpc b
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
b.to_xml
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.from_xml_rpc xml_node
|
17
|
+
values = xml_node.first_element_child.element_children
|
18
|
+
|
19
|
+
values.map do |value|
|
20
|
+
Object.from_xml_rpc value.first_element_child
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|