jimson-temp 0.9.2
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/CHANGELOG.rdoc +86 -0
- data/LICENSE.txt +17 -0
- data/README.md +29 -0
- data/Rakefile +31 -0
- data/VERSION +1 -0
- data/lib/jimson-temp.rb +8 -0
- data/lib/jimson/client.rb +180 -0
- data/lib/jimson/client/error.rb +23 -0
- data/lib/jimson/handler.rb +25 -0
- data/lib/jimson/request.rb +25 -0
- data/lib/jimson/response.rb +30 -0
- data/lib/jimson/router.rb +24 -0
- data/lib/jimson/router/map.rb +75 -0
- data/lib/jimson/server.rb +225 -0
- data/lib/jimson/server/error.rb +66 -0
- data/spec/client_spec.rb +191 -0
- data/spec/handler_spec.rb +62 -0
- data/spec/router_spec.rb +75 -0
- data/spec/server_spec.rb +466 -0
- data/spec/spec_helper.rb +7 -0
- metadata +134 -0
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
== 0.9.1 / 2012-09-18
|
2
|
+
|
3
|
+
* Bug fixes
|
4
|
+
|
5
|
+
* Allow opts to be passed to Server.with_routes
|
6
|
+
|
7
|
+
== 0.9.0 / 2012-08-22
|
8
|
+
|
9
|
+
* Minor enhancements
|
10
|
+
|
11
|
+
* Add show_errors option to server, which will cause application errors to include the error name and the first line of the backtrace
|
12
|
+
|
13
|
+
== 0.8.0 / 2012-08-17
|
14
|
+
|
15
|
+
* Major enhancements
|
16
|
+
|
17
|
+
* Add namespaced method calls to client (e.g. 'client[:foo].sum(1,2,3) # calls foo.sum')
|
18
|
+
* Add Server.with_routes to quickly created a routed server
|
19
|
+
|
20
|
+
== 0.7.1 / 2012-08-16
|
21
|
+
|
22
|
+
* Bug fixes
|
23
|
+
|
24
|
+
* Fix handling of array params in client, which were erroneously being flattened
|
25
|
+
|
26
|
+
== 0.7.0 / 2012-04-13
|
27
|
+
|
28
|
+
* Major enhancements
|
29
|
+
|
30
|
+
* Add namespaced routing
|
31
|
+
|
32
|
+
* Bug fixes
|
33
|
+
|
34
|
+
* Fix deprecation warning about RDoc task in Rakefile
|
35
|
+
|
36
|
+
== 0.6.0 / 2012-03-14
|
37
|
+
|
38
|
+
* Minor enhancements
|
39
|
+
|
40
|
+
* Add ability to pass options to Rack and RestClient
|
41
|
+
|
42
|
+
== 0.5.0 / 2012-03-06
|
43
|
+
|
44
|
+
* Major enhancements
|
45
|
+
|
46
|
+
* Switch to MultiJson from json gem
|
47
|
+
|
48
|
+
* Bug fixes
|
49
|
+
|
50
|
+
* Allow BigNum in 'id' field of request and response
|
51
|
+
|
52
|
+
== 0.3.1 / 2011-08-11
|
53
|
+
|
54
|
+
* Minor enhancements
|
55
|
+
|
56
|
+
* Refactor the way the server is intantiated/started to work better with config.ru
|
57
|
+
|
58
|
+
== 0.3.0 / 2011-08-11
|
59
|
+
|
60
|
+
* Major enhancements
|
61
|
+
|
62
|
+
* Replace eventmachine-httpserver with rack for more cross-platform goodness
|
63
|
+
|
64
|
+
== 0.2.3 / 2011-08-01
|
65
|
+
|
66
|
+
* Bug fixes
|
67
|
+
|
68
|
+
* Fix argument error in client error handling
|
69
|
+
|
70
|
+
== 0.2.2 / 2011-07-28
|
71
|
+
|
72
|
+
* Bug fixes
|
73
|
+
|
74
|
+
* Fix invalid local variable error in client error handling
|
75
|
+
|
76
|
+
== 0.2.1 / 2011-07-27
|
77
|
+
|
78
|
+
* Bug fixes
|
79
|
+
|
80
|
+
* Fix error in client handling some errors caused by errant 'new' keyword
|
81
|
+
|
82
|
+
== 0.2.0 / 2011-07-20
|
83
|
+
|
84
|
+
* Major enhancements
|
85
|
+
|
86
|
+
* Replace patron with rest-client for JRuby compatibility in the client
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
2
|
+
of this software and associated documentation files (the "Software"), to deal
|
3
|
+
in the Software without restriction, including without limitation the rights
|
4
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
5
|
+
copies of the Software, and to permit persons to whom the Software is
|
6
|
+
furnished to do so, subject to the following conditions:
|
7
|
+
|
8
|
+
The above copyright notice and this permission notice shall be included in
|
9
|
+
all copies or substantial portions of the Software.
|
10
|
+
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
12
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
13
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
14
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
15
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
16
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
17
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Jimson
|
2
|
+
### JSON-RPC 2.0 Client and Server for Ruby
|
3
|
+

|
4
|
+
|
5
|
+
## Client: Quick Start
|
6
|
+
require 'jimson-temp'
|
7
|
+
client = Jimson::Client.new("http://www.example.com:8999") # the URL for the JSON-RPC 2.0 server to connect to
|
8
|
+
result = client.sum(1,2) # call the 'sum' method on the RPC server and save the result '3'
|
9
|
+
|
10
|
+
## Server: Quick Start
|
11
|
+
require 'jimson-temp'
|
12
|
+
|
13
|
+
class MyHandler
|
14
|
+
extend Jimson::Handler
|
15
|
+
|
16
|
+
def sum(a,b)
|
17
|
+
a + b
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
server = Jimson::Server.new(MyHandler.new)
|
22
|
+
server.start # serve with webrick on http://0.0.0.0:8999/
|
23
|
+
|
24
|
+
## JSON Engine
|
25
|
+
Jimson uses multi\_json, so you can load the JSON library of your choice in your application and Jimson will use it automatically.
|
26
|
+
|
27
|
+
For example, require the 'json' gem in your application:
|
28
|
+
require 'json'
|
29
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
gem 'rubygems-tasks', '~> 0.2'
|
6
|
+
require 'rubygems/tasks'
|
7
|
+
|
8
|
+
Gem::Tasks.new
|
9
|
+
|
10
|
+
desc "Run all specs"
|
11
|
+
RSpec::Core::RakeTask.new(:rspec) do |spec|
|
12
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
16
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
17
|
+
spec.rcov = true
|
18
|
+
end
|
19
|
+
|
20
|
+
task :default => :rspec
|
21
|
+
|
22
|
+
require 'rdoc/task'
|
23
|
+
|
24
|
+
Rake::RDocTask.new do |rdoc|
|
25
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
26
|
+
|
27
|
+
rdoc.rdoc_dir = 'rdoc'
|
28
|
+
rdoc.title = "jimson #{version}"
|
29
|
+
rdoc.rdoc_files.include('README*')
|
30
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
31
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.2
|
data/lib/jimson-temp.rb
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'blankslate'
|
2
|
+
require 'multi_json'
|
3
|
+
require 'rest-client'
|
4
|
+
require 'jimson/request'
|
5
|
+
require 'jimson/response'
|
6
|
+
|
7
|
+
module Jimson
|
8
|
+
class ClientHelper
|
9
|
+
JSON_RPC_VERSION = '2.0'
|
10
|
+
|
11
|
+
def self.make_id
|
12
|
+
rand(10**12)
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(url, opts = {}, namespace = nil)
|
16
|
+
@url = url
|
17
|
+
URI.parse(@url) # for the sake of validating the url
|
18
|
+
@batch = []
|
19
|
+
@opts = opts
|
20
|
+
@namespace = namespace
|
21
|
+
@opts[:content_type] = 'application/json'
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_call(sym, args)
|
25
|
+
resp = send_single_request(sym.to_s, args)
|
26
|
+
|
27
|
+
begin
|
28
|
+
data = MultiJson.decode(resp)
|
29
|
+
rescue
|
30
|
+
raise Client::Error::InvalidJSON.new(resp)
|
31
|
+
end
|
32
|
+
|
33
|
+
return process_single_response(data)
|
34
|
+
|
35
|
+
rescue Exception, StandardError => e
|
36
|
+
e.extend(Client::Error) unless e.is_a?(Client::Error)
|
37
|
+
raise e
|
38
|
+
end
|
39
|
+
|
40
|
+
def send_single_request(method, args)
|
41
|
+
namespaced_method = @namespace.nil? ? method : "#@namespace#{method}"
|
42
|
+
post_data = MultiJson.encode({
|
43
|
+
'jsonrpc' => JSON_RPC_VERSION,
|
44
|
+
'method' => namespaced_method,
|
45
|
+
'params' => args,
|
46
|
+
'id' => self.class.make_id
|
47
|
+
})
|
48
|
+
resp = RestClient.post(@url, post_data, @opts)
|
49
|
+
if resp.nil? || resp.body.nil? || resp.body.empty?
|
50
|
+
raise Client::Error::InvalidResponse.new
|
51
|
+
end
|
52
|
+
|
53
|
+
return resp.body
|
54
|
+
end
|
55
|
+
|
56
|
+
def send_batch_request(batch)
|
57
|
+
post_data = MultiJson.encode(batch)
|
58
|
+
resp = RestClient.post(@url, post_data, @opts)
|
59
|
+
if resp.nil? || resp.body.nil? || resp.body.empty?
|
60
|
+
raise Client::Error::InvalidResponse.new
|
61
|
+
end
|
62
|
+
|
63
|
+
return resp.body
|
64
|
+
end
|
65
|
+
|
66
|
+
def process_batch_response(responses)
|
67
|
+
responses.each do |resp|
|
68
|
+
saved_response = @batch.map { |r| r[1] }.select { |r| r.id == resp['id'] }.first
|
69
|
+
raise Client::Error::InvalidResponse.new if saved_response.nil?
|
70
|
+
saved_response.populate!(resp)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def process_single_response(data)
|
75
|
+
raise Client::Error::InvalidResponse.new if !valid_response?(data)
|
76
|
+
|
77
|
+
if !!data['error']
|
78
|
+
code = data['error']['code']
|
79
|
+
msg = data['error']['message']
|
80
|
+
raise Client::Error::ServerError.new(code, msg)
|
81
|
+
end
|
82
|
+
|
83
|
+
return data['result']
|
84
|
+
end
|
85
|
+
|
86
|
+
def valid_response?(data)
|
87
|
+
return false if !data.is_a?(Hash)
|
88
|
+
|
89
|
+
return false if data['jsonrpc'] != JSON_RPC_VERSION
|
90
|
+
|
91
|
+
return false if !data.has_key?('id')
|
92
|
+
|
93
|
+
return false if data.has_key?('error') && data.has_key?('result')
|
94
|
+
|
95
|
+
if data.has_key?('error')
|
96
|
+
if !data['error'].is_a?(Hash) || !data['error'].has_key?('code') || !data['error'].has_key?('message')
|
97
|
+
return false
|
98
|
+
end
|
99
|
+
|
100
|
+
if !data['error']['code'].is_a?(Fixnum) || !data['error']['message'].is_a?(String)
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
return true
|
106
|
+
|
107
|
+
rescue
|
108
|
+
return false
|
109
|
+
end
|
110
|
+
|
111
|
+
def push_batch_request(request)
|
112
|
+
request.id = self.class.make_id
|
113
|
+
response = Response.new(request.id)
|
114
|
+
@batch << [request, response]
|
115
|
+
return response
|
116
|
+
end
|
117
|
+
|
118
|
+
def send_batch
|
119
|
+
batch = @batch.map(&:first) # get the requests
|
120
|
+
response = send_batch_request(batch)
|
121
|
+
|
122
|
+
begin
|
123
|
+
responses = MultiJson.decode(response)
|
124
|
+
rescue
|
125
|
+
raise Client::Error::InvalidJSON.new(json)
|
126
|
+
end
|
127
|
+
|
128
|
+
process_batch_response(responses)
|
129
|
+
@batch = []
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
class BatchClient < BlankSlate
|
135
|
+
|
136
|
+
def initialize(helper)
|
137
|
+
@helper = helper
|
138
|
+
end
|
139
|
+
|
140
|
+
def method_missing(sym, *args, &block)
|
141
|
+
request = Jimson::Request.new(sym.to_s, args)
|
142
|
+
@helper.push_batch_request(request)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
class Client < BlankSlate
|
148
|
+
reveal :instance_variable_get
|
149
|
+
reveal :inspect
|
150
|
+
reveal :to_s
|
151
|
+
|
152
|
+
def self.batch(client)
|
153
|
+
helper = client.instance_variable_get(:@helper)
|
154
|
+
batch_client = BatchClient.new(helper)
|
155
|
+
yield batch_client
|
156
|
+
helper.send_batch
|
157
|
+
end
|
158
|
+
|
159
|
+
def initialize(url, opts = {}, namespace = nil)
|
160
|
+
@url, @opts, @namespace = url, opts, namespace
|
161
|
+
@helper = ClientHelper.new(url, opts, namespace)
|
162
|
+
end
|
163
|
+
|
164
|
+
def method_missing(sym, *args, &block)
|
165
|
+
@helper.process_call(sym, args)
|
166
|
+
end
|
167
|
+
|
168
|
+
def [](method, *args)
|
169
|
+
if method.is_a?(Symbol)
|
170
|
+
# namespace requested
|
171
|
+
new_ns = @namespace.nil? ? "#{method}." : "#@namespace#{method}."
|
172
|
+
return Client.new(@url, @opts, new_ns)
|
173
|
+
end
|
174
|
+
@helper.process_call(method, args)
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
require 'jimson/client/error'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Jimson
|
2
|
+
class Client
|
3
|
+
module Error
|
4
|
+
class InvalidResponse < StandardError
|
5
|
+
def initialize()
|
6
|
+
super('Invalid or empty response from server.')
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class InvalidJSON < StandardError
|
11
|
+
def initialize(json)
|
12
|
+
super("Couldn't parse JSON string received from server:\n#{json}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ServerError < StandardError
|
17
|
+
def initialize(code, message)
|
18
|
+
super("Server error #{code}: #{message}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Jimson
|
2
|
+
module Handler
|
3
|
+
|
4
|
+
def jimson_default_methods
|
5
|
+
self.instance_methods.map(&:to_s) - Object.methods.map(&:to_s)
|
6
|
+
end
|
7
|
+
|
8
|
+
def jimson_expose(*methods)
|
9
|
+
@jimson_exposed_methods ||= []
|
10
|
+
@jimson_exposed_methods += methods.map(&:to_s)
|
11
|
+
end
|
12
|
+
|
13
|
+
def jimson_exclude(*methods)
|
14
|
+
@jimson_excluded_methods ||= []
|
15
|
+
@jimson_excluded_methods += methods.map(&:to_s)
|
16
|
+
end
|
17
|
+
|
18
|
+
def jimson_exposed_methods
|
19
|
+
@jimson_exposed_methods ||= []
|
20
|
+
@jimson_excluded_methods ||= []
|
21
|
+
(jimson_default_methods - @jimson_excluded_methods + @jimson_exposed_methods).sort
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -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
|