em-jsonrpc 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|