threez-rack-rpc 0.0.8
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/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
|