threez-rack-rpc 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +3 -0
- data/CREDITS +0 -0
- data/README +149 -0
- data/UNLICENSE +24 -0
- data/VERSION +1 -0
- data/lib/rack/rpc/endpoint/jsonrpc.rb +255 -0
- data/lib/rack/rpc/endpoint/xmlrpc.rb +110 -0
- data/lib/rack/rpc/endpoint.rb +47 -0
- data/lib/rack/rpc/error.rb +27 -0
- data/lib/rack/rpc/middleware.rb +25 -0
- data/lib/rack/rpc/operation.rb +226 -0
- data/lib/rack/rpc/server.rb +112 -0
- data/lib/rack/rpc/service.rb +67 -0
- data/lib/rack/rpc/version.rb +22 -0
- data/lib/rack/rpc.rb +13 -0
- metadata +158 -0
data/AUTHORS
ADDED
data/CREDITS
ADDED
File without changes
|
data/README
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
JSON-RPC/XML-RPC Server for Rack Applications
|
2
|
+
=============================================
|
3
|
+
|
4
|
+
This is a [Rack][] middleware that facilitates the creation of
|
5
|
+
protocol-agnostic RPC servers. The current implementation provides support
|
6
|
+
for [JSON-RPC 2.0][] and [XML-RPC][].
|
7
|
+
|
8
|
+
* <https://github.com/datagraph/rack-rpc>
|
9
|
+
|
10
|
+
Features
|
11
|
+
--------
|
12
|
+
|
13
|
+
* Handles JSON-RPC and XML-RPC requests with the same code.
|
14
|
+
* Compatible with any Rack application and any Rack-based framework.
|
15
|
+
* Provides Rails-style controller filtering for your RPC methods.
|
16
|
+
|
17
|
+
Examples
|
18
|
+
--------
|
19
|
+
|
20
|
+
### A basic RPC server
|
21
|
+
|
22
|
+
require 'rack/rpc'
|
23
|
+
|
24
|
+
class Server < Rack::RPC::Server
|
25
|
+
def hello_world
|
26
|
+
"Hello, world!"
|
27
|
+
end
|
28
|
+
rpc 'hello_world' => :hello_world
|
29
|
+
end
|
30
|
+
|
31
|
+
### Simple filtering
|
32
|
+
|
33
|
+
require 'rack/rpc'
|
34
|
+
|
35
|
+
class Server < Rack::RPC::Server
|
36
|
+
before_filter :check_auth
|
37
|
+
|
38
|
+
def hello_world
|
39
|
+
"Hello, world!"
|
40
|
+
end
|
41
|
+
rpc 'hello_world' => :hello_world
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def check_auth
|
46
|
+
raise "Not authorized" unless authorized
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
### Filtering via a proc with more options
|
51
|
+
|
52
|
+
require 'rack/rpc'
|
53
|
+
|
54
|
+
class Server < Rack::RPC::Server
|
55
|
+
before_filter :check_auth, :only => :super_secret_hello_world do
|
56
|
+
raise "Not authorized" unless authorized
|
57
|
+
end
|
58
|
+
|
59
|
+
def hello_world
|
60
|
+
"Hello, world!"
|
61
|
+
end
|
62
|
+
rpc 'hello_world' => :hello_world
|
63
|
+
|
64
|
+
def super_secret_hello_world
|
65
|
+
'super_secret_hello_world'
|
66
|
+
end
|
67
|
+
rpc 'super_secret_hello_world' => :super_secret_hello_world
|
68
|
+
end
|
69
|
+
|
70
|
+
### Running the server
|
71
|
+
|
72
|
+
# config.ru
|
73
|
+
use Rack::RPC::Endpoint, Server.new
|
74
|
+
|
75
|
+
run MyApplication
|
76
|
+
|
77
|
+
### Customizing the default RPC path
|
78
|
+
|
79
|
+
# config.ru
|
80
|
+
use Rack::RPC::Endpoint, Server.new, :path => '/api'
|
81
|
+
|
82
|
+
run MyApplication
|
83
|
+
|
84
|
+
More on Filters
|
85
|
+
---------------
|
86
|
+
|
87
|
+
The `:only` and `:except` options for filters can take a single method or an
|
88
|
+
array of methods.
|
89
|
+
|
90
|
+
You can halt execution in a filter by raising an exception. An error
|
91
|
+
response will be returned with the exception's message set as the error
|
92
|
+
object's message text.
|
93
|
+
|
94
|
+
Communicationg with the Server
|
95
|
+
------------------------------
|
96
|
+
|
97
|
+
By default, methods will only be invoked on `POST` requests to "/rpc". The
|
98
|
+
default path can be overridden by sending a `:path` option when creating
|
99
|
+
your middleware (see example above).
|
100
|
+
|
101
|
+
The protocol used is determined by the `CONTENT_TYPE` header
|
102
|
+
("application/xml" and "text/xml" for XML and "application/json" for JSON).
|
103
|
+
|
104
|
+
Dependencies
|
105
|
+
------------
|
106
|
+
|
107
|
+
* [Rack](http://rubygems.org/gems/rack) (>= 1.0.0)
|
108
|
+
* [Builder](http://rubygems.org/gems/builder) (>= 2.1.2)
|
109
|
+
|
110
|
+
Installation
|
111
|
+
------------
|
112
|
+
|
113
|
+
The recommended installation method is via [RubyGems](http://rubygems.org/).
|
114
|
+
To install the latest official release of the gem, do:
|
115
|
+
|
116
|
+
% [sudo] gem install rack-rpc
|
117
|
+
|
118
|
+
Download
|
119
|
+
--------
|
120
|
+
|
121
|
+
To get a local working copy of the development repository, do:
|
122
|
+
|
123
|
+
% git clone git://github.com/datagraph/rack-rpc.git
|
124
|
+
|
125
|
+
Alternatively, download the latest development version as a tarball as
|
126
|
+
follows:
|
127
|
+
|
128
|
+
% wget http://github.com/datagraph/rack-rpc/tarball/master
|
129
|
+
|
130
|
+
Mailing List
|
131
|
+
------------
|
132
|
+
|
133
|
+
* <http://groups.google.com/group/rack-devel>
|
134
|
+
|
135
|
+
Authors
|
136
|
+
-------
|
137
|
+
|
138
|
+
* [Arto Bendiken](https://github.com/bendiken) - <http://ar.to/>
|
139
|
+
* [Josh Huckabee](https://github.com/jhuckabee) - <http://joshhuckabee.com/>
|
140
|
+
|
141
|
+
License
|
142
|
+
-------
|
143
|
+
|
144
|
+
This is free and unencumbered public domain software. For more information,
|
145
|
+
see <http://unlicense.org/> or the accompanying {file:UNLICENSE} file.
|
146
|
+
|
147
|
+
[Rack]: http://rack.rubyforge.org/
|
148
|
+
[JSON-RPC 2.0]: http://groups.google.com/group/json-rpc/web/json-rpc-2-0
|
149
|
+
[XML-RPC]: http://www.xmlrpc.com/
|
data/UNLICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
2
|
+
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
4
|
+
distribute this software, either in source code form or as a compiled
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
6
|
+
means.
|
7
|
+
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
9
|
+
of this software dedicate any and all copyright interest in the
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
11
|
+
of the public at large and to the detriment of our heirs and
|
12
|
+
successors. We intend this dedication to be an overt act of
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
14
|
+
software under copyright law.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.8
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'json' unless defined?(JSON)
|
2
|
+
|
3
|
+
class Rack::RPC::Endpoint
|
4
|
+
##
|
5
|
+
# @see http://en.wikipedia.org/wiki/JSON-RPC
|
6
|
+
# @see http://groups.google.com/group/json-rpc/web/json-rpc-2-0
|
7
|
+
module JSONRPC
|
8
|
+
CONTENT_TYPE = 'application/json; charset=UTF-8'
|
9
|
+
VERSION = 2.0
|
10
|
+
|
11
|
+
##
|
12
|
+
# @see http://groups.google.com/group/json-rpc/web/json-rpc-2-0
|
13
|
+
class Server
|
14
|
+
##
|
15
|
+
# @param [Rack::RPC::Server] server
|
16
|
+
# @param [Hash{Symbol => Object}] options
|
17
|
+
def initialize(server, options = {})
|
18
|
+
@server, @options = server, options.dup
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# @param [Rack::Request] request
|
23
|
+
# @return [Rack::Response]
|
24
|
+
def execute(request)
|
25
|
+
# Store the request so that it can be accessed from the server methods:
|
26
|
+
@server.request = request if @server.respond_to?(:request=)
|
27
|
+
|
28
|
+
request_body = request.body.read
|
29
|
+
request_body.force_encoding(Encoding::UTF_8) if request_body.respond_to?(:force_encoding) # Ruby 1.9+
|
30
|
+
|
31
|
+
Rack::Response.new([process(request_body, request)], 200, {
|
32
|
+
'Content-Type' => (request.content_type || CONTENT_TYPE).to_s,
|
33
|
+
})
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# @param [String] input
|
38
|
+
# @param [Object] context
|
39
|
+
# @return [String]
|
40
|
+
def process(input, context = nil)
|
41
|
+
response = nil
|
42
|
+
begin
|
43
|
+
response = case (json = JSON.parse(input))
|
44
|
+
when Array then process_batch(json, context)
|
45
|
+
when Hash then process_request(json, context)
|
46
|
+
end
|
47
|
+
rescue JSON::ParserError => exception
|
48
|
+
response = JSONRPC::Response.new
|
49
|
+
response.error = JSONRPC::ParseError.new(:message => exception.to_s)
|
50
|
+
end
|
51
|
+
response.to_json + "\n"
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# @param [Array<Hash>] batch
|
56
|
+
# @param [Object] context
|
57
|
+
# @return [Array]
|
58
|
+
def process_batch(batch, context = nil)
|
59
|
+
batch.map { |struct| process_request(struct, context) }
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# @param [Hash] struct
|
64
|
+
# @param [Object] context
|
65
|
+
# @return [Hash]
|
66
|
+
def process_request(struct, context = nil)
|
67
|
+
response = JSONRPC::Response.new
|
68
|
+
begin
|
69
|
+
request = JSONRPC::Request.new(struct, context)
|
70
|
+
response.id = request.id
|
71
|
+
|
72
|
+
raise ::TypeError, "invalid JSON-RPC request" unless request.valid?
|
73
|
+
|
74
|
+
case operator = @server.class[request.method]
|
75
|
+
when nil
|
76
|
+
raise ::NoMethodError, "undefined operation `#{request.method}'"
|
77
|
+
when Class # a Rack::RPC::Operation subclass
|
78
|
+
response.result = operator.new(request).execute
|
79
|
+
else
|
80
|
+
response.result = @server.__send__(operator, *request.params)
|
81
|
+
end
|
82
|
+
|
83
|
+
rescue ::TypeError => exception # FIXME
|
84
|
+
response.error = JSONRPC::ClientError.new(:message => exception.to_s)
|
85
|
+
|
86
|
+
rescue ::NoMethodError => exception
|
87
|
+
response.error = JSONRPC::NoMethodError.new(:message => exception.to_s)
|
88
|
+
|
89
|
+
rescue ::ArgumentError => exception
|
90
|
+
response.error = JSONRPC::ArgumentError.new(:message => exception.to_s)
|
91
|
+
|
92
|
+
rescue ::Rack::RPC::Error => exception
|
93
|
+
response.error = JSONRPC::Error.new(:message => exception.to_s,
|
94
|
+
:code => exception.code,
|
95
|
+
:data => exception.data)
|
96
|
+
|
97
|
+
rescue => exception
|
98
|
+
response.error = JSONRPC::InternalError.new(:message => exception.to_s)
|
99
|
+
end
|
100
|
+
|
101
|
+
response.to_hash.delete_if { |k, v| v.nil? }
|
102
|
+
end
|
103
|
+
end # Server
|
104
|
+
|
105
|
+
##
|
106
|
+
# Base class for JSON-RPC objects.
|
107
|
+
class Object
|
108
|
+
OPTIONS = {}
|
109
|
+
|
110
|
+
##
|
111
|
+
# @param [String] input
|
112
|
+
# @return [Object]
|
113
|
+
def self.parse(input)
|
114
|
+
self.new(JSON.parse(input))
|
115
|
+
end
|
116
|
+
|
117
|
+
##
|
118
|
+
# An arbitrary context associated with the object.
|
119
|
+
#
|
120
|
+
# @return [Object]
|
121
|
+
attr_reader :context
|
122
|
+
|
123
|
+
##
|
124
|
+
# @param [Hash] options
|
125
|
+
# @param [Object] context
|
126
|
+
# an optional context to associate with the object
|
127
|
+
def initialize(options = {}, context = nil)
|
128
|
+
options = self.class.const_get(:OPTIONS).merge(options)
|
129
|
+
options.each do |k, v|
|
130
|
+
instance_variable_set("@#{k}", v)
|
131
|
+
end
|
132
|
+
@context = context if context
|
133
|
+
end
|
134
|
+
|
135
|
+
##
|
136
|
+
# @return [String]
|
137
|
+
def to_json
|
138
|
+
to_hash.delete_if { |k, v| v.nil? }.to_json
|
139
|
+
end
|
140
|
+
end # Object
|
141
|
+
|
142
|
+
##
|
143
|
+
# JSON-RPC notification objects.
|
144
|
+
class Notification < Object
|
145
|
+
attr_accessor :version
|
146
|
+
attr_accessor :method
|
147
|
+
attr_accessor :params
|
148
|
+
|
149
|
+
##
|
150
|
+
# @return [Boolean]
|
151
|
+
def valid?
|
152
|
+
true # TODO
|
153
|
+
end
|
154
|
+
|
155
|
+
##
|
156
|
+
# @return [Hash]
|
157
|
+
def to_hash
|
158
|
+
{
|
159
|
+
:jsonrpc => (version || VERSION).to_s,
|
160
|
+
:method => method.to_s,
|
161
|
+
:params => params ? params.to_a : [], # NOTE: named arguments not supported
|
162
|
+
}
|
163
|
+
end
|
164
|
+
end # Notification
|
165
|
+
|
166
|
+
##
|
167
|
+
# JSON-RPC request objects.
|
168
|
+
class Request < Notification
|
169
|
+
attr_accessor :id
|
170
|
+
|
171
|
+
##
|
172
|
+
# @return [Boolean]
|
173
|
+
def valid?
|
174
|
+
super && !id.nil?
|
175
|
+
end
|
176
|
+
|
177
|
+
##
|
178
|
+
# @return [Hash]
|
179
|
+
def to_hash
|
180
|
+
super.merge({
|
181
|
+
:id => id,
|
182
|
+
})
|
183
|
+
end
|
184
|
+
|
185
|
+
##
|
186
|
+
# @return [Array]
|
187
|
+
def to_args
|
188
|
+
# used from Operation#initialize
|
189
|
+
params
|
190
|
+
end
|
191
|
+
end # Request
|
192
|
+
|
193
|
+
##
|
194
|
+
# JSON-RPC response objects.
|
195
|
+
class Response < Object
|
196
|
+
attr_accessor :version
|
197
|
+
attr_accessor :result
|
198
|
+
attr_accessor :error
|
199
|
+
attr_accessor :id
|
200
|
+
|
201
|
+
##
|
202
|
+
# @return [Hash]
|
203
|
+
def to_hash
|
204
|
+
{
|
205
|
+
:jsonrpc => (version || VERSION).to_s,
|
206
|
+
:result => result,
|
207
|
+
:error => error ? error.to_hash : nil,
|
208
|
+
:id => id,
|
209
|
+
}
|
210
|
+
end
|
211
|
+
end # Response
|
212
|
+
|
213
|
+
##
|
214
|
+
# JSON-RPC error objects.
|
215
|
+
class Error < Object
|
216
|
+
attr_accessor :code
|
217
|
+
attr_accessor :message
|
218
|
+
attr_accessor :data
|
219
|
+
|
220
|
+
##
|
221
|
+
# @return [Hash]
|
222
|
+
def to_hash
|
223
|
+
{
|
224
|
+
:code => code.to_i,
|
225
|
+
:message => message.to_s,
|
226
|
+
:data => data,
|
227
|
+
}
|
228
|
+
end
|
229
|
+
end # Error
|
230
|
+
|
231
|
+
class ParseError < Error
|
232
|
+
OPTIONS = {:code => -32700, :message => "parse error"}
|
233
|
+
end # ParseError
|
234
|
+
|
235
|
+
class ClientError < Error
|
236
|
+
OPTIONS = {:code => -32600, :message => "invalid request"}
|
237
|
+
end # ClientError
|
238
|
+
|
239
|
+
class NoMethodError < Error
|
240
|
+
OPTIONS = {:code => -32601, :message => "undefined method"}
|
241
|
+
end # NoMethodError
|
242
|
+
|
243
|
+
class ArgumentError < Error
|
244
|
+
OPTIONS = {:code => -32602, :message => "invalid arguments"}
|
245
|
+
end # ArgumentError
|
246
|
+
|
247
|
+
class InternalError < Error
|
248
|
+
OPTIONS = {:code => -32603, :message => "internal error"}
|
249
|
+
end # InternalError
|
250
|
+
|
251
|
+
class ServerError < Error
|
252
|
+
OPTIONS = {:code => -32000, :message => "server error"}
|
253
|
+
end # ServerError
|
254
|
+
end # JSONRPC
|
255
|
+
end # Rack::RPC::Endpoint
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'xmlrpc/server' unless defined?(XMLRPC::BasicServer)
|
2
|
+
require 'builder' # @see http://rubygems.org/gems/builder
|
3
|
+
|
4
|
+
class Rack::RPC::Endpoint
|
5
|
+
##
|
6
|
+
# @see http://en.wikipedia.org/wiki/XML-RPC
|
7
|
+
# @see http://www.xmlrpc.com/spec
|
8
|
+
module XMLRPC
|
9
|
+
CONTENT_TYPE = 'application/xml; charset=UTF-8'
|
10
|
+
|
11
|
+
##
|
12
|
+
# @see http://ruby-doc.org/stdlib/libdoc/xmlrpc/rdoc/classes/XMLRPC/BasicServer.html
|
13
|
+
class Server < ::XMLRPC::BasicServer
|
14
|
+
##
|
15
|
+
# @param [Rack::RPC::Server] server
|
16
|
+
# @param [Hash{Symbol => Object}] options
|
17
|
+
def initialize(server, options = {})
|
18
|
+
@server = server
|
19
|
+
super()
|
20
|
+
add_multicall unless options[:multicall] == false
|
21
|
+
add_introspection unless options[:introspection] == false
|
22
|
+
add_capabilities unless options[:capabilities] == false
|
23
|
+
server.class.rpc.each do |rpc_name, method_name|
|
24
|
+
add_handler(rpc_name, nil, nil, &server.method(method_name))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
##
|
29
|
+
# @param [Rack::Request] request
|
30
|
+
# @return [Rack::Response]
|
31
|
+
def execute(request)
|
32
|
+
@server.request = request # Store the request so it can be accessed from the server methods
|
33
|
+
request_body = request.body.read
|
34
|
+
request_body.force_encoding(Encoding::UTF_8) if request_body.respond_to?(:force_encoding) # Ruby 1.9+
|
35
|
+
Rack::Response.new([process(request_body)], 200, {
|
36
|
+
'Content-Type' => (request.content_type || CONTENT_TYPE).to_s,
|
37
|
+
})
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Process requests and ensure errors are handled properly
|
42
|
+
#
|
43
|
+
# @param [String] request body
|
44
|
+
def process(request_body)
|
45
|
+
begin
|
46
|
+
super(request_body)
|
47
|
+
rescue RuntimeError => e
|
48
|
+
error_response(-32500, "application error - #{e.message}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Implements the `system.getCapabilities` standard method, enabling
|
54
|
+
# clients to determine whether a given capability is supported by this
|
55
|
+
# server.
|
56
|
+
#
|
57
|
+
# @param [Hash{Symbol => Object}] options
|
58
|
+
# @option options [Boolean] :faults_interop (true)
|
59
|
+
# whether to indicate support for the XMLRPC-EPI Specification for
|
60
|
+
# Fault Code Interoperability
|
61
|
+
# @return [void]
|
62
|
+
# @see http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
|
63
|
+
def add_capabilities(options = {})
|
64
|
+
add_handler('system.getCapabilities', %w(struct), '') do
|
65
|
+
capabilities = {}
|
66
|
+
unless options[:faults_interop] == false
|
67
|
+
capabilities['faults_interop'] = {
|
68
|
+
'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
|
69
|
+
'specVersion' => 20010516,
|
70
|
+
}
|
71
|
+
end
|
72
|
+
capabilities
|
73
|
+
end
|
74
|
+
self
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
##
|
79
|
+
# Create a valid error response for a given code and message
|
80
|
+
#
|
81
|
+
# @param [Int] error code
|
82
|
+
# @param [String] error message
|
83
|
+
# @return [String] response xml string
|
84
|
+
def error_response(code, message)
|
85
|
+
xml = Builder::XmlMarkup.new
|
86
|
+
xml.instruct! :xml, :version=>"1.0"
|
87
|
+
xml.methodResponse{
|
88
|
+
xml.fault {
|
89
|
+
xml.value{
|
90
|
+
xml.struct{
|
91
|
+
xml.member{
|
92
|
+
xml.name('faultCode')
|
93
|
+
xml.value{
|
94
|
+
xml.int(code)
|
95
|
+
}
|
96
|
+
}
|
97
|
+
xml.member{
|
98
|
+
xml.name('faultString')
|
99
|
+
xml.value{
|
100
|
+
xml.string(message)
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end # Server
|
109
|
+
end # XMLRPC
|
110
|
+
end # Rack::RPC::Endpoint
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Rack::RPC
|
2
|
+
##
|
3
|
+
# A Rack middleware for RPC endpoints.
|
4
|
+
class Endpoint < Middleware
|
5
|
+
autoload :JSONRPC, 'rack/rpc/endpoint/jsonrpc'
|
6
|
+
autoload :XMLRPC, 'rack/rpc/endpoint/xmlrpc'
|
7
|
+
|
8
|
+
DEFAULT_PATH = '/rpc'
|
9
|
+
|
10
|
+
# @return [Server]
|
11
|
+
attr_reader :server
|
12
|
+
def server
|
13
|
+
@server = @server.call if @server.is_a?(Proc)
|
14
|
+
@server
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# @param [#call] app
|
19
|
+
# @param [Server] server
|
20
|
+
# @param [Hash] options
|
21
|
+
def initialize(app, server, options = {})
|
22
|
+
@server = server
|
23
|
+
super(app, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# @return [String]
|
28
|
+
def path
|
29
|
+
@path ||= options[:path] || DEFAULT_PATH
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# @param [Hash] env
|
34
|
+
# @return [Array]
|
35
|
+
def call(env)
|
36
|
+
return super unless env['PATH_INFO'].eql?(path)
|
37
|
+
return super unless env['REQUEST_METHOD'].eql?('POST')
|
38
|
+
case content_type = env['CONTENT_TYPE']
|
39
|
+
when %r(^application/xml), %r(^text/xml)
|
40
|
+
XMLRPC::Server.new(server).execute(Rack::Request.new(env)).finish
|
41
|
+
when %r(^application/json)
|
42
|
+
JSONRPC::Server.new(server).execute(Rack::Request.new(env)).finish
|
43
|
+
else super
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end # Endpoint
|
47
|
+
end # Rack::RPC
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "xmlrpc/parser" unless defined?(XMLRPC::FaultException)
|
2
|
+
|
3
|
+
module Rack::RPC
|
4
|
+
##
|
5
|
+
# Represents an RPC Exception service.
|
6
|
+
#
|
7
|
+
class Error < XMLRPC::FaultException
|
8
|
+
attr_reader :data
|
9
|
+
|
10
|
+
alias code faultCode
|
11
|
+
alias message faultString
|
12
|
+
alias to_s faultString
|
13
|
+
|
14
|
+
##
|
15
|
+
# Creates a new rpc related exception. This is useful if one wants to define
|
16
|
+
# custom exceptions.
|
17
|
+
# @param [Fixnum] code an error code for the exception (used for mapping
|
18
|
+
# on the client side)
|
19
|
+
# @param [String] message the message that should be send along
|
20
|
+
# @param [Object] a data object that may contain additional data on the
|
21
|
+
# error (CAUTION: this is not possible with XMLRPC)
|
22
|
+
def initialize(code, message, data = nil)
|
23
|
+
@data = data
|
24
|
+
super(code, message)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Rack::RPC
|
2
|
+
##
|
3
|
+
# A Rack middleware base class.
|
4
|
+
class Middleware
|
5
|
+
# @return [#call]
|
6
|
+
attr_reader :app
|
7
|
+
|
8
|
+
# @return [Hash]
|
9
|
+
attr_reader :options
|
10
|
+
|
11
|
+
##
|
12
|
+
# @param [#call] app
|
13
|
+
# @param [Hash] options
|
14
|
+
def initialize(app, options = {})
|
15
|
+
@app, @options = app, options.dup
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# @param [Hash] env
|
20
|
+
# @return [Array]
|
21
|
+
def call(env)
|
22
|
+
@app.call(env)
|
23
|
+
end
|
24
|
+
end # Middleware
|
25
|
+
end # Rack::RPC
|
@@ -0,0 +1,226 @@
|
|
1
|
+
module Rack::RPC
|
2
|
+
##
|
3
|
+
# Represents an RPC server operation.
|
4
|
+
#
|
5
|
+
class Operation
|
6
|
+
##
|
7
|
+
# Defines an operand for this operation class.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# class Multiply < Operation
|
11
|
+
# operand :x, Numeric
|
12
|
+
# operand :y, Numeric
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# @param [Symbol, #to_sym] name
|
16
|
+
# @param [Class] type
|
17
|
+
# @param [Hash{Symbol => Object}] options
|
18
|
+
# @option options [Boolean] :optional (false)
|
19
|
+
# @option options [Boolean] :nullable (false)
|
20
|
+
# @return [void]
|
21
|
+
def self.operand(name, type = Object, options = {})
|
22
|
+
raise TypeError, "expected a Class, but got #{type.inspect}" unless type.is_a?(Class)
|
23
|
+
operands[name.to_sym] = options.merge(:type => type)
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Defines the `#prepare` instance method.
|
28
|
+
#
|
29
|
+
# @yield
|
30
|
+
# @return [void]
|
31
|
+
def self.prepare(&block)
|
32
|
+
self.send(:define_method, :prepare) do
|
33
|
+
begin
|
34
|
+
begin
|
35
|
+
before_prepare if respond_to?(:before_prepare)
|
36
|
+
instance_eval(&block)
|
37
|
+
ensure
|
38
|
+
after_prepare if respond_to?(:after_prepare)
|
39
|
+
end
|
40
|
+
self
|
41
|
+
rescue Exception => error
|
42
|
+
after_error(error) if respond_to?(:after_error)
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
##
|
49
|
+
# Defines the `#execute` instance method.
|
50
|
+
#
|
51
|
+
# @yield
|
52
|
+
# @return [void]
|
53
|
+
def self.execute(&block)
|
54
|
+
self.send(:define_method, :execute) do
|
55
|
+
begin
|
56
|
+
before_execute if respond_to?(:before_execute)
|
57
|
+
result = instance_eval(&block)
|
58
|
+
after_execute if respond_to?(:after_execute)
|
59
|
+
result
|
60
|
+
rescue Exception => error
|
61
|
+
after_error(error) if respond_to?(:after_error)
|
62
|
+
raise
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
##
|
68
|
+
# Returns the operand definitions for this operation class.
|
69
|
+
#
|
70
|
+
# @return [Hash{Symbol => Hash}]
|
71
|
+
def self.operands
|
72
|
+
@operands ||= {}
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Returns the arity range for this operation class.
|
77
|
+
#
|
78
|
+
# @return [Range]
|
79
|
+
def self.arity
|
80
|
+
@arity ||= begin
|
81
|
+
if const_defined?(:ARITY)
|
82
|
+
const_get(:ARITY)
|
83
|
+
else
|
84
|
+
min, max = 0, 0
|
85
|
+
operands.each do |name, options|
|
86
|
+
min += 1 unless options[:optional].eql?(true)
|
87
|
+
max += 1
|
88
|
+
end
|
89
|
+
Range.new(min, max)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
##
|
95
|
+
# @return [Object]
|
96
|
+
attr_reader :context
|
97
|
+
def context() @__context__ end
|
98
|
+
|
99
|
+
##
|
100
|
+
# Initializes a new operation with the given arguments.
|
101
|
+
#
|
102
|
+
# @param [Hash{Symbol => Object}] args
|
103
|
+
def initialize(args = [])
|
104
|
+
case args
|
105
|
+
when Array then initialize_from_array(args)
|
106
|
+
when Hash then initialize_from_hash(args)
|
107
|
+
else case
|
108
|
+
when args.respond_to?(:to_args)
|
109
|
+
initialize_from_array(args.to_args)
|
110
|
+
@__context__ = args.context if args.respond_to?(:context)
|
111
|
+
else raise ArgumentError, "expected an Array or Hash, but got #{args.inspect}"
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
initialize! if respond_to?(:initialize!)
|
116
|
+
end
|
117
|
+
|
118
|
+
##
|
119
|
+
# @private
|
120
|
+
def initialize_from_array(args)
|
121
|
+
validate_arity!(args)
|
122
|
+
|
123
|
+
pos = 0
|
124
|
+
self.class.operands.each do |param_name, param_options|
|
125
|
+
arg = args[pos]; pos += 1
|
126
|
+
|
127
|
+
validate_argument!(arg, param_name, param_options)
|
128
|
+
|
129
|
+
instance_variable_set("@#{param_name}", arg)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
protected :initialize_from_array
|
133
|
+
|
134
|
+
##
|
135
|
+
# @private
|
136
|
+
def initialize_from_hash(args)
|
137
|
+
validate_arity!(args)
|
138
|
+
|
139
|
+
params = self.class.operands
|
140
|
+
args.each do |param_name, arg|
|
141
|
+
param_options = params[param_name.to_sym]
|
142
|
+
|
143
|
+
raise ArgumentError, "unknown parameter name #{param_name.inspect}" unless param_options
|
144
|
+
validate_argument!(arg, param_name, param_options)
|
145
|
+
|
146
|
+
instance_variable_set("@#{param_name}", arg)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
protected :initialize_from_hash
|
150
|
+
|
151
|
+
##
|
152
|
+
# @private
|
153
|
+
def validate_arity!(args)
|
154
|
+
unless self.class.arity.include?(argc = args.count)
|
155
|
+
raise ArgumentError, (argc < self.class.arity.min) ?
|
156
|
+
"too few arguments (#{argc} for #{self.class.arity.min})" :
|
157
|
+
"too many arguments (#{argc} for #{self.class.arity.max})"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
protected :validate_arity!
|
161
|
+
|
162
|
+
##
|
163
|
+
# @private
|
164
|
+
def validate_argument!(arg, param_name, param_options)
|
165
|
+
return if arg.nil? && (param_options[:nullable] || param_options[:optional])
|
166
|
+
|
167
|
+
if (param_type = param_options[:type]) && !(param_type === arg)
|
168
|
+
case param_type
|
169
|
+
when Regexp
|
170
|
+
raise TypeError, "expected a String matching #{param_type.inspect}, but got #{arg.inspect}"
|
171
|
+
else
|
172
|
+
raise TypeError, "expected a #{param_type}, but got #{arg.inspect}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
protected :validate_argument!
|
177
|
+
|
178
|
+
##
|
179
|
+
# Prepares this operation.
|
180
|
+
#
|
181
|
+
# @abstract
|
182
|
+
# @return [void] `self`
|
183
|
+
def prepare
|
184
|
+
self
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Executes this operation.
|
189
|
+
#
|
190
|
+
# @abstract
|
191
|
+
# @return [void]
|
192
|
+
def execute
|
193
|
+
raise NotImplementedError, "#{self.class}#execute"
|
194
|
+
end
|
195
|
+
|
196
|
+
##
|
197
|
+
# Returns the array representation of the arguments to this operation.
|
198
|
+
#
|
199
|
+
# @return [Array]
|
200
|
+
def to_a
|
201
|
+
self.class.operands.inject([]) do |result, (param_name, param_options)|
|
202
|
+
result << instance_variable_get("@#{param_name}")
|
203
|
+
result
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Returns the hash representation of the arguments to this operation.
|
209
|
+
#
|
210
|
+
# @return [Hash]
|
211
|
+
def to_hash
|
212
|
+
self.class.operands.inject({}) do |result, (param_name, param_options)|
|
213
|
+
result[param_name] = instance_variable_get("@#{param_name}")
|
214
|
+
result
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# Returns the JSON representation of the arguments to this operation.
|
220
|
+
#
|
221
|
+
# @return [String] a serialized JSON object
|
222
|
+
def to_json
|
223
|
+
to_hash.to_json
|
224
|
+
end
|
225
|
+
end # Operation
|
226
|
+
end # Rack::RPC
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Rack::RPC
|
2
|
+
##
|
3
|
+
# A base class for RPC servers.
|
4
|
+
class Server
|
5
|
+
##
|
6
|
+
# @private
|
7
|
+
def self.[](rpc_method_name)
|
8
|
+
@mappings ||= {}
|
9
|
+
@mappings[rpc_method_name]
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# @private
|
14
|
+
def self.rpc(mappings = {})
|
15
|
+
@mappings ||= {}
|
16
|
+
if mappings.empty?
|
17
|
+
@mappings
|
18
|
+
else
|
19
|
+
# Store the mappings
|
20
|
+
@mappings.merge!(mappings)
|
21
|
+
|
22
|
+
# Wrap each method so we can inject before and after callbacks
|
23
|
+
mappings.each do |rpc_method_name, server_method|
|
24
|
+
self.send(:alias_method, :"#{server_method}_without_callbacks", server_method.to_sym)
|
25
|
+
self.send(:define_method, server_method) do |*args|
|
26
|
+
self.class.hooks[:before].each{|command| command.call(self) if command.callable?(server_method)}
|
27
|
+
out = self.send(:"#{server_method}_without_callbacks", *args)
|
28
|
+
self.class.hooks[:after].each{|command| command.call(self) if command.callable?(server_method)}
|
29
|
+
out
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.hooks
|
36
|
+
@hooks ||= {:before => [], :after => []}
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.before_filter(method_sym = nil, options = {}, &block)
|
40
|
+
setup_hook(:before, method_sym, options, block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.after_filter(method_sym = nil, options = {}, &block)
|
44
|
+
setup_hook(:after, method_sym, options, block)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Hash]
|
48
|
+
attr_reader :options
|
49
|
+
|
50
|
+
# @return [Rack::Request]
|
51
|
+
attr_accessor :request
|
52
|
+
|
53
|
+
##
|
54
|
+
# @param [Hash] options
|
55
|
+
def initialize(options = {}, &block)
|
56
|
+
@options = options.dup
|
57
|
+
block.call(self) if block_given?
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def self.setup_hook(type, method, options, proc)
|
63
|
+
hooks[type] << if proc
|
64
|
+
ProcCommand.new(proc, options)
|
65
|
+
else
|
66
|
+
MethodCommand.new(method, options)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end # Server
|
71
|
+
|
72
|
+
class Command
|
73
|
+
attr_reader :options
|
74
|
+
|
75
|
+
def initialize(options)
|
76
|
+
@options = options
|
77
|
+
|
78
|
+
# Convert non-array options to arrays
|
79
|
+
[:only, :except].each do |option|
|
80
|
+
options[option] = [options[option]] if !options[option].nil? && !options[option].is_a?(Array)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def callable?(method)
|
85
|
+
options.empty? ||
|
86
|
+
(!options[:only].nil? && options[:only].include?(method)) ||
|
87
|
+
(!options[:except].nil? && !options[:except].include?(method))
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class ProcCommand < Command
|
92
|
+
def initialize(proc, options)
|
93
|
+
@proc = proc.to_proc
|
94
|
+
super(options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def call(server)
|
98
|
+
server.instance_eval(&@proc)
|
99
|
+
end
|
100
|
+
end # ProcCommand
|
101
|
+
|
102
|
+
class MethodCommand < Command
|
103
|
+
def initialize(method, options)
|
104
|
+
@method = method.to_sym
|
105
|
+
super(options)
|
106
|
+
end
|
107
|
+
|
108
|
+
def call(server)
|
109
|
+
server.__send__(@method)
|
110
|
+
end
|
111
|
+
end # MethodCommand
|
112
|
+
end # Rack::RPC
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Rack::RPC
|
2
|
+
##
|
3
|
+
# Represents an RPC service.
|
4
|
+
#
|
5
|
+
class Service
|
6
|
+
##
|
7
|
+
# Defines an operator for this service class.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# class Calculator < Service
|
11
|
+
# operator Add
|
12
|
+
# operator Subtract
|
13
|
+
# operator Multiply
|
14
|
+
# operator Divide
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @param [Class] klass
|
18
|
+
# @param [Hash{Symbol => Object}] options
|
19
|
+
# @return [void]
|
20
|
+
def self.operator(klass, options = {})
|
21
|
+
raise TypeError, "expected a Class, but got #{klass.inspect}" unless klass.is_a?(Class)
|
22
|
+
operators[klass] ||= options
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Returns the operator definitions for this service class.
|
27
|
+
#
|
28
|
+
# @return [Hash{Class => Hash}]
|
29
|
+
def self.operators
|
30
|
+
@operators ||= {}
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Returns the operator class for the given operator name.
|
35
|
+
#
|
36
|
+
# @param [Symbol, #to_sym] operator_name
|
37
|
+
# @return [Class]
|
38
|
+
def self.[](operator_name)
|
39
|
+
operator_name = operator_name.to_sym
|
40
|
+
operators.find do |klass, options|
|
41
|
+
klass_name = klass.name.split('::').last # TODO: optimize this
|
42
|
+
return klass if operator_name.eql?(klass_name.to_sym)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
##
|
47
|
+
# @param [Symbol, #to_sym] method_name
|
48
|
+
# @return [Boolean] `true` or `false`
|
49
|
+
def respond_to?(method_name)
|
50
|
+
super || (self.class[method_name] ? true : false)
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# @param [Symbol, #to_sym] method_name
|
55
|
+
# @param [Array] args
|
56
|
+
# @return [void]
|
57
|
+
# @raise [NoMethodError] if `self` doesn't respond to `method_name`
|
58
|
+
def method_missing(method_name, *args, &block)
|
59
|
+
if (operator = self.class[method_name]).nil?
|
60
|
+
super # raises NoMethodError
|
61
|
+
else
|
62
|
+
operator.new(args).execute
|
63
|
+
end
|
64
|
+
end
|
65
|
+
protected :method_missing
|
66
|
+
end # Service
|
67
|
+
end # Rack::RPC
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Rack; module RPC
|
2
|
+
module VERSION
|
3
|
+
MAJOR = 0
|
4
|
+
MINOR = 0
|
5
|
+
TINY = 8
|
6
|
+
EXTRA = nil
|
7
|
+
|
8
|
+
STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.')
|
9
|
+
|
10
|
+
##
|
11
|
+
# @return [String]
|
12
|
+
def self.to_s() STRING end
|
13
|
+
|
14
|
+
##
|
15
|
+
# @return [String]
|
16
|
+
def self.to_str() STRING end
|
17
|
+
|
18
|
+
##
|
19
|
+
# @return [Array(Integer, Integer, Integer)]
|
20
|
+
def self.to_a() [MAJOR, MINOR, TINY] end
|
21
|
+
end
|
22
|
+
end; end
|
data/lib/rack/rpc.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rack' # @see http://rubygems.org/gems/rack
|
2
|
+
|
3
|
+
module Rack
|
4
|
+
module RPC
|
5
|
+
autoload :Endpoint, 'rack/rpc/endpoint'
|
6
|
+
autoload :Middleware, 'rack/rpc/middleware'
|
7
|
+
autoload :Operation, 'rack/rpc/operation'
|
8
|
+
autoload :Server, 'rack/rpc/server'
|
9
|
+
autoload :Service, 'rack/rpc/service'
|
10
|
+
autoload :VERSION, 'rack/rpc/version'
|
11
|
+
autoload :Error, 'rack/rpc/error'
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: threez-rack-rpc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.8
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Datagraph
|
9
|
+
- Vincent Landgraf
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-05-17 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: builder
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.1.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 2.1.2
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rack
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '1.0'
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '1.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: nokogiri
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.4.4
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.4.4
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: yard
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: 0.6.0
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 0.6.0
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: rspec
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 2.1.0
|
87
|
+
type: :development
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: 2.1.0
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: rack-test
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 0.5.6
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.5.6
|
111
|
+
description: Rack middleware for serving up RPC endpoints.
|
112
|
+
email: vincent.landgraf@1und1.de
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- AUTHORS
|
118
|
+
- CREDITS
|
119
|
+
- README
|
120
|
+
- UNLICENSE
|
121
|
+
- VERSION
|
122
|
+
- lib/rack/rpc/endpoint/jsonrpc.rb
|
123
|
+
- lib/rack/rpc/endpoint/xmlrpc.rb
|
124
|
+
- lib/rack/rpc/endpoint.rb
|
125
|
+
- lib/rack/rpc/error.rb
|
126
|
+
- lib/rack/rpc/middleware.rb
|
127
|
+
- lib/rack/rpc/operation.rb
|
128
|
+
- lib/rack/rpc/server.rb
|
129
|
+
- lib/rack/rpc/service.rb
|
130
|
+
- lib/rack/rpc/version.rb
|
131
|
+
- lib/rack/rpc.rb
|
132
|
+
homepage: https://github.com/threez/rack-rpc
|
133
|
+
licenses:
|
134
|
+
- Public Domain
|
135
|
+
post_install_message:
|
136
|
+
rdoc_options: []
|
137
|
+
require_paths:
|
138
|
+
- lib
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ! '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: 1.9.2
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ! '>='
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
requirements: []
|
152
|
+
rubyforge_project:
|
153
|
+
rubygems_version: 1.8.24
|
154
|
+
signing_key:
|
155
|
+
specification_version: 3
|
156
|
+
summary: JSON-RPC/XML-RPC server for Rack applications.
|
157
|
+
test_files: []
|
158
|
+
has_rdoc: false
|