jimson-reloaded 0.12.0

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.
@@ -0,0 +1,25 @@
1
+ module Jimson
2
+ class Request
3
+
4
+ attr_accessor :method, :params, :id
5
+ def initialize(method, params, id = nil)
6
+ @method = method
7
+ @params = params
8
+ @id = id
9
+ end
10
+
11
+ def to_h
12
+ h = {
13
+ 'jsonrpc' => '2.0',
14
+ 'method' => @method
15
+ }
16
+ h.merge!('params' => @params) if !!@params && !params.empty?
17
+ h.merge!('id' => id)
18
+ end
19
+
20
+ def to_json(*a)
21
+ MultiJson.encode(self.to_h)
22
+ end
23
+
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ module Jimson
2
+ class Response
3
+ attr_accessor :result, :error, :id
4
+
5
+ def initialize(id)
6
+ @id = id
7
+ end
8
+
9
+ def to_h
10
+ h = {'jsonrpc' => '2.0'}
11
+ h.merge!('result' => @result) if !!@result
12
+ h.merge!('error' => @error) if !!@error
13
+ h.merge!('id' => @id)
14
+ end
15
+
16
+ def is_error?
17
+ !!@error
18
+ end
19
+
20
+ def succeeded?
21
+ !!@result
22
+ end
23
+
24
+ def populate!(data)
25
+ @error = data['error'] if !!data['error']
26
+ @result = data['result'] if !!data['result']
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ require 'jimson/router/map'
2
+ require 'forwardable'
3
+
4
+ module Jimson
5
+ class Router
6
+ extend Forwardable
7
+
8
+ def_delegators :@map, :handler_for_method,
9
+ :root,
10
+ :namespace,
11
+ :jimson_methods,
12
+ :strip_method_namespace
13
+
14
+ def initialize(opts = {})
15
+ @map = Map.new(opts)
16
+ end
17
+
18
+ def draw(&block)
19
+ @map.instance_eval &block
20
+ self
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,77 @@
1
+ module Jimson
2
+ class Router
3
+
4
+ #
5
+ # Provides a DSL for routing method namespaces to handlers.
6
+ # Only handles root-level and non-nested namespaces, e.g. 'foo.bar' or 'foo'.
7
+ #
8
+ class Map
9
+
10
+ def initialize(opts = {})
11
+ @routes = {}
12
+ @opts = opts
13
+ @ns_sep = opts[:ns_sep] || '.'
14
+ end
15
+
16
+ #
17
+ # Set the root handler, i.e. the handler used for a bare method like 'foo'
18
+ #
19
+ def root(handler)
20
+ handler = handler.new if handler.is_a?(Class)
21
+ @routes[''] = handler
22
+ end
23
+
24
+ #
25
+ # Define the handler for a namespace
26
+ #
27
+ def namespace(ns, handler = nil, &block)
28
+ if !!handler
29
+ handler = handler.new if handler.is_a?(Class)
30
+ @routes[ns.to_s] = handler
31
+ else
32
+ # passed a block for nested namespacing
33
+ map = Jimson::Router::Map.new(@opts)
34
+ @routes[ns.to_s] = map
35
+ map.instance_eval &block
36
+ end
37
+ end
38
+
39
+ #
40
+ # Return the handler for a (possibly namespaced) method name
41
+ #
42
+ def handler_for_method(method)
43
+ parts = method.split(@ns_sep)
44
+ ns = (method.index(@ns_sep) == nil ? '' : parts.first)
45
+ handler = @routes[ns]
46
+ if handler.is_a?(Jimson::Router::Map)
47
+ return handler.handler_for_method(parts[1..-1].join(@ns_sep))
48
+ end
49
+ handler
50
+ end
51
+
52
+ #
53
+ # Strip off the namespace part of a method and return the bare method name
54
+ #
55
+ def strip_method_namespace(method)
56
+ method.split(@ns_sep).last
57
+ end
58
+
59
+ #
60
+ # Return an array of all methods on handlers in the map, fully namespaced
61
+ #
62
+ def jimson_methods
63
+ arr = @routes.keys.map do |ns|
64
+ prefix = (ns == '' ? '' : "#{ns}#{@ns_sep}")
65
+ handler = @routes[ns]
66
+ if handler.is_a?(Jimson::Router::Map)
67
+ handler.jimson_methods
68
+ else
69
+ handler.class.jimson_exposed_methods.map { |method| prefix + method }
70
+ end
71
+ end
72
+ arr.flatten
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,225 @@
1
+ require 'rack'
2
+ require 'rack/request'
3
+ require 'rack/response'
4
+ require 'multi_json'
5
+ require 'jimson/handler'
6
+ require 'jimson/router'
7
+ require 'jimson/server/error'
8
+
9
+ module Jimson
10
+ class Server
11
+
12
+ class System
13
+ extend Handler
14
+
15
+ def initialize(router)
16
+ @router = router
17
+ end
18
+
19
+ def listMethods
20
+ @router.jimson_methods
21
+ end
22
+
23
+ def isAlive
24
+ true
25
+ end
26
+ end
27
+
28
+ JSON_RPC_VERSION = '2.0'
29
+
30
+ attr_accessor :router, :host, :port, :show_errors, :opts
31
+
32
+ #
33
+ # Create a Server with routes defined
34
+ #
35
+ def self.with_routes(opts = {}, &block)
36
+ router = Router.new
37
+ router.send(:draw, &block)
38
+ self.new(router, opts)
39
+ end
40
+
41
+ #
42
+ # +router_or_handler+ is an instance of Jimson::Router or extends Jimson::Handler
43
+ #
44
+ # +opts+ may include:
45
+ # * :host - the hostname or ip to bind to
46
+ # * :port - the port to listen on
47
+ # * :server - the rack handler to use, e.g. 'webrick' or 'thin'
48
+ # * :show_errors - true or false, send backtraces in error responses?
49
+ #
50
+ # Remaining options are forwarded to the underlying Rack server.
51
+ #
52
+ def initialize(router_or_handler, opts = {})
53
+ if !router_or_handler.is_a?(Router)
54
+ # arg is a handler, wrap it in a Router
55
+ @router = Router.new
56
+ @router.root router_or_handler
57
+ else
58
+ # arg is a router
59
+ @router = router_or_handler
60
+ end
61
+ @router.namespace 'system', System.new(@router)
62
+
63
+ @host = opts.delete(:host) || '0.0.0.0'
64
+ @port = opts.delete(:port) || 8999
65
+ @show_errors = opts.delete(:show_errors) || false
66
+ @opts = opts
67
+ end
68
+
69
+ #
70
+ # Starts the server so it can process requests
71
+ #
72
+ def start
73
+ Rack::Server.start(opts.merge(
74
+ :app => self,
75
+ :Host => @host,
76
+ :Port => @port
77
+ ))
78
+ end
79
+
80
+ #
81
+ # Entry point for Rack
82
+ #
83
+ def call(env)
84
+ req = Rack::Request.new(env)
85
+ resp = Rack::Response.new
86
+ return resp.finish if !req.post?
87
+ resp.write process(req.body.read)
88
+ resp.finish
89
+ end
90
+
91
+ def process(content)
92
+ begin
93
+ request = parse_request(content)
94
+ if request.is_a?(Array)
95
+ raise Server::Error::InvalidRequest.new if request.empty?
96
+ response = request.map { |req| handle_request(req) }
97
+ else
98
+ response = handle_request(request)
99
+ end
100
+ rescue Server::Error::ParseError, Server::Error::InvalidRequest => e
101
+ response = error_response(e)
102
+ rescue Server::Error => e
103
+ response = error_response(e, request)
104
+ rescue StandardError, Exception => e
105
+ response = error_response(Server::Error::InternalError.new(e))
106
+ end
107
+
108
+ response.compact! if response.is_a?(Array)
109
+
110
+ return nil if response.nil? || (response.respond_to?(:empty?) && response.empty?)
111
+
112
+ MultiJson.encode(response)
113
+ end
114
+
115
+ def handle_request(request)
116
+ response = nil
117
+ begin
118
+ if !validate_request(request)
119
+ response = error_response(Server::Error::InvalidRequest.new)
120
+ else
121
+ response = create_response(request)
122
+ end
123
+ rescue Server::Error => e
124
+ response = error_response(e, request)
125
+ end
126
+
127
+ response
128
+ end
129
+
130
+ def validate_request(request)
131
+ required_keys = %w(jsonrpc method)
132
+ required_types = {
133
+ 'jsonrpc' => [String],
134
+ 'method' => [String],
135
+ 'params' => [Hash, Array],
136
+ 'id' => [String, Integer, NilClass]
137
+ }
138
+
139
+ return false if !request.is_a?(Hash)
140
+
141
+ required_keys.each do |key|
142
+ return false if !request.has_key?(key)
143
+ end
144
+
145
+ required_types.each do |key, types|
146
+ return false if request.has_key?(key) && !types.any? { |type| request[key].is_a?(type) }
147
+ end
148
+
149
+ return false if request['jsonrpc'] != JSON_RPC_VERSION
150
+
151
+ true
152
+ end
153
+
154
+ def create_response(request)
155
+ method = request['method']
156
+ params = request['params']
157
+ result = dispatch_request(method, params)
158
+
159
+ response = success_response(request, result)
160
+
161
+ # A Notification is a Request object without an "id" member.
162
+ # The Server MUST NOT reply to a Notification, including those
163
+ # that are within a batch request.
164
+ response = nil if !request.has_key?('id')
165
+
166
+ return response
167
+
168
+ rescue Server::Error => e
169
+ raise e
170
+ rescue ArgumentError
171
+ raise Server::Error::InvalidParams.new
172
+ rescue Exception, StandardError => e
173
+ raise Server::Error::ApplicationError.new(e, @show_errors)
174
+ end
175
+
176
+ def dispatch_request(method, params)
177
+ method_name = method.to_s
178
+ handler = @router.handler_for_method(method_name)
179
+ method_name = @router.strip_method_namespace(method_name)
180
+
181
+ if handler.nil? \
182
+ || !handler.class.jimson_exposed_methods.include?(method_name) \
183
+ || !handler.respond_to?(method_name)
184
+ raise Server::Error::MethodNotFound.new(method)
185
+ end
186
+
187
+ if params.nil?
188
+ return handler.send(method_name)
189
+ elsif params.is_a?(Hash)
190
+ return handler.send(method_name, params)
191
+ else
192
+ return handler.send(method_name, *params)
193
+ end
194
+ end
195
+
196
+ def error_response(error, request = nil)
197
+ resp = {
198
+ 'jsonrpc' => JSON_RPC_VERSION,
199
+ 'error' => error.to_h,
200
+ }
201
+ if !!request && request.has_key?('id')
202
+ resp['id'] = request['id']
203
+ else
204
+ resp['id'] = nil
205
+ end
206
+
207
+ resp
208
+ end
209
+
210
+ def success_response(request, result)
211
+ {
212
+ 'jsonrpc' => JSON_RPC_VERSION,
213
+ 'result' => result,
214
+ 'id' => request['id']
215
+ }
216
+ end
217
+
218
+ def parse_request(post)
219
+ data = MultiJson.decode(post)
220
+ rescue
221
+ raise Server::Error::ParseError.new
222
+ end
223
+
224
+ end
225
+ end
@@ -0,0 +1,66 @@
1
+ module Jimson
2
+ class Server
3
+ class Error < StandardError
4
+ attr_accessor :code, :message
5
+
6
+ def initialize(code, message)
7
+ @code = code
8
+ @message = message
9
+ super(message)
10
+ end
11
+
12
+ def to_h
13
+ {
14
+ 'code' => @code,
15
+ 'message' => @message
16
+ }
17
+ end
18
+
19
+ class ParseError < Error
20
+ def initialize
21
+ super(-32700, 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.')
22
+ end
23
+ end
24
+
25
+ class InvalidRequest < Error
26
+ def initialize
27
+ super(-32600, 'The JSON sent is not a valid Request object.')
28
+ end
29
+ end
30
+
31
+ class MethodNotFound < Error
32
+ def initialize(method)
33
+ super(-32601, "Method '#{method}' not found.")
34
+ end
35
+ end
36
+
37
+ class InvalidParams < Error
38
+ def initialize
39
+ super(-32602, 'Invalid method parameter(s).')
40
+ end
41
+ end
42
+
43
+ class InternalError < Error
44
+ def initialize(e)
45
+ super(-32603, "Internal server error: #{e}")
46
+ end
47
+ end
48
+
49
+ class ApplicationError < Error
50
+ def initialize(err, show_error = false)
51
+ msg = "Server application error"
52
+ msg += ': ' + err.message + ' at ' + err.backtrace.first if show_error
53
+ super(-32099, msg)
54
+ end
55
+ end
56
+
57
+ CODES = {
58
+ -32700 => ParseError,
59
+ -32600 => InvalidRequest,
60
+ -32601 => MethodNotFound,
61
+ -32602 => InvalidParams,
62
+ -32603 => InternalError
63
+ }
64
+ end
65
+ end
66
+ end