sr-jimson 0.11.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.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG.rdoc +92 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +17 -0
- data/README.md +29 -0
- data/Rakefile +21 -0
- data/VERSION +1 -0
- data/lib/sr/jimson.rb +10 -0
- data/lib/sr/jimson/blankslate.rb +132 -0
- data/lib/sr/jimson/client.rb +180 -0
- data/lib/sr/jimson/client/error.rb +23 -0
- data/lib/sr/jimson/handler.rb +25 -0
- data/lib/sr/jimson/request.rb +25 -0
- data/lib/sr/jimson/response.rb +30 -0
- data/lib/sr/jimson/router.rb +25 -0
- data/lib/sr/jimson/router/map.rb +75 -0
- data/lib/sr/jimson/server.rb +224 -0
- data/lib/sr/jimson/server/error.rb +66 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/sr/jimson/client_spec.rb +191 -0
- data/spec/sr/jimson/handler_spec.rb +61 -0
- data/spec/sr/jimson/router_spec.rb +75 -0
- data/spec/sr/jimson/server_spec.rb +466 -0
- data/spec/sr/jimson_spec.rb +7 -0
- data/sr-jimson.gemspec +31 -0
- metadata +206 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8a05ed480e1d0fd2b07c91b468b844324b045825
|
4
|
+
data.tar.gz: 544159b0cf98683f4e33525723e0fc5c24c3d8a0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cf2c8ad81c0bebca24ebda12b593b49cbd26e9e48dbdcb3bd92191b9baa3b36f61f5a32b3ba8fbd4a797ef77b5660133182b3f2926f1cb69d278b599afac016f
|
7
|
+
data.tar.gz: 4b7c7caaadcd51da3df4e92d431069d0971c45edb4579fdaaa6d5dc9e57681ad6ccc5d3e6b43321b23f37ab815022f4b38c3d009476408a30c8050eb4928e731
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.rdoc
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
== 0.10.0 / 2013-06-28
|
2
|
+
|
3
|
+
* Minor enhancements
|
4
|
+
|
5
|
+
* Update dependency versions
|
6
|
+
|
7
|
+
== 0.9.1 / 2012-09-18
|
8
|
+
|
9
|
+
* Bug fixes
|
10
|
+
|
11
|
+
* Allow opts to be passed to Server.with_routes
|
12
|
+
|
13
|
+
== 0.9.0 / 2012-08-22
|
14
|
+
|
15
|
+
* Minor enhancements
|
16
|
+
|
17
|
+
* Add show_errors option to server, which will cause application errors to include the error name and the first line of the backtrace
|
18
|
+
|
19
|
+
== 0.8.0 / 2012-08-17
|
20
|
+
|
21
|
+
* Major enhancements
|
22
|
+
|
23
|
+
* Add namespaced method calls to client (e.g. 'client[:foo].sum(1,2,3) # calls foo.sum')
|
24
|
+
* Add Server.with_routes to quickly created a routed server
|
25
|
+
|
26
|
+
== 0.7.1 / 2012-08-16
|
27
|
+
|
28
|
+
* Bug fixes
|
29
|
+
|
30
|
+
* Fix handling of array params in client, which were erroneously being flattened
|
31
|
+
|
32
|
+
== 0.7.0 / 2012-04-13
|
33
|
+
|
34
|
+
* Major enhancements
|
35
|
+
|
36
|
+
* Add namespaced routing
|
37
|
+
|
38
|
+
* Bug fixes
|
39
|
+
|
40
|
+
* Fix deprecation warning about RDoc task in Rakefile
|
41
|
+
|
42
|
+
== 0.6.0 / 2012-03-14
|
43
|
+
|
44
|
+
* Minor enhancements
|
45
|
+
|
46
|
+
* Add ability to pass options to Rack and RestClient
|
47
|
+
|
48
|
+
== 0.5.0 / 2012-03-06
|
49
|
+
|
50
|
+
* Major enhancements
|
51
|
+
|
52
|
+
* Switch to MultiJson from json gem
|
53
|
+
|
54
|
+
* Bug fixes
|
55
|
+
|
56
|
+
* Allow BigNum in 'id' field of request and response
|
57
|
+
|
58
|
+
== 0.3.1 / 2011-08-11
|
59
|
+
|
60
|
+
* Minor enhancements
|
61
|
+
|
62
|
+
* Refactor the way the server is intantiated/started to work better with config.ru
|
63
|
+
|
64
|
+
== 0.3.0 / 2011-08-11
|
65
|
+
|
66
|
+
* Major enhancements
|
67
|
+
|
68
|
+
* Replace eventmachine-httpserver with rack for more cross-platform goodness
|
69
|
+
|
70
|
+
== 0.2.3 / 2011-08-01
|
71
|
+
|
72
|
+
* Bug fixes
|
73
|
+
|
74
|
+
* Fix argument error in client error handling
|
75
|
+
|
76
|
+
== 0.2.2 / 2011-07-28
|
77
|
+
|
78
|
+
* Bug fixes
|
79
|
+
|
80
|
+
* Fix invalid local variable error in client error handling
|
81
|
+
|
82
|
+
== 0.2.1 / 2011-07-27
|
83
|
+
|
84
|
+
* Bug fixes
|
85
|
+
|
86
|
+
* Fix error in client handling some errors caused by errant 'new' keyword
|
87
|
+
|
88
|
+
== 0.2.0 / 2011-07-20
|
89
|
+
|
90
|
+
* Major enhancements
|
91
|
+
|
92
|
+
* Replace patron with rest-client for JRuby compatibility in the client
|
data/Gemfile
ADDED
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'
|
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'
|
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,21 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
require 'rdoc/task'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new(:rspec)
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
8
|
+
ENV['COVERAGE'] = 'true'
|
9
|
+
Rake::Task["rspec"].execute
|
10
|
+
end
|
11
|
+
|
12
|
+
Rake::RDocTask.new do |rdoc|
|
13
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
14
|
+
|
15
|
+
rdoc.rdoc_dir = 'rdoc'
|
16
|
+
rdoc.title = "jimson #{version}"
|
17
|
+
rdoc.rdoc_files.include('README*')
|
18
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :rspec
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.10.0
|
data/lib/sr/jimson.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
|
3
|
+
# All rights reserved.
|
4
|
+
|
5
|
+
# Permission is granted for use, copying, modification, distribution,
|
6
|
+
# and distribution of modified versions of this work as long as the
|
7
|
+
# above copyright notice is included.
|
8
|
+
#++
|
9
|
+
|
10
|
+
class String
|
11
|
+
if instance_methods.first.is_a?(Symbol)
|
12
|
+
def _blankslate_as_name
|
13
|
+
to_sym
|
14
|
+
end
|
15
|
+
else
|
16
|
+
def _blankslate_as_name
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Symbol
|
23
|
+
if instance_methods.first.is_a?(Symbol)
|
24
|
+
def _blankslate_as_name
|
25
|
+
self
|
26
|
+
end
|
27
|
+
else
|
28
|
+
def _blankslate_as_name
|
29
|
+
to_s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
######################################################################
|
35
|
+
# BlankSlate provides an abstract base class with no predefined
|
36
|
+
# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
|
37
|
+
# BlankSlate is useful as a base class when writing classes that
|
38
|
+
# depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
|
39
|
+
#
|
40
|
+
class BlankSlate
|
41
|
+
class << self
|
42
|
+
|
43
|
+
# Hide the method named +name+ in the BlankSlate class. Don't
|
44
|
+
# hide +instance_eval+ or any method beginning with "__".
|
45
|
+
def hide(name)
|
46
|
+
if instance_methods.include?(name._blankslate_as_name) and
|
47
|
+
name !~ /^(__|(instance_eval|object_id)$)/
|
48
|
+
@hidden_methods ||= {}
|
49
|
+
@hidden_methods[name.to_sym] = instance_method(name)
|
50
|
+
undef_method name
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_hidden_method(name)
|
55
|
+
@hidden_methods ||= {}
|
56
|
+
@hidden_methods[name] || superclass.find_hidden_method(name)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Redefine a previously hidden method so that it may be called on a blank
|
60
|
+
# slate object.
|
61
|
+
def reveal(name)
|
62
|
+
hidden_method = find_hidden_method(name)
|
63
|
+
fail "Don't know how to reveal method '#{name}'" unless hidden_method
|
64
|
+
define_method(name, hidden_method)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
instance_methods.each { |m| hide(m) }
|
69
|
+
end
|
70
|
+
|
71
|
+
######################################################################
|
72
|
+
# Since Ruby is very dynamic, methods added to the ancestors of
|
73
|
+
# BlankSlate <em>after BlankSlate is defined</em> will show up in the
|
74
|
+
# list of available BlankSlate methods. We handle this by defining a
|
75
|
+
# hook in the Object and Kernel classes that will hide any method
|
76
|
+
# defined after BlankSlate has been loaded.
|
77
|
+
#
|
78
|
+
module Kernel
|
79
|
+
class << self
|
80
|
+
alias_method :blank_slate_method_added, :method_added
|
81
|
+
|
82
|
+
# Detect method additions to Kernel and remove them in the
|
83
|
+
# BlankSlate class.
|
84
|
+
def method_added(name)
|
85
|
+
result = blank_slate_method_added(name)
|
86
|
+
return result if self != Kernel
|
87
|
+
BlankSlate.hide(name)
|
88
|
+
result
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
######################################################################
|
94
|
+
# Same as above, except in Object.
|
95
|
+
#
|
96
|
+
class Object
|
97
|
+
class << self
|
98
|
+
alias_method :blank_slate_method_added, :method_added
|
99
|
+
|
100
|
+
# Detect method additions to Object and remove them in the
|
101
|
+
# BlankSlate class.
|
102
|
+
def method_added(name)
|
103
|
+
result = blank_slate_method_added(name)
|
104
|
+
return result if self != Object
|
105
|
+
BlankSlate.hide(name)
|
106
|
+
result
|
107
|
+
end
|
108
|
+
|
109
|
+
def find_hidden_method(name)
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
######################################################################
|
116
|
+
# Also, modules included into Object need to be scanned and have their
|
117
|
+
# instance methods removed from blank slate. In theory, modules
|
118
|
+
# included into Kernel would have to be removed as well, but a
|
119
|
+
# "feature" of Ruby prevents late includes into modules from being
|
120
|
+
# exposed in the first place.
|
121
|
+
#
|
122
|
+
class Module
|
123
|
+
alias blankslate_original_append_features append_features
|
124
|
+
def append_features(mod)
|
125
|
+
result = blankslate_original_append_features(mod)
|
126
|
+
return result if mod != Object
|
127
|
+
instance_methods.each do |name|
|
128
|
+
BlankSlate.hide(name)
|
129
|
+
end
|
130
|
+
result
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'sr/jimson/blankslate'
|
4
|
+
require 'sr/jimson/request'
|
5
|
+
require 'sr/jimson/response'
|
6
|
+
|
7
|
+
module Sr::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 = Sr::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 'sr/jimson/client/error'
|