rack-rpc 0.0.0 → 0.0.1

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