jsonrpc-server 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -1,14 +1,23 @@
1
- == About
1
+ About
2
+ =====
2
3
 
3
- This is an implementation of {JSON RPC 2.0}[link:http://groups.google.com/group/json-rpc/web/json-rpc-2-0?pli=1] server
4
+ This is an implementation of JSON RPC 2.0 server (http://groups.google.com/group/json-rpc/web/json-rpc-2-0?pli=1)
4
5
 
5
- == Using
6
+ Using
7
+ =====
6
8
 
7
- To make your rails controller JSON RPC 2.0 callable, make next include:
9
+ To make your rails controller JSON RPC 2.0 callable, do next
8
10
 
9
11
  class TestController < ActionController::Base
10
- include JSONServer
12
+ include JSONServer # adds json-rpc 2.0 functionality
11
13
 
14
+ def index
15
+ # next will redirect post contents to json-rpc dispatcher
16
+ # and output resut as string
17
+ render :text => dispatch_json_2(request.body.read)
18
+ end
19
+
20
+ # function which will become remotely callable
12
21
  def summ(left, right)
13
22
  return left+right
14
23
  end
@@ -19,17 +28,15 @@ In your config/routing.rb you will have to add
19
28
  post 'json' => 'test#index'
20
29
 
21
30
  JSONServer will automaticaly check if posted json is valid JSON-RPC 2.0 request
22
- and and fire called method. if method is not found - propper error will be sent.
31
+ and fire called method. if method is not found - propper error will be sent.
23
32
 
24
33
  Batch request are handled.
34
+ Notices are handled.
35
+ Test taken from examples on http://groups.google.com/group/json-rpc/web/json-rpc-2-0?pli=1
25
36
 
26
37
  == Errors
27
38
 
28
- You can raise standart errors, or JSONServer::Error with parameters :code,
39
+ You can raise standart errors, or JSONRPCError with parameters :code,
29
40
  :message, :data, which will be set for correspoding fields
30
41
  :code defaults to -30000 if JSONServer::Error and to -32000 otherwise
31
42
  :message defaults to "Runtime Error"
32
-
33
- == TODO list
34
-
35
- write tests
@@ -0,0 +1,36 @@
1
+ require 'json'
2
+ require 'jsonrpc-server/defs'
3
+ require 'jsonrpc-server/parser'
4
+
5
+ # JSONRPCServer - container, and router for json-rpc 2.0 requests
6
+ #
7
+ # Procedure handler classe, module or object is stored inside the class
8
+ #
9
+ # Handler can specify own procedure prefix to create namespace of allowed
10
+ # to call functions. say, one does not want someone to call to_s inside class
11
+ # which encrypts user data with secret key.
12
+
13
+ class JSONRPCServer
14
+
15
+ attr_accessor :handler, :prefix
16
+
17
+ def initialize in_handler, in_prefix = ""
18
+ self.handler = in_handler
19
+ self.prefix = in_prefix
20
+ end
21
+
22
+ def dispatch request
23
+ # here request specific errors are handled
24
+ begin
25
+ # no wraping, because single_request dispatcher does it
26
+ result = core_dispatch request
27
+ rescue JSON::ParserError
28
+ # called only on parsing error. all other errors
29
+ # have to be handled on per-request basis
30
+ result = wrap_error @@ParseError
31
+ end
32
+ return '' if result==nil || result.empty?
33
+ return result.to_json
34
+ end
35
+
36
+ end
@@ -0,0 +1,48 @@
1
+ # Different class difenitions
2
+ # Standart errors
3
+ # Constants
4
+
5
+ class JSONRPCServer
6
+
7
+ @@Version = "2.0".freeze
8
+
9
+ class Error < StandardError
10
+
11
+ attr_reader :data
12
+
13
+ def initialize *args
14
+ @data = {:code => -30000, :message => "Runtime Error"}
15
+
16
+ return unless args.length > 0
17
+
18
+ # if initialized by object
19
+ if (args.length == 1) and (args[0].is_a? Hash) then
20
+ @data.update(args[0])
21
+ return self
22
+ end
23
+
24
+ # per argument: code, message, data
25
+ @data[:code] = args[0] if args[0].is_a? Fixnum
26
+ @data[:message] = args[1] if args[1].is_a? String
27
+ @data[:data] = args[2] if args[2].is_a? Hash
28
+ end
29
+
30
+ def Error.to_e err
31
+ return err if err.is_a? Error
32
+ Error.new -32000, $!.message, $!.to_s
33
+ end
34
+
35
+ end
36
+
37
+ class Result
38
+ attr_reader :data
39
+ def initialize obj = nil
40
+ @data = obj
41
+ end
42
+ end
43
+
44
+ # Errors
45
+ @@MethodNotFound = Error.new(-32601, "Method not found.").freeze
46
+ @@ParseError = Error.new(-32700, "Parse error.").freeze
47
+ @@InvalidRequest = Error.new(-32600, "Invalid Request.").freeze
48
+ end
@@ -0,0 +1,69 @@
1
+ require 'jsonrpc-server/defs'
2
+
3
+ class JSONRPCServer
4
+
5
+ private
6
+
7
+ def core_dispatch request
8
+ data = JSON.parse request
9
+ return parse_batch data if data.kind_of? Array
10
+ return parse_single data
11
+ end
12
+
13
+ def wrap data, id = nil
14
+ data.update({ :jsonrpc => @@Version, :id => id })
15
+ end
16
+
17
+ def wrap_error error, id = nil
18
+ wrap({ :error => error.data }, id)
19
+ end
20
+
21
+ def wrap_result result, id = nil
22
+ wrap({ :result => result }, id)
23
+ end
24
+
25
+ def parse_batch data
26
+ # batch can not be empty
27
+ return wrap_error @@InvalidRequest if data.length == 0
28
+ res = data.collect{ |single|
29
+ parse_single single
30
+ }
31
+ res.delete(nil)
32
+ res
33
+ end
34
+
35
+ def parse_single data
36
+ current_id = nil
37
+ result = catch :result do
38
+ begin
39
+ raise @@InvalidRequest unless data.kind_of?(Hash)
40
+ current_id = data["id"] unless data["id"] == nil
41
+ check_request data
42
+ method_name = self.prefix + data["method"]
43
+ raise @@MethodNotFound unless handler.respond_to? method_name
44
+ if data["params"].kind_of? Array
45
+ arr = data["params"]
46
+ elsif
47
+ arr = handler.method(method_name).parameters.collect { |pd|
48
+ data["params"][pd[1].to_s]
49
+ }
50
+ end
51
+ result = handler.send(method_name, *arr)
52
+ throw(:result,
53
+ wrap_result(result, current_id)) unless current_id == nil
54
+ throw(:result, nil)
55
+ rescue
56
+ throw :result, wrap_error(Error.to_e($!), current_id)
57
+ end
58
+ end
59
+ result
60
+ end
61
+
62
+ def check_request data
63
+ return if data["jsonrpc"] == @@Version &&
64
+ data["method"].kind_of?(String) &&
65
+ data["method"] != ""
66
+ raise @@InvalidRequest
67
+ end
68
+
69
+ end
@@ -0,0 +1,73 @@
1
+ require 'test/unit'
2
+ require 'jsonrpc-server'
3
+
4
+ require './test/no_namespace'
5
+ require './test/rpc_class'
6
+
7
+ class All < Test::Unit::TestCase
8
+
9
+ @@server = JSONRPCServer.new NoNamespace.new
10
+ @@prefixed_server = JSONRPCServer.new RPCClass.new, "rpc_"
11
+
12
+ def test_from_file
13
+ file = YAML.load(File.open("test/cases.yml"))
14
+ file["tests"].each { |test|
15
+ result = @@server.dispatch test["request"]
16
+ expected_res = test["response"]
17
+ assert_equal(JSON.parse(expected_res),
18
+ JSON.parse(result), test["id"].to_s+" :: "+test["name"])
19
+ }
20
+ end
21
+
22
+ def test_notification
23
+ result = @@server.dispatch '{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}'
24
+ assert_equal('', result, "update_method")
25
+ assert_equal([1,2,3,4,5], @@server.handler.arr, "update_method")
26
+
27
+ @@server.handler.foobar_called = false
28
+ result = @@server.dispatch '{"jsonrpc": "2.0", "method": "foobar"}'
29
+ assert_equal('', result, "foobar_method")
30
+ assert_equal(true, @@server.handler.foobar_called, "foobar_method")
31
+ end
32
+
33
+ def test_call_batch
34
+ result = @@server.dispatch '[
35
+ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
36
+ {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
37
+ {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
38
+ {"foo": "boo"},
39
+ {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
40
+ {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
41
+ ]'
42
+ expected_res = '[
43
+ {"jsonrpc": "2.0", "result": 7, "id": "1"},
44
+ {"jsonrpc": "2.0", "result": 19, "id": "2"},
45
+ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null},
46
+ {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found."}, "id": "5"},
47
+ {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"}
48
+ ]'
49
+ assert_equal(JSON.parse(expected_res),
50
+ JSON.parse(result))
51
+ assert_equal(@@server.handler.notify_hello_data, 7)
52
+ end
53
+
54
+ def test_call_batch_all_notifications
55
+ @@server.handler.notify_sum_data = nil
56
+ @@server.handler.notify_hello_data = nil
57
+ result = @@server.dispatch '[
58
+ {"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
59
+ {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
60
+ ]'
61
+ assert_equal("", result);
62
+ assert_equal([1,2,4], @@server.handler.notify_sum_data);
63
+ assert_equal(7, @@server.handler.notify_hello_data);
64
+ end
65
+
66
+ def test_simple_prefix_request
67
+ result = @@prefixed_server.dispatch '{"jsonrpc": "2.0", "method": "multiply", "params": [6, 8], "id": 1}'
68
+ expected_res = '{"jsonrpc": "2.0", "result": 48, "id": 1}'
69
+ assert_equal(JSON.parse(expected_res),
70
+ JSON.parse(result))
71
+ end
72
+
73
+ end
@@ -0,0 +1,64 @@
1
+ tests:
2
+ - id: 1
3
+ name: 'simple request'
4
+ request: '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}'
5
+ response: '{"jsonrpc": "2.0", "result": 19, "id": 1}'
6
+
7
+ - id: 2
8
+ name: 'simple request'
9
+ request: '{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 1}'
10
+ response: '{"jsonrpc": "2.0", "result": -19, "id": 1}'
11
+
12
+ - id: 3
13
+ name: 'named request'
14
+ request: '{"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 1}'
15
+ response: '{"jsonrpc": "2.0", "result": 19, "id": 1}'
16
+
17
+ - id: 4
18
+ name: 'named request'
19
+ request: '{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 23, "subtrahend": 42}, "id": 1}'
20
+ response: '{"jsonrpc": "2.0", "result": -19, "id": 1}'
21
+
22
+ - id: 5
23
+ name: 'non_existing'
24
+ request: '{"jsonrpc": "2.0", "method": "foobar1", "id": "1"}'
25
+ response: '{"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found."}, "id": "1"}'
26
+
27
+ - id: 6
28
+ name: 'parse_error'
29
+ request: '{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]'
30
+ response: '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error."}, "id": null}'
31
+
32
+ - id: 7
33
+ name: 'invalid_request_object'
34
+ request: '{"jsonrpc": "2.0", "method": 1, "params": "bar"}'
35
+ response: '{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null}'
36
+
37
+ - id: 8
38
+ name: 'call_batch_invalid_json'
39
+ request: '[ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},{"jsonrpc": "2.0", "method" ]'
40
+ response: '{"jsonrpc": "2.0", "error": {"code": -32700, "message": "Parse error."}, "id": null}'
41
+
42
+ - id: 9
43
+ name: 'empty_batch'
44
+ request: '[]'
45
+ response: '{"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null}'
46
+
47
+ - id: 10
48
+ name: 'invalid_batch_not_empty'
49
+ request: '[1]'
50
+ response: '[
51
+ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null}
52
+ ]'
53
+
54
+ - id: 11
55
+ name: 'invalid_batch'
56
+ request: '[1,2,3]'
57
+ response: '[
58
+ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null},
59
+ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null},
60
+ {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request."}, "id": null}
61
+ ]'
62
+
63
+
64
+
@@ -0,0 +1,35 @@
1
+ # Example of RPC handler
2
+
3
+ class NoNamespace
4
+
5
+ attr_accessor :notify_sum_data, :arr, :notify_hello_data, :foobar_called
6
+
7
+ def sum left, middle, right
8
+ return left + middle + right
9
+ end
10
+
11
+ def get_data
12
+ return ["hello", 5]
13
+ end
14
+
15
+ def subtract minuend, subtrahend
16
+ return minuend - subtrahend
17
+ end
18
+
19
+ def notify_sum *arr
20
+ @notify_sum_data = arr
21
+ end
22
+
23
+ def update *arr
24
+ @arr = arr
25
+ end
26
+
27
+ def notify_hello hello
28
+ @notify_hello_data = hello
29
+ end
30
+
31
+ def foobar
32
+ @foobar_called = true
33
+ end
34
+ end
35
+
@@ -0,0 +1,5 @@
1
+ class RPCClass
2
+ def rpc_multiply left, right
3
+ return left * right
4
+ end
5
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
- - 2
7
+ - 3
8
8
  - 0
9
- version: 0.2.0
9
+ version: 0.3.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Dima Levchenko
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2011-05-31 00:00:00 +03:00
17
+ date: 2011-06-07 00:00:00 +03:00
18
18
  default_executable:
19
19
  dependencies: []
20
20
 
@@ -27,10 +27,16 @@ extensions: []
27
27
  extra_rdoc_files: []
28
28
 
29
29
  files:
30
- - lib/json_server.rb
30
+ - lib/jsonrpc-server.rb
31
+ - lib/jsonrpc-server/parser.rb
32
+ - lib/jsonrpc-server/defs.rb
33
+ - test/cases.yml
34
+ - test/rpc_class.rb
35
+ - test/no_namespace.rb
36
+ - test/all.rb
31
37
  - README
32
38
  has_rdoc: true
33
- homepage: http://rubygems.org/gems/jsonrpc-server
39
+ homepage: https://github.com/dimalev/jsonrpc-server
34
40
  licenses: []
35
41
 
36
42
  post_install_message:
@@ -1,135 +0,0 @@
1
- require 'json'
2
-
3
- module JSONServer
4
-
5
- @@JSONRPCVersion = "2.0".freeze
6
-
7
- class JSONRPCError < RuntimeError
8
- attr_reader :code, :message, :data
9
- def initialize obj = {}
10
- @code = obj[:code] || -30000
11
- @message = obj[:message] || "Runtime Error"
12
- @data = obj[:data]
13
- end
14
- end
15
-
16
- def dispatch_json_2 request
17
- # here request specific errors are handled
18
- begin
19
- # no wraping, because single_request dispatcher does it
20
- result = core_dispatch request
21
- rescue JSONRPCError
22
- result = wrap_error({ :code => $!.code,
23
- :message => $!.message,
24
- :data => $!.data
25
- })
26
- rescue RuntimeError
27
- result = wrap_error({ :code => -32000,
28
- :message => $!.message,
29
- :data => $!.to_s
30
- })
31
- end
32
- if result != nil
33
- return result.to_json
34
- end
35
- return ''
36
- end
37
-
38
- private
39
-
40
- def core_dispatch request
41
- begin
42
- data = JSON.parse request
43
- rescue JSON::ParserError
44
- parse_error # converting error into json-rpc 2.0
45
- end
46
- if data.kind_of? Array
47
- invalid_request if data.length == 0 # batch can not be empty
48
- res = data.collect{ |single|
49
- parse_single single
50
- }
51
- res.delete(nil)
52
- return res unless res.length == 0
53
- return nil
54
- end
55
- parse_single data
56
- end
57
-
58
- def wrap data, id = nil
59
- data.update({ :jsonrpc => @@JSONRPCVersion, :id => id })
60
- end
61
-
62
- def wrap_error error, id = nil
63
- wrap({ :error => error }, id)
64
- end
65
-
66
- def wrap_result result, id = nil
67
- wrap({:result => result}, id)
68
- end
69
-
70
- def parse_single(data)
71
- current_id = nil
72
- begin
73
- invalid_request unless data.kind_of?(Hash)
74
- current_id = data["id"] unless data["id"] == nil
75
- if data["jsonrpc"] != @@JSONRPCVersion ||
76
- (not data["method"].kind_of?(String)) ||
77
- data["method"] == ""
78
- invalid_request
79
- end
80
- self.class.public_instance_methods.each { |method|
81
- if method.to_s == data["method"]
82
- if data["params"].kind_of? Array
83
- arr = data["params"]
84
- elsif
85
- arr = self.method(data["method"]).parameters.collect { |pd|
86
- data["params"][pd[1].to_s]
87
- }
88
- end
89
- result = self.send(data["method"], *arr)
90
- if current_id != nil
91
- return wrap_result(result, current_id)
92
- else
93
- return
94
- end
95
- end
96
- }
97
- method_not_found
98
- rescue JSONRPCError
99
- result = wrap_error({ :code => $!.code,
100
- :message => $!.message,
101
- :data => $!.data
102
- }, current_id)
103
- rescue RuntimeError
104
- result = wrap_error({ :code => -32000,
105
- :message => $!.message,
106
- :data => $!.to_s
107
- }, current_id)
108
- end
109
- end
110
-
111
- ##
112
- # Error handling section
113
- ##
114
-
115
- def method_not_found
116
- raise(JSONRPCError, {
117
- :code => -32601,
118
- :message => "Method not found."
119
- }, caller)
120
- end
121
-
122
- def parse_error
123
- raise(JSONRPCError, {
124
- :code => -32700,
125
- :message => "Parse error."
126
- }, caller)
127
- end
128
-
129
- def invalid_request
130
- raise(JSONRPCError, {
131
- :code => -32600,
132
- :message => "Invalid Request."
133
- }, caller)
134
- end
135
- end