rapuncel 0.0.1.alpha
Sign up to get free protection for your applications and to get access to all the features.
- 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
|