em-jsonrpc 0.0.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/lib/em-jsonrpc/client.rb +207 -0
- data/lib/em-jsonrpc/constants.rb +39 -0
- data/lib/em-jsonrpc/server.rb +215 -0
- data/lib/em-jsonrpc/version.rb +5 -0
- data/test/test-em-jsonrpc-client.rb +139 -0
- data/test/test-em-jsonrpc-server.rb +61 -0
- metadata +98 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
require "yajl"
|
3
|
+
require "securerandom"
|
4
|
+
|
5
|
+
require "em-jsonrpc/version"
|
6
|
+
require "em-jsonrpc/constants"
|
7
|
+
|
8
|
+
|
9
|
+
module EventMachine::JsonRPC
|
10
|
+
|
11
|
+
class Client < EM::Connection
|
12
|
+
DEFAULT_REQUEST_TIMEOUT = 2
|
13
|
+
|
14
|
+
attr_reader :pending_requests
|
15
|
+
|
16
|
+
def initialize(host, port, request_timeout = nil, parser_options = {})
|
17
|
+
@host = host
|
18
|
+
@port = port
|
19
|
+
@request_timeout = request_timeout || DEFAULT_REQUEST_TIMEOUT
|
20
|
+
@parser_options = parser_options
|
21
|
+
|
22
|
+
if parser_options and parser_options[:symbolize_keys]
|
23
|
+
@key_jsonrpc = :jsonrpc
|
24
|
+
@key_id = :id
|
25
|
+
@key_result = :result
|
26
|
+
@key_error = :error
|
27
|
+
@key_code = :code
|
28
|
+
@key_message = :message
|
29
|
+
else
|
30
|
+
@key_jsonrpc = KEY_JSONRPC
|
31
|
+
@key_id = KEY_ID
|
32
|
+
@key_result = KEY_RESULT
|
33
|
+
@key_error = KEY_ERROR
|
34
|
+
@key_code = KEY_CODE
|
35
|
+
@key_message = KEY_MESSAGE
|
36
|
+
end
|
37
|
+
|
38
|
+
@pending_requests = {}
|
39
|
+
@connected = false
|
40
|
+
end
|
41
|
+
|
42
|
+
def post_init
|
43
|
+
@encoder = Yajl::Encoder.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def connection_completed
|
47
|
+
@connected = true
|
48
|
+
@parser = Yajl::Parser.new @parser_options
|
49
|
+
@parser.on_parse_complete = method(:obj_parsed)
|
50
|
+
@state = :data
|
51
|
+
ready
|
52
|
+
end
|
53
|
+
|
54
|
+
def unbind
|
55
|
+
@pending_requests.clear
|
56
|
+
|
57
|
+
if @connected
|
58
|
+
connection_terminated
|
59
|
+
else
|
60
|
+
connection_failed
|
61
|
+
end
|
62
|
+
@connected = false
|
63
|
+
end
|
64
|
+
|
65
|
+
def connected?
|
66
|
+
@connected
|
67
|
+
end
|
68
|
+
|
69
|
+
def ready
|
70
|
+
end
|
71
|
+
|
72
|
+
def connection_failed
|
73
|
+
end
|
74
|
+
|
75
|
+
def connection_terminated
|
76
|
+
end
|
77
|
+
|
78
|
+
def connect_again
|
79
|
+
reconnect @host, @port
|
80
|
+
end
|
81
|
+
|
82
|
+
def send_request(method, params=nil)
|
83
|
+
id = SecureRandom.hex 4
|
84
|
+
request = Request.new self, id
|
85
|
+
request.timeout @request_timeout
|
86
|
+
@pending_requests[id] = request
|
87
|
+
|
88
|
+
jsonrpc_request = {
|
89
|
+
KEY_JSONRPC => VALUE_VERSION,
|
90
|
+
KEY_ID => id,
|
91
|
+
KEY_METHOD => method
|
92
|
+
}
|
93
|
+
jsonrpc_request[KEY_PARAMS] = params if params
|
94
|
+
send_data @encoder.encode jsonrpc_request
|
95
|
+
|
96
|
+
return request
|
97
|
+
end
|
98
|
+
|
99
|
+
def receive_data(data)
|
100
|
+
case @state
|
101
|
+
when :data
|
102
|
+
parse_data(data)
|
103
|
+
when :ignore
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse_data(data)
|
109
|
+
begin
|
110
|
+
@parser << data
|
111
|
+
rescue Yajl::ParseError => e
|
112
|
+
close_connection
|
113
|
+
@connected = false
|
114
|
+
@state = :ignore
|
115
|
+
parsing_error
|
116
|
+
cancel_pending_requests "response parsing error"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def obj_parsed(obj)
|
121
|
+
case obj
|
122
|
+
when Hash
|
123
|
+
process(obj)
|
124
|
+
when Array
|
125
|
+
# Do nothing, just ignore.
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def process(obj)
|
130
|
+
return unless (id = obj[@key_id]) and id.is_a? String
|
131
|
+
return unless request = @pending_requests[id]
|
132
|
+
|
133
|
+
request.delete
|
134
|
+
|
135
|
+
unless obj[@key_jsonrpc] == "2.0"
|
136
|
+
request.fail :invalid_response, "invalid response: doesn't include \"jsonrpc\": \"2.0\""
|
137
|
+
return
|
138
|
+
end
|
139
|
+
|
140
|
+
if obj.has_key? @key_result
|
141
|
+
request.succeed obj[@key_result]
|
142
|
+
return
|
143
|
+
elsif error = obj[@key_error]
|
144
|
+
if error.is_a? Hash and (code = error[@key_code]) and (message = error[@key_message])
|
145
|
+
request.fail :error, error
|
146
|
+
return
|
147
|
+
else
|
148
|
+
request.fail :invalid_response, "invalid response: \"error\" is not a valid object"
|
149
|
+
return
|
150
|
+
end
|
151
|
+
else
|
152
|
+
request.fail :invalid_response, "invalid response: not valid \"result\" or \"error\""
|
153
|
+
return
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def parsing_error
|
158
|
+
end
|
159
|
+
|
160
|
+
def cancel_pending_requests description
|
161
|
+
@pending_requests.each_value do |request|
|
162
|
+
request.fail :canceled, "request canceled: #{description}"
|
163
|
+
end
|
164
|
+
@pending_requests.clear
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
class Request
|
169
|
+
include EM::Deferrable
|
170
|
+
|
171
|
+
def initialize(client, id)
|
172
|
+
@client = client
|
173
|
+
@id = id
|
174
|
+
end
|
175
|
+
|
176
|
+
def delete
|
177
|
+
@client.pending_requests.delete @id
|
178
|
+
end
|
179
|
+
|
180
|
+
# Override EM::Deferrable#timeout method so the errback is executed passing
|
181
|
+
# a :request_timeout single argument.
|
182
|
+
# Also, the request is removed from the list of pending requests.
|
183
|
+
def timeout seconds
|
184
|
+
cancel_timeout
|
185
|
+
me = self
|
186
|
+
@deferred_timeout = EM::Timer.new(seconds) do
|
187
|
+
me.delete
|
188
|
+
me.fail :request_timeout
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end # class Request
|
192
|
+
|
193
|
+
end # class Client
|
194
|
+
|
195
|
+
|
196
|
+
class ConnectionError < StandardError ; end
|
197
|
+
|
198
|
+
|
199
|
+
def self.connect_tcp(host, port, handler, request_timeout=nil, parser_options={}, &block)
|
200
|
+
raise Error, "EventMachine is not running" unless EM.reactor_running?
|
201
|
+
|
202
|
+
EM.connect(host, port, handler, host, port, request_timeout, parser_options, &block)
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module EventMachine::JsonRPC
|
2
|
+
|
3
|
+
CODE_INVALID_JSON = -32700
|
4
|
+
MSG_INVALID_JSON = "invalid JSON"
|
5
|
+
|
6
|
+
CODE_INVALID_REQUEST = -32600
|
7
|
+
MSG_INVALID_REQ_JSONRPC = "invalid request: doesn't include \"jsonrpc\": \"2.0\""
|
8
|
+
MSG_INVALID_REQ_ID = "invalid request: wrong id"
|
9
|
+
MSG_INVALID_REQ_METHOD = "invalid request: wrong method"
|
10
|
+
MSG_INVALID_REQ_PARAMS = "invalid request: wrong params"
|
11
|
+
|
12
|
+
CODE_METHOD_NOT_FOUND = -32601
|
13
|
+
MSG_METHOD_NOT_FOUND = "method not found"
|
14
|
+
|
15
|
+
CODE_INVALID_PARAMS = -32602
|
16
|
+
MSG_INVALID_PARAMS = "invalid parameter(s)"
|
17
|
+
|
18
|
+
CODE_INTERNAL_ERROR = -32603
|
19
|
+
MSG_INTERNAL_ERROR = "internal error"
|
20
|
+
|
21
|
+
PARSING_ERROR_RESPONSE = "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{" \
|
22
|
+
"\"code\":#{CODE_INVALID_JSON}," \
|
23
|
+
"\"message\":\"#{MSG_INVALID_JSON}\"}}"
|
24
|
+
|
25
|
+
BATCH_NOT_SUPPORTED_RESPONSE = "{\"jsonrpc\":\"2.0\",\"id\":null,\"error\":{" \
|
26
|
+
"\"code\":-32099," \
|
27
|
+
"\"message\":\"batch mode not implemented\"}}"
|
28
|
+
|
29
|
+
KEY_JSONRPC = "jsonrpc"
|
30
|
+
VALUE_VERSION = "2.0"
|
31
|
+
KEY_ID = "id"
|
32
|
+
KEY_METHOD = "method"
|
33
|
+
KEY_PARAMS = "params"
|
34
|
+
KEY_RESULT = "result"
|
35
|
+
KEY_ERROR = "error"
|
36
|
+
KEY_CODE = "code"
|
37
|
+
KEY_MESSAGE = "message"
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require "eventmachine"
|
2
|
+
require "yajl"
|
3
|
+
|
4
|
+
require "em-jsonrpc/version"
|
5
|
+
require "em-jsonrpc/constants"
|
6
|
+
|
7
|
+
|
8
|
+
module EventMachine::JsonRPC
|
9
|
+
|
10
|
+
class Server < EM::Connection
|
11
|
+
attr_reader :encoder
|
12
|
+
|
13
|
+
def initialize(*options)
|
14
|
+
parser_options = options.first || {}
|
15
|
+
|
16
|
+
if parser_options[:symbolize_keys]
|
17
|
+
@key_jsonrpc = :jsonrpc
|
18
|
+
@key_id = :id
|
19
|
+
@key_method = :method
|
20
|
+
@key_params = :params
|
21
|
+
else
|
22
|
+
@key_jsonrpc = KEY_JSONRPC
|
23
|
+
@key_id = KEY_ID
|
24
|
+
@key_method = KEY_METHOD
|
25
|
+
@key_params = KEY_PARAMS
|
26
|
+
end
|
27
|
+
|
28
|
+
@parser = Yajl::Parser.new parser_options
|
29
|
+
@parser.on_parse_complete = method(:obj_parsed)
|
30
|
+
|
31
|
+
@state = :data
|
32
|
+
end
|
33
|
+
|
34
|
+
def receive_data(data)
|
35
|
+
case @state
|
36
|
+
when :data
|
37
|
+
parse_data(data)
|
38
|
+
when :ignore
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse_data(data)
|
44
|
+
begin
|
45
|
+
@parser << data
|
46
|
+
rescue Yajl::ParseError => e
|
47
|
+
send_data PARSING_ERROR_RESPONSE
|
48
|
+
close_connection_after_writing
|
49
|
+
@state = :ignore
|
50
|
+
parsing_error data, e
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def obj_parsed(obj)
|
55
|
+
@encoder ||= Yajl::Encoder.new
|
56
|
+
|
57
|
+
case obj
|
58
|
+
# Individual request/notification.
|
59
|
+
when Hash
|
60
|
+
process(obj)
|
61
|
+
# Batch: multiple requests/notifications in an array.
|
62
|
+
# NOTE: Not implemented as it doesn't make sense using JSON RPC over pure TCP / UnixSocket.
|
63
|
+
when Array
|
64
|
+
send_data BATCH_NOT_SUPPORTED_RESPONSE
|
65
|
+
close_connection_after_writing
|
66
|
+
@state = :ignore
|
67
|
+
batch_not_supported_error obj
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def process(obj)
|
72
|
+
is_request = obj.has_key?(@key_id)
|
73
|
+
id = obj[@key_id]
|
74
|
+
|
75
|
+
if is_request
|
76
|
+
unless id.is_a? String or id.is_a? Fixnum or id.is_a? NilClass
|
77
|
+
invalid_request obj, CODE_INVALID_REQUEST, MSG_INVALID_REQ_ID
|
78
|
+
reply_error nil, CODE_INVALID_REQUEST, MSG_INVALID_REQ_ID
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
unless obj[@key_jsonrpc] == "2.0"
|
84
|
+
invalid_request obj, CODE_INVALID_REQUEST, MSG_INVALID_REQ_JSONRPC
|
85
|
+
reply_error id, CODE_INVALID_REQUEST, MSG_INVALID_REQ_JSONRPC
|
86
|
+
return false
|
87
|
+
end
|
88
|
+
|
89
|
+
unless (method = obj[@key_method]).is_a? String
|
90
|
+
invalid_request obj, CODE_INVALID_REQUEST, MSG_INVALID_REQ_METHOD
|
91
|
+
reply_error id, CODE_INVALID_REQUEST, MSG_INVALID_REQ_METHOD
|
92
|
+
return false
|
93
|
+
end
|
94
|
+
|
95
|
+
if (params = obj[@key_params])
|
96
|
+
unless params.is_a? Array or params.is_a? Hash
|
97
|
+
invalid_request obj, CODE_INVALID_REQUEST, MSG_INVALID_REQ_PARAMS
|
98
|
+
reply_error id, CODE_INVALID_REQUEST, MSG_INVALID_REQ_PARAMS
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
if is_request
|
104
|
+
receive_request Request.new(self, id, method, params)
|
105
|
+
else
|
106
|
+
receive_notification method, params
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# This method must be overriden in the user's inherited class.
|
111
|
+
def receive_request(request)
|
112
|
+
puts "request received:\n#{request.inspect}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# This method must be overriden in the user's inherited class.
|
116
|
+
def receive_notification(method, params)
|
117
|
+
puts "notification received (method: #{method.inspect}, params: #{params.inspect})"
|
118
|
+
end
|
119
|
+
|
120
|
+
def reply_error(id, code, message)
|
121
|
+
send_data @encoder.encode({
|
122
|
+
KEY_JSONRPC => VALUE_VERSION,
|
123
|
+
KEY_ID => id,
|
124
|
+
KEY_ERROR => {
|
125
|
+
KEY_CODE => code,
|
126
|
+
KEY_MESSAGE => message
|
127
|
+
}
|
128
|
+
})
|
129
|
+
end
|
130
|
+
|
131
|
+
# This method could be overriden in the user's inherited class.
|
132
|
+
def parsing_error(data, exception)
|
133
|
+
$stderr.puts "parsing error:\n#{exception.message}"
|
134
|
+
end
|
135
|
+
|
136
|
+
# This method could be overriden in the user's inherited class.
|
137
|
+
def batch_not_supported_error(obj)
|
138
|
+
$stderr.puts "batch request received but not implemented"
|
139
|
+
end
|
140
|
+
|
141
|
+
# This method could be overriden in the user's inherited class.
|
142
|
+
def invalid_request(obj, code, message=nil)
|
143
|
+
$stderr.puts "error #{code}: #{message}"
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
class Request
|
148
|
+
attr_reader :rpc_method, :params, :id
|
149
|
+
|
150
|
+
def initialize(conn, id, rpc_method, params)
|
151
|
+
@conn = conn
|
152
|
+
@id = id
|
153
|
+
@rpc_method = rpc_method
|
154
|
+
@params = params
|
155
|
+
end
|
156
|
+
|
157
|
+
def reply_result(result)
|
158
|
+
return nil if @conn.error?
|
159
|
+
|
160
|
+
response = {
|
161
|
+
KEY_JSONRPC => VALUE_VERSION,
|
162
|
+
KEY_ID => @id,
|
163
|
+
KEY_RESULT => result
|
164
|
+
}
|
165
|
+
|
166
|
+
# Send the response in chunks (good in case of a big response).
|
167
|
+
begin
|
168
|
+
@conn.encoder.encode(response) do |chunk|
|
169
|
+
@conn.send_data(chunk)
|
170
|
+
end
|
171
|
+
return true
|
172
|
+
rescue Yajl::EncodeError => e
|
173
|
+
reply_internal_error "response encode error: #{e.message}"
|
174
|
+
return false
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def reply_internal_error(message=nil)
|
179
|
+
return nil if @conn.error?
|
180
|
+
@conn.reply_error(@id, CODE_INTERNAL_ERROR, message || MSG_INTERNAL_ERROR)
|
181
|
+
end
|
182
|
+
|
183
|
+
def reply_method_not_found(message=nil)
|
184
|
+
return nil if @conn.error?
|
185
|
+
@conn.reply_error(@id, CODE_METHOD_NOT_FOUND, message || MSG_METHOD_NOT_FOUND)
|
186
|
+
end
|
187
|
+
|
188
|
+
def reply_invalid_params(message=nil)
|
189
|
+
return nil if @conn.error?
|
190
|
+
@conn.reply_error(@id, CODE_INVALID_PARAMS, message || MSG_INVALID_PARAMS)
|
191
|
+
end
|
192
|
+
|
193
|
+
def reply_custom_error(code, message)
|
194
|
+
return nil if @conn.error?
|
195
|
+
unless code.is_a? Integer and (-32099..-32000).include? code
|
196
|
+
raise ArgumentError, "code must be an integer between -32099 and -32000"
|
197
|
+
end
|
198
|
+
@conn.reply_error(@id, code, message)
|
199
|
+
end
|
200
|
+
end # class Request
|
201
|
+
|
202
|
+
end # class Server
|
203
|
+
|
204
|
+
|
205
|
+
def self.start_tcp_server(addr, port, handler, options=nil, &block)
|
206
|
+
raise Error, "EventMachine is not running" unless EM.reactor_running?
|
207
|
+
EM.start_server addr, port, handler, options, &block
|
208
|
+
end
|
209
|
+
|
210
|
+
def self.start_unix_domain_server(filename, handler, options=nil, &block)
|
211
|
+
raise Error, "EventMachine is not running" unless EM.reactor_running?
|
212
|
+
EM.start_unix_domain_server filename, handler, options, &block
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require "test/unit"
|
5
|
+
require "rubygems"
|
6
|
+
require "yajl"
|
7
|
+
require "socket"
|
8
|
+
require "timeout"
|
9
|
+
|
10
|
+
|
11
|
+
class TestJsonRPC < Test::Unit::TestCase
|
12
|
+
|
13
|
+
def initialize(*args)
|
14
|
+
super
|
15
|
+
@socket = TCPSocket.new "127.0.0.1", 8888
|
16
|
+
@parser = Yajl::Parser.new
|
17
|
+
@parser.on_parse_complete = method(:response_parsed)
|
18
|
+
@encoder = Yajl::Encoder.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def send_request(request)
|
22
|
+
@response = nil
|
23
|
+
@socket.send request, 0
|
24
|
+
|
25
|
+
begin
|
26
|
+
Timeout.timeout(0.2) do
|
27
|
+
while true do
|
28
|
+
begin
|
29
|
+
data = @socket.recv_nonblock 1024
|
30
|
+
@parser << data
|
31
|
+
return @response if @response
|
32
|
+
rescue IO::WaitReadable #Errno::EAGAIN
|
33
|
+
IO.select([@socket])
|
34
|
+
retry
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
rescue Timeout::Error
|
39
|
+
return nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def response_parsed(response)
|
44
|
+
@response = response
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_01_rpc_call_with_positional_parameters
|
48
|
+
reply = send_request '{"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1}'
|
49
|
+
assert_equal( {"jsonrpc"=>"2.0", "result"=>19, "id"=>1}, reply )
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_02_rpc_call_with_positional_parameters
|
53
|
+
reply = send_request '{"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2}'
|
54
|
+
assert_equal( {"jsonrpc"=>"2.0", "result"=>-19, "id"=>2}, reply )
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_03_rpc_call_with_named_parameters
|
58
|
+
reply = send_request '{"jsonrpc": "2.0", "method": "subtract", "params": {"subtrahend": 23, "minuend": 42}, "id": 3}'
|
59
|
+
assert_equal( {"jsonrpc"=>"2.0", "result"=>19, "id"=>3}, reply )
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_04_rpc_call_with_named_parameters
|
63
|
+
reply = send_request '{"jsonrpc": "2.0", "method": "subtract", "params": {"minuend": 42, "subtrahend": 23}, "id": 4}'
|
64
|
+
assert_equal( {"jsonrpc"=>"2.0", "result"=>19, "id"=>4}, reply )
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_05_a_notification
|
68
|
+
reply = send_request '{"jsonrpc": "2.0", "method": "update", "params": [1,2,3,4,5]}'
|
69
|
+
assert_equal( nil, reply )
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_06_rpc_call_of_non_existent_method
|
73
|
+
reply = send_request '{"jsonrpc": "2.0", "method": "foobar", "id": "1"}'
|
74
|
+
assert_equal( {"jsonrpc"=>"2.0", "error"=>{"code"=>-32601, "message"=>"method not found"}, "id"=>"1"}, reply )
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_07_rpc_call_with_invalid_JSON
|
78
|
+
reply = send_request '{"jsonrpc": "2.0", "method": "foobar, "params": "bar", "baz]'
|
79
|
+
assert_equal( {"jsonrpc"=>"2.0", "error"=>{"code"=>-32700, "message"=>"invalid JSON"}, "id"=>nil}, reply )
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_08_rpc_call_with_invalid_Request_object
|
83
|
+
reply = send_request '{"jsonrpc": "2.0", "method": 1, "params": "bar"}'
|
84
|
+
assert_equal( {"jsonrpc"=>"2.0", "error" =>{"code"=>-32600, "message"=>"invalid request: wrong method"}, "id"=>nil}, reply )
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_09_rpc_call_Batch_invalid_JSON
|
88
|
+
reply = send_request '[ {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},{"jsonrpc": "2.0", "method" ]'
|
89
|
+
assert_equal( {"jsonrpc"=>"2.0", "error"=>{"code"=>-32700, "message"=>"invalid JSON"}, "id"=>nil}, reply )
|
90
|
+
end
|
91
|
+
|
92
|
+
# NOTE: Batch mode is not implemented so following tests don't make sense.
|
93
|
+
#
|
94
|
+
# def test_10_rpc_call_with_an_empty_Array
|
95
|
+
# reply = send_request '[]'
|
96
|
+
# assert_equal( {"jsonrpc"=>"2.0", "error"=>{"code"=>-32600, "message"=>"invalid JSON"}, "id"=>nil}, reply )
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# def test_11_rpc_call_with_an_invalid_Batch_but_not_empty
|
100
|
+
# reply = send_request '[1]'
|
101
|
+
# assert_equal( [{"jsonrpc"=>"2.0", "error"=>{"code"=>-32600, "message"=>"Invalid Request."}, "id"=>nil}], reply )
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# def test_12_rpc_call_with_invalid_Batch
|
105
|
+
# reply = send_request '[1,2,3]'
|
106
|
+
# assert_equal( [
|
107
|
+
# {"jsonrpc"=>"2.0", "error"=>{"code"=>-32600, "message"=>"invalid JSON"}, "id"=>nil},
|
108
|
+
# {"jsonrpc"=>"2.0", "error"=>{"code"=>-32600, "message"=>"invalid JSON"}, "id"=>nil},
|
109
|
+
# {"jsonrpc"=>"2.0", "error"=>{"code"=>-32600, "message"=>"invalid JSON"}, "id"=>nil}
|
110
|
+
# ], reply )
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# def test_13_rpc_call_Batch
|
114
|
+
# reply = send_request '[
|
115
|
+
# {"jsonrpc": "2.0", "method": "sum", "params": [1,2,4], "id": "1"},
|
116
|
+
# {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]},
|
117
|
+
# {"jsonrpc": "2.0", "method": "subtract", "params": [42,23], "id": "2"},
|
118
|
+
# {"foo": "boo"},
|
119
|
+
# {"jsonrpc": "2.0", "method": "foo.get", "params": {"name": "myself"}, "id": "5"},
|
120
|
+
# {"jsonrpc": "2.0", "method": "get_data", "id": "9"}
|
121
|
+
# ]'
|
122
|
+
# assert_equal( [
|
123
|
+
# {"jsonrpc"=>"2.0", "result"=>7, "id"=>"1"},
|
124
|
+
# {"jsonrpc"=>"2.0", "result"=>19, "id"=>"2"},
|
125
|
+
# {"jsonrpc"=>"2.0", "error"=>{"code"=>-32600, "message"=>"invalid JSON"}, "id"=>nil},
|
126
|
+
# {"jsonrpc"=>"2.0", "error"=>{"code"=>-32601, "message"=>"method not found."}, "id"=>"5"},
|
127
|
+
# {"jsonrpc"=>"2.0", "result"=>["hello", 5], "id"=>"9"}
|
128
|
+
# ], reply )
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# def test_14_rpc_call_Batch_all_notifications
|
132
|
+
# reply = send_request '[
|
133
|
+
# {"jsonrpc": "2.0", "method": "notify_sum", "params": [1,2,4]},
|
134
|
+
# {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}
|
135
|
+
# ]'
|
136
|
+
# assert_equal( nil, reply )
|
137
|
+
# end
|
138
|
+
|
139
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
### TMP
|
5
|
+
$LOAD_PATH.insert 0, File.expand_path(File.join(File.dirname(__FILE__), "../", "lib"))
|
6
|
+
require "em-jsonrpc/server"
|
7
|
+
|
8
|
+
|
9
|
+
class MyJsonRpcServer < EM::JsonRPC::Server
|
10
|
+
|
11
|
+
def receive_request(request)
|
12
|
+
puts "request received:"
|
13
|
+
puts "- id : #{request.id.inspect}"
|
14
|
+
puts "- method : #{request.rpc_method.inspect}"
|
15
|
+
puts "- params : #{request.params.inspect}"
|
16
|
+
|
17
|
+
case request.rpc_method
|
18
|
+
|
19
|
+
when /^(subtract|\-)$/
|
20
|
+
if request.params.is_a? Array
|
21
|
+
minued = request.params[0].to_i
|
22
|
+
subtrahend = request.params[1].to_i
|
23
|
+
elsif request.params.is_a? Hash
|
24
|
+
minued = request.params[:minuend].to_i
|
25
|
+
subtrahend = request.params[:subtrahend].to_i
|
26
|
+
end
|
27
|
+
result = minued - subtrahend
|
28
|
+
request.reply_result(result)
|
29
|
+
|
30
|
+
when /^(sum|\+)$/
|
31
|
+
if request.params.is_a? Array
|
32
|
+
sum1 = request.params[0].to_i
|
33
|
+
sum2 = request.params[1].to_i
|
34
|
+
elsif request.params.is_a? Hash
|
35
|
+
sum1 = request.params[:minuend].to_i
|
36
|
+
sum2 = request.params[:subtrahend].to_i
|
37
|
+
end
|
38
|
+
result = sum1 + sum2
|
39
|
+
request.reply_result(result)
|
40
|
+
|
41
|
+
else
|
42
|
+
request.reply_method_not_found
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
EM.run do
|
50
|
+
yajl_options = { :symbolize_keys => true }
|
51
|
+
|
52
|
+
EM::JsonRPC.start_tcp_server("0.0.0.0", 8888, MyJsonRpcServer, yajl_options) do |conn|
|
53
|
+
puts "\nnew TCP connection"
|
54
|
+
conn.set_comm_inactivity_timeout 120
|
55
|
+
end
|
56
|
+
|
57
|
+
EM::JsonRPC.start_unix_domain_server("/tmp/borrame", MyJsonRpcServer, yajl_options) do |conn|
|
58
|
+
puts "\nnew UnixSocket connection"
|
59
|
+
conn.set_comm_inactivity_timeout 120
|
60
|
+
end
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-jsonrpc
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
version: 0.0.2
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- "I\xC3\xB1aki Baz Castillo"
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-01-02 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: eventmachine
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: yajl-ruby
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
- 8
|
44
|
+
- 0
|
45
|
+
version: 0.8.0
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
description: em-jsonrpc provides a JSON RPC 2.0 TCP/UnixSocket client and server to be integrated within EventMachine reactor
|
49
|
+
email: ibc@aliax.net
|
50
|
+
executables: []
|
51
|
+
|
52
|
+
extensions: []
|
53
|
+
|
54
|
+
extra_rdoc_files: []
|
55
|
+
|
56
|
+
files:
|
57
|
+
- lib/em-jsonrpc/version.rb
|
58
|
+
- lib/em-jsonrpc/constants.rb
|
59
|
+
- lib/em-jsonrpc/server.rb
|
60
|
+
- lib/em-jsonrpc/client.rb
|
61
|
+
- test/test-em-jsonrpc-client.rb
|
62
|
+
- test/test-em-jsonrpc-server.rb
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: https://github.com/ibc/em-jsonrpc
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
segments:
|
78
|
+
- 1
|
79
|
+
- 8
|
80
|
+
- 7
|
81
|
+
version: 1.8.7
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
segments:
|
88
|
+
- 0
|
89
|
+
version: "0"
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 1.3.7
|
94
|
+
signing_key:
|
95
|
+
specification_version: 3
|
96
|
+
summary: JSON RCP 2.0 client and server for EventMachine over TCP or UnixSocket
|
97
|
+
test_files: []
|
98
|
+
|