rack-rpc 0.0.0 → 0.0.1

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.0
1
+ 0.0.1
data/lib/rack/rpc.rb CHANGED
@@ -1,7 +1,10 @@
1
- require 'rack'
1
+ require 'rack' # @see http://rubygems.org/gems/rack
2
2
 
3
3
  module Rack
4
4
  module RPC
5
- autoload :VERSION, 'rack/rpc/version'
5
+ autoload :Endpoint, 'rack/rpc/endpoint'
6
+ autoload :Middleware, 'rack/rpc/middleware'
7
+ autoload :Server, 'rack/rpc/server'
8
+ autoload :VERSION, 'rack/rpc/version'
6
9
  end
7
10
  end
@@ -0,0 +1,47 @@
1
+ module Rack; module 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; end # Rack::RPC
@@ -0,0 +1,212 @@
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
+ Rack::Response.new([process(request.body.read)], 200, {
26
+ 'Content-Type' => (request.content_type || CONTENT_TYPE).to_s,
27
+ })
28
+ end
29
+
30
+ ##
31
+ # @param [String] input
32
+ # @return [String]
33
+ def process(input)
34
+ begin
35
+ response = case json = JSON.parse(input)
36
+ when Array then process_batch(json)
37
+ when Hash then process_request(json)
38
+ end
39
+ rescue JSON::ParserError => exception
40
+ response = JSONRPC::Response.new
41
+ response.error = JSONRPC::ParseError.new(:message => exception.to_s)
42
+ end
43
+ response.to_json + "\n"
44
+ end
45
+
46
+ ##
47
+ # @param [Array<Hash>] batch
48
+ # @return [Array]
49
+ def process_batch(batch)
50
+ batch.map { |struct| process_request(struct) }
51
+ end
52
+
53
+ ##
54
+ # @param [Hash] struct
55
+ # @return [Hash]
56
+ def process_request(struct)
57
+ response = JSONRPC::Response.new
58
+ begin
59
+ request = JSONRPC::Request.new(struct)
60
+ response.id = request.id
61
+ raise ::TypeError, "invalid request" unless request.valid?
62
+ method = @server.class.rpc[request.method]
63
+ raise ::NoMethodError, "undefined method `#{request.method}'" unless method && @server.respond_to?(method)
64
+ response.result = @server.__send__(method, *request.params)
65
+ rescue ::TypeError => exception # FIXME
66
+ response.error = JSONRPC::ClientError.new(:message => exception.to_s)
67
+ rescue ::NoMethodError => exception
68
+ response.error = JSONRPC::NoMethodError.new(:message => exception.to_s)
69
+ rescue ::ArgumentError => exception
70
+ response.error = JSONRPC::ArgumentError.new(:message => exception.to_s)
71
+ rescue => exception
72
+ response.error = JSONRPC::InternalError.new(:message => exception.to_s)
73
+ end
74
+ response.to_hash.delete_if { |k, v| v.nil? }
75
+ end
76
+ end # Server
77
+
78
+ ##
79
+ # Base class for JSON-RPC objects.
80
+ class Object
81
+ OPTIONS = {}
82
+
83
+ ##
84
+ # @parse [String] input
85
+ # @return [Object]
86
+ def self.parse(input)
87
+ self.new(JSON.parse(input))
88
+ end
89
+
90
+ ##
91
+ # @param [Hash] options
92
+ def initialize(options = {})
93
+ options = self.class.const_get(:OPTIONS).merge(options)
94
+ options.each do |k, v|
95
+ instance_variable_set("@#{k}", v)
96
+ end
97
+ end
98
+
99
+ ##
100
+ # @return [String]
101
+ def to_json
102
+ to_hash.delete_if { |k, v| v.nil? }.to_json
103
+ end
104
+ end # Object
105
+
106
+ ##
107
+ # JSON-RPC notification objects.
108
+ class Notification < Object
109
+ attr_accessor :version
110
+ attr_accessor :method
111
+ attr_accessor :params
112
+
113
+ ##
114
+ # @return [Boolean]
115
+ def valid?
116
+ true # TODO
117
+ end
118
+
119
+ ##
120
+ # @return [Hash]
121
+ def to_hash
122
+ {
123
+ :jsonrpc => (version || VERSION).to_s,
124
+ :method => method.to_s,
125
+ :params => params ? params.to_a : [], # NOTE: named arguments not supported
126
+ }
127
+ end
128
+ end # Notification
129
+
130
+ ##
131
+ # JSON-RPC request objects.
132
+ class Request < Notification
133
+ attr_accessor :id
134
+
135
+ ##
136
+ # @return [Boolean]
137
+ def valid?
138
+ super && !id.nil?
139
+ end
140
+
141
+ ##
142
+ # @return [Hash]
143
+ def to_hash
144
+ super.merge({
145
+ :id => id,
146
+ })
147
+ end
148
+ end # Request
149
+
150
+ ##
151
+ # JSON-RPC response objects.
152
+ class Response < Object
153
+ attr_accessor :version
154
+ attr_accessor :result
155
+ attr_accessor :error
156
+ attr_accessor :id
157
+
158
+ ##
159
+ # @return [Hash]
160
+ def to_hash
161
+ {
162
+ :jsonrpc => (version || VERSION).to_s,
163
+ :result => result,
164
+ :error => error ? error.to_hash : nil,
165
+ :id => id,
166
+ }
167
+ end
168
+ end # Response
169
+
170
+ ##
171
+ # JSON-RPC error objects.
172
+ class Error < Object
173
+ attr_accessor :code
174
+ attr_accessor :message
175
+ attr_accessor :data
176
+
177
+ ##
178
+ # @return [Hash]
179
+ def to_hash
180
+ {
181
+ :code => code.to_i,
182
+ :message => message.to_s,
183
+ :data => data,
184
+ }
185
+ end
186
+ end # Error
187
+
188
+ class ParseError < Error
189
+ OPTIONS = {:code => -32700, :message => "parse error"}
190
+ end # ParseError
191
+
192
+ class ClientError < Error
193
+ OPTIONS = {:code => -32600, :message => "invalid request"}
194
+ end # ClientError
195
+
196
+ class NoMethodError < Error
197
+ OPTIONS = {:code => -32601, :message => "undefined method"}
198
+ end # NoMethodError
199
+
200
+ class ArgumentError < Error
201
+ OPTIONS = {:code => -32602, :message => "invalid arguments"}
202
+ end # ArgumentError
203
+
204
+ class InternalError < Error
205
+ OPTIONS = {:code => -32603, :message => "internal error"}
206
+ end # InternalError
207
+
208
+ class ServerError < Error
209
+ OPTIONS = {:code => -32000, :message => "server error"}
210
+ end # ServerError
211
+ end # JSONRPC
212
+ end # Rack::RPC::Endpoint
@@ -0,0 +1,65 @@
1
+ require 'xmlrpc/server' unless defined?(XMLRPC::BasicServer)
2
+ begin
3
+ require 'builder/xchar' # @see http://rubygems.org/gems/builder
4
+ rescue LoadError => e
5
+ end
6
+
7
+ class Rack::RPC::Endpoint
8
+ ##
9
+ # @see http://en.wikipedia.org/wiki/XML-RPC
10
+ # @see http://www.xmlrpc.com/spec
11
+ module XMLRPC
12
+ CONTENT_TYPE = 'application/xml; charset=UTF-8'
13
+
14
+ ##
15
+ # @see http://ruby-doc.org/stdlib/libdoc/xmlrpc/rdoc/classes/XMLRPC/BasicServer.html
16
+ class Server < ::XMLRPC::BasicServer
17
+ ##
18
+ # @param [Rack::RPC::Server] server
19
+ # @param [Hash{Symbol => Object}] options
20
+ def initialize(server, options = {})
21
+ super()
22
+ add_multicall unless options[:multicall] == false
23
+ add_introspection unless options[:introspection] == false
24
+ add_capabilities unless options[:capabilities] == false
25
+ server.class.rpc.each do |rpc_name, method_name|
26
+ add_handler(rpc_name, nil, nil, &server.method(method_name))
27
+ end
28
+ end
29
+
30
+ ##
31
+ # @param [Rack::Request] request
32
+ # @return [Rack::Response]
33
+ def execute(request)
34
+ Rack::Response.new([process(request.body.read)], 200, {
35
+ 'Content-Type' => (request.content_type || CONTENT_TYPE).to_s,
36
+ })
37
+ end
38
+
39
+ ##
40
+ # Implements the `system.getCapabilities` standard method, enabling
41
+ # clients to determine whether a given capability is supported by this
42
+ # server.
43
+ #
44
+ # @param [Hash{Symbol => Object}] options
45
+ # @option options [Boolean] :faults_interop (true)
46
+ # whether to indicate support for the XMLRPC-EPI Specification for
47
+ # Fault Code Interoperability
48
+ # @return [void]
49
+ # @see http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
50
+ def add_capabilities(options = {})
51
+ add_handler('system.getCapabilities', %w(struct), '') do
52
+ capabilities = {}
53
+ unless options[:faults_interop] == false
54
+ capabilities['faults_interop'] = {
55
+ 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',
56
+ 'specVersion' => 20010516,
57
+ }
58
+ end
59
+ capabilities
60
+ end
61
+ self
62
+ end
63
+ end # Server
64
+ end # XMLRPC
65
+ end # Rack::RPC::Endpoint
@@ -0,0 +1,25 @@
1
+ module Rack; module 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; end # Rack::RPC
@@ -0,0 +1,26 @@
1
+ module Rack; module RPC
2
+ ##
3
+ # A base class for RPC servers.
4
+ class Server
5
+ ##
6
+ # @private
7
+ def self.rpc(mappings = {})
8
+ @mappings ||= {}
9
+ if mappings.empty?
10
+ @mappings
11
+ else
12
+ @mappings.merge!(mappings)
13
+ end
14
+ end
15
+
16
+ # @return [Hash]
17
+ attr_reader :options
18
+
19
+ ##
20
+ # @param [Hash] options
21
+ def initialize(options = {}, &block)
22
+ @options = options.dup
23
+ block.call(self) if block_given?
24
+ end
25
+ end # Server
26
+ end; end # Rack::RPC
@@ -2,7 +2,7 @@ module Rack; module RPC
2
2
  module VERSION
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 0
5
+ TINY = 1
6
6
  EXTRA = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, EXTRA].compact.join('.')
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 0
9
- version: 0.0.0
8
+ - 1
9
+ version: 0.0.1
10
10
  platform: ruby
11
11
  authors:
12
12
  - Datagraph
@@ -86,6 +86,11 @@ files:
86
86
  - README
87
87
  - UNLICENSE
88
88
  - VERSION
89
+ - lib/rack/rpc/endpoint/jsonrpc.rb
90
+ - lib/rack/rpc/endpoint/xmlrpc.rb
91
+ - lib/rack/rpc/endpoint.rb
92
+ - lib/rack/rpc/middleware.rb
93
+ - lib/rack/rpc/server.rb
89
94
  - lib/rack/rpc/version.rb
90
95
  - lib/rack/rpc.rb
91
96
  has_rdoc: false