jrpc 1.1.7 → 2.0.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 +4 -4
- data/.github/workflows/ci.yml +55 -0
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rubocop.yml +228 -0
- data/CHANGELOG.md +84 -0
- data/Gemfile +17 -0
- data/README.md +163 -13
- data/Rakefile +3 -1
- data/bin/console +15 -0
- data/bin/jrpc +111 -0
- data/bin/jrpc-shell +109 -0
- data/bin/setup +8 -0
- data/jrpc.gemspec +9 -8
- data/lib/jrpc/errors.rb +65 -0
- data/lib/jrpc/id_generator.rb +22 -0
- data/lib/jrpc/message.rb +78 -0
- data/lib/jrpc/shared_client/outbound_queue.rb +71 -0
- data/lib/jrpc/shared_client/registry.rb +46 -0
- data/lib/jrpc/shared_client/ticket.rb +84 -0
- data/lib/jrpc/shared_client/transport_loop.rb +290 -0
- data/lib/jrpc/shared_client.rb +194 -0
- data/lib/jrpc/simple_client.rb +89 -0
- data/lib/jrpc/transport/base.rb +60 -0
- data/lib/jrpc/transport/tcp.rb +243 -0
- data/lib/jrpc/transport.rb +12 -0
- data/lib/jrpc/version.rb +3 -1
- data/lib/jrpc.rb +15 -16
- metadata +35 -76
- data/.travis.yml +0 -4
- data/lib/jrpc/base_client.rb +0 -123
- data/lib/jrpc/error/client_error.rb +0 -5
- data/lib/jrpc/error/connection_error.rb +0 -11
- data/lib/jrpc/error/error.rb +0 -5
- data/lib/jrpc/error/internal_error.rb +0 -9
- data/lib/jrpc/error/internal_server_error.rb +0 -5
- data/lib/jrpc/error/invalid_params.rb +0 -9
- data/lib/jrpc/error/invalid_request.rb +0 -9
- data/lib/jrpc/error/method_not_found.rb +0 -9
- data/lib/jrpc/error/parse_error.rb +0 -9
- data/lib/jrpc/error/server_error.rb +0 -11
- data/lib/jrpc/error/unknown_error.rb +0 -5
- data/lib/jrpc/tcp_client.rb +0 -112
- data/lib/jrpc/transport/socket_base.rb +0 -88
- data/lib/jrpc/transport/socket_tcp.rb +0 -98
- data/lib/jrpc/utils.rb +0 -9
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
|
|
5
|
+
module JRPC
|
|
6
|
+
module Transport
|
|
7
|
+
class Tcp < Base
|
|
8
|
+
# Pull error classes into this scope so `raise ConnectionError` resolves correctly.
|
|
9
|
+
# Without this, Ruby's constant lookup would find JRPC::ConnectionError (v1) instead.
|
|
10
|
+
ConnectionError = Base::ConnectionError
|
|
11
|
+
Timeout = Base::Timeout
|
|
12
|
+
MalformedFrame = Base::MalformedFrame
|
|
13
|
+
|
|
14
|
+
def initialize(server, **options)
|
|
15
|
+
super
|
|
16
|
+
@socket = nil
|
|
17
|
+
@read_buffer = ''.b
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def connect
|
|
21
|
+
deadline = @connect_timeout ? monotonic_now + @connect_timeout : nil
|
|
22
|
+
attempts_remaining = @connect_retry_count + 1
|
|
23
|
+
begin
|
|
24
|
+
connect_once(deadline)
|
|
25
|
+
rescue ConnectionError, Timeout
|
|
26
|
+
attempts_remaining -= 1
|
|
27
|
+
raise if attempts_remaining <= 0
|
|
28
|
+
raise if deadline && remaining_time(deadline) <= 0 # total budget spent
|
|
29
|
+
|
|
30
|
+
sleep @connect_retry_interval
|
|
31
|
+
retry
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def write_frame(bytes, timeout:)
|
|
36
|
+
raise ConnectionError, 'transport closed' if @socket.nil?
|
|
37
|
+
|
|
38
|
+
frame = "#{bytes.bytesize}:#{bytes},"
|
|
39
|
+
written = 0
|
|
40
|
+
deadline = timeout ? monotonic_now + timeout : nil
|
|
41
|
+
|
|
42
|
+
while written < frame.bytesize
|
|
43
|
+
remaining = remaining_time(deadline)
|
|
44
|
+
close_and_raise_timeout!('write') if remaining && remaining <= 0
|
|
45
|
+
|
|
46
|
+
writable = @socket.wait_writable(remaining)
|
|
47
|
+
close_and_raise_timeout!('write') unless writable
|
|
48
|
+
|
|
49
|
+
begin
|
|
50
|
+
n = @socket.write_nonblock(frame.byteslice(written..))
|
|
51
|
+
written += n
|
|
52
|
+
rescue IO::WaitWritable
|
|
53
|
+
# socket not ready yet; loop back to IO.select. Rescue the module (not the
|
|
54
|
+
# concrete IO::EAGAINWaitWritable) so IO::EWOULDBLOCKWaitWritable is also
|
|
55
|
+
# caught on platforms where EAGAIN != EWOULDBLOCK (macOS/BSD).
|
|
56
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, IOError => e
|
|
57
|
+
close
|
|
58
|
+
raise ConnectionError, "write failed: #{e.class}: #{e.message}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def read_frame(timeout:)
|
|
64
|
+
raise ConnectionError, 'transport closed' if @socket.nil?
|
|
65
|
+
|
|
66
|
+
deadline = timeout ? monotonic_now + timeout : nil
|
|
67
|
+
|
|
68
|
+
loop do
|
|
69
|
+
result = try_parse_frame
|
|
70
|
+
return result unless result == :wait
|
|
71
|
+
|
|
72
|
+
remaining = remaining_time(deadline)
|
|
73
|
+
close_and_raise_timeout!('read') if remaining && remaining <= 0
|
|
74
|
+
|
|
75
|
+
readable = @socket.wait_readable(remaining)
|
|
76
|
+
close_and_raise_timeout!('read') unless readable
|
|
77
|
+
|
|
78
|
+
fill_buffer
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def try_read_frame
|
|
83
|
+
raise ConnectionError, 'transport closed' if @socket.nil?
|
|
84
|
+
|
|
85
|
+
# Parse first so already-buffered frames survive an EOF on the next read.
|
|
86
|
+
result = try_parse_frame
|
|
87
|
+
return result unless result == :wait
|
|
88
|
+
|
|
89
|
+
# fill_buffer swallows EAGAIN (no data yet); the re-parse then returns :wait.
|
|
90
|
+
fill_buffer
|
|
91
|
+
try_parse_frame
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def close
|
|
95
|
+
begin
|
|
96
|
+
@socket&.close
|
|
97
|
+
rescue StandardError
|
|
98
|
+
nil
|
|
99
|
+
end
|
|
100
|
+
@socket = nil
|
|
101
|
+
@read_buffer = ''.b
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def closed?
|
|
105
|
+
@socket.nil? || @socket.closed?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
attr_reader :socket
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
|
|
112
|
+
# Attempt a single TCP connect. All errors are normalised to ConnectionError or Timeout.
|
|
113
|
+
def connect_once(deadline)
|
|
114
|
+
# Close any existing socket first so connecting on an already-connected
|
|
115
|
+
# transport replaces it cleanly instead of orphaning the old file descriptor.
|
|
116
|
+
begin
|
|
117
|
+
@socket&.close
|
|
118
|
+
rescue StandardError
|
|
119
|
+
nil
|
|
120
|
+
end
|
|
121
|
+
@socket = nil
|
|
122
|
+
|
|
123
|
+
sock, sockaddr = build_socket
|
|
124
|
+
|
|
125
|
+
loop do
|
|
126
|
+
break if try_connect_nonblock(sock, sockaddr, deadline)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
@socket = sock
|
|
130
|
+
@read_buffer = ''.b
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def fill_buffer
|
|
134
|
+
chunk = @socket.read_nonblock(65_536)
|
|
135
|
+
@read_buffer << chunk.b
|
|
136
|
+
rescue IO::WaitReadable
|
|
137
|
+
# no data right now; caller already confirmed readable via IO.select
|
|
138
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, IOError => e
|
|
139
|
+
close
|
|
140
|
+
raise ConnectionError, "read failed: #{e.class}: #{e.message}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def build_socket
|
|
144
|
+
host, port_str = @server.split(':', 2)
|
|
145
|
+
addr_info = ::Socket.getaddrinfo(host, nil, nil, ::Socket::SOCK_STREAM)
|
|
146
|
+
family = ::Socket.const_get(addr_info[0][0])
|
|
147
|
+
sockaddr = ::Socket.pack_sockaddr_in(port_str.to_i, addr_info[0][3])
|
|
148
|
+
sock = ::Socket.new(family, ::Socket::SOCK_STREAM, 0)
|
|
149
|
+
[sock, sockaddr]
|
|
150
|
+
rescue StandardError => e
|
|
151
|
+
raise ConnectionError, "#{e.class}: #{e.message}"
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def try_connect_nonblock(sock, sockaddr, deadline)
|
|
155
|
+
sock.connect_nonblock(sockaddr)
|
|
156
|
+
true # connected
|
|
157
|
+
rescue Errno::EISCONN
|
|
158
|
+
true # already connected
|
|
159
|
+
rescue IO::WaitWritable
|
|
160
|
+
writable = sock.wait_writable(connect_wait_timeout(deadline))
|
|
161
|
+
unless writable
|
|
162
|
+
begin
|
|
163
|
+
sock.close
|
|
164
|
+
rescue StandardError
|
|
165
|
+
nil
|
|
166
|
+
end
|
|
167
|
+
raise Timeout, "connect timed out to #{@server}"
|
|
168
|
+
end
|
|
169
|
+
false
|
|
170
|
+
# loop again → next connect_nonblock call will return EISCONN or error
|
|
171
|
+
rescue StandardError => e
|
|
172
|
+
begin
|
|
173
|
+
sock.close
|
|
174
|
+
rescue StandardError
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
raise ConnectionError, "#{e.class}: #{e.message}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Wait passed to a single IO.select while connecting: the smaller of the per-attempt
|
|
181
|
+
# cap and the time left in the overall connect deadline. nil (block forever) only when
|
|
182
|
+
# neither bound is set.
|
|
183
|
+
def connect_wait_timeout(deadline)
|
|
184
|
+
bounds = [@connect_attempt_timeout, remaining_time(deadline)].compact
|
|
185
|
+
return nil if bounds.empty?
|
|
186
|
+
|
|
187
|
+
wait = bounds.min
|
|
188
|
+
wait.negative? ? 0 : wait
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def try_parse_frame
|
|
192
|
+
return :wait if @read_buffer.empty?
|
|
193
|
+
|
|
194
|
+
colon_idx = @read_buffer.index(':'.b)
|
|
195
|
+
|
|
196
|
+
if colon_idx.nil?
|
|
197
|
+
unless @read_buffer.match?(/\A\d+\z/)
|
|
198
|
+
raise MalformedFrame, "non-digit in length prefix: #{@read_buffer.byteslice(0, 32).inspect}"
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
return :wait
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
raise MalformedFrame, 'empty length prefix' if colon_idx.zero?
|
|
205
|
+
|
|
206
|
+
length_str = @read_buffer.byteslice(0, colon_idx)
|
|
207
|
+
raise MalformedFrame, "non-digit in length prefix: #{length_str.inspect}" unless length_str.match?(/\A\d+\z/)
|
|
208
|
+
if length_str.bytesize > 1 && length_str.getbyte(0) == 48 # ord('0'): leading zero
|
|
209
|
+
raise MalformedFrame, "leading zero in length prefix: #{length_str.inspect}"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
length = Integer(length_str, 10)
|
|
213
|
+
frame_end = colon_idx + 1 + length # index of the expected comma
|
|
214
|
+
|
|
215
|
+
return :wait if @read_buffer.bytesize <= frame_end
|
|
216
|
+
|
|
217
|
+
unless @read_buffer.getbyte(frame_end) == 44 # ord(',')
|
|
218
|
+
raise MalformedFrame, "missing comma terminator at position #{frame_end}"
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
data = @read_buffer.byteslice(colon_idx + 1, length).force_encoding(Encoding::UTF_8)
|
|
222
|
+
# The line-194 guard guarantees bytesize > frame_end, so this byteslice is never nil.
|
|
223
|
+
@read_buffer = @read_buffer.byteslice((frame_end + 1)..)
|
|
224
|
+
data
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def monotonic_now
|
|
228
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def remaining_time(deadline)
|
|
232
|
+
return nil unless deadline
|
|
233
|
+
|
|
234
|
+
deadline - monotonic_now
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def close_and_raise_timeout!(operation)
|
|
238
|
+
close
|
|
239
|
+
raise Timeout, "#{operation} timed out on #{@server}"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
data/lib/jrpc/version.rb
CHANGED
data/lib/jrpc.rb
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'concurrent'
|
|
1
6
|
require 'jrpc/version'
|
|
2
|
-
require 'jrpc/
|
|
3
|
-
require 'jrpc/
|
|
4
|
-
require 'jrpc/
|
|
5
|
-
require 'jrpc/transport
|
|
6
|
-
require 'jrpc/
|
|
7
|
-
require 'jrpc/
|
|
8
|
-
require 'jrpc/
|
|
9
|
-
require 'jrpc/
|
|
10
|
-
require 'jrpc/
|
|
11
|
-
require 'jrpc/
|
|
12
|
-
require 'jrpc/error/internal_server_error'
|
|
13
|
-
require 'jrpc/error/invalid_params'
|
|
14
|
-
require 'jrpc/error/invalid_request'
|
|
15
|
-
require 'jrpc/error/method_not_found'
|
|
16
|
-
require 'jrpc/error/parse_error'
|
|
17
|
-
require 'jrpc/error/unknown_error'
|
|
7
|
+
require 'jrpc/errors'
|
|
8
|
+
require 'jrpc/id_generator'
|
|
9
|
+
require 'jrpc/message'
|
|
10
|
+
require 'jrpc/transport'
|
|
11
|
+
require 'jrpc/simple_client'
|
|
12
|
+
require 'jrpc/shared_client/ticket'
|
|
13
|
+
require 'jrpc/shared_client/registry'
|
|
14
|
+
require 'jrpc/shared_client/outbound_queue'
|
|
15
|
+
require 'jrpc/shared_client/transport_loop'
|
|
16
|
+
require 'jrpc/shared_client'
|
|
18
17
|
|
|
19
18
|
module JRPC
|
|
20
19
|
JSON_RPC_VERSION = '2.0'
|
metadata
CHANGED
|
@@ -1,123 +1,83 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jrpc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Denis Talakevich
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: concurrent-ruby
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
18
|
+
version: '1.2'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
25
|
+
version: '1.2'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
27
|
+
name: logger
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
|
-
- - "
|
|
30
|
+
- - ">="
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
32
|
+
version: '0'
|
|
34
33
|
type: :runtime
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
|
-
- - "
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: '3.0'
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: bundler
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - "~>"
|
|
37
|
+
- - ">="
|
|
46
38
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - "~>"
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: '1.10'
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: rake
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - "~>"
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: '10.0'
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - "~>"
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: '10.0'
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: rspec
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - "~>"
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: '3.0'
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - "~>"
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: '3.0'
|
|
39
|
+
version: '0'
|
|
83
40
|
description: JSON RPC client over TCP
|
|
84
41
|
email:
|
|
85
42
|
- senid231@gmail.com
|
|
86
|
-
executables:
|
|
43
|
+
executables:
|
|
44
|
+
- jrpc
|
|
45
|
+
- jrpc-shell
|
|
87
46
|
extensions: []
|
|
88
47
|
extra_rdoc_files: []
|
|
89
48
|
files:
|
|
49
|
+
- ".github/workflows/ci.yml"
|
|
90
50
|
- ".gitignore"
|
|
91
51
|
- ".rspec"
|
|
92
|
-
- ".
|
|
52
|
+
- ".rubocop.yml"
|
|
53
|
+
- CHANGELOG.md
|
|
93
54
|
- Gemfile
|
|
94
55
|
- LICENSE.txt
|
|
95
56
|
- README.md
|
|
96
57
|
- Rakefile
|
|
58
|
+
- bin/console
|
|
59
|
+
- bin/jrpc
|
|
60
|
+
- bin/jrpc-shell
|
|
61
|
+
- bin/setup
|
|
97
62
|
- jrpc.gemspec
|
|
98
63
|
- lib/jrpc.rb
|
|
99
|
-
- lib/jrpc/
|
|
100
|
-
- lib/jrpc/
|
|
101
|
-
- lib/jrpc/
|
|
102
|
-
- lib/jrpc/
|
|
103
|
-
- lib/jrpc/
|
|
104
|
-
- lib/jrpc/
|
|
105
|
-
- lib/jrpc/
|
|
106
|
-
- lib/jrpc/
|
|
107
|
-
- lib/jrpc/
|
|
108
|
-
- lib/jrpc/
|
|
109
|
-
- lib/jrpc/
|
|
110
|
-
- lib/jrpc/
|
|
111
|
-
- lib/jrpc/tcp_client.rb
|
|
112
|
-
- lib/jrpc/transport/socket_base.rb
|
|
113
|
-
- lib/jrpc/transport/socket_tcp.rb
|
|
114
|
-
- lib/jrpc/utils.rb
|
|
64
|
+
- lib/jrpc/errors.rb
|
|
65
|
+
- lib/jrpc/id_generator.rb
|
|
66
|
+
- lib/jrpc/message.rb
|
|
67
|
+
- lib/jrpc/shared_client.rb
|
|
68
|
+
- lib/jrpc/shared_client/outbound_queue.rb
|
|
69
|
+
- lib/jrpc/shared_client/registry.rb
|
|
70
|
+
- lib/jrpc/shared_client/ticket.rb
|
|
71
|
+
- lib/jrpc/shared_client/transport_loop.rb
|
|
72
|
+
- lib/jrpc/simple_client.rb
|
|
73
|
+
- lib/jrpc/transport.rb
|
|
74
|
+
- lib/jrpc/transport/base.rb
|
|
75
|
+
- lib/jrpc/transport/tcp.rb
|
|
115
76
|
- lib/jrpc/version.rb
|
|
116
|
-
homepage: https://github.com/
|
|
77
|
+
homepage: https://github.com/didww/jrpc
|
|
117
78
|
licenses:
|
|
118
79
|
- MIT
|
|
119
80
|
metadata: {}
|
|
120
|
-
post_install_message:
|
|
121
81
|
rdoc_options: []
|
|
122
82
|
require_paths:
|
|
123
83
|
- lib
|
|
@@ -125,15 +85,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
125
85
|
requirements:
|
|
126
86
|
- - ">="
|
|
127
87
|
- !ruby/object:Gem::Version
|
|
128
|
-
version: '
|
|
88
|
+
version: '3.3'
|
|
129
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
90
|
requirements:
|
|
131
91
|
- - ">="
|
|
132
92
|
- !ruby/object:Gem::Version
|
|
133
93
|
version: '0'
|
|
134
94
|
requirements: []
|
|
135
|
-
rubygems_version: 3.
|
|
136
|
-
signing_key:
|
|
95
|
+
rubygems_version: 3.6.9
|
|
137
96
|
specification_version: 4
|
|
138
97
|
summary: JSON RPC client
|
|
139
98
|
test_files: []
|
data/.travis.yml
DELETED
data/lib/jrpc/base_client.rb
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
require 'oj'
|
|
2
|
-
require 'forwardable'
|
|
3
|
-
module JRPC
|
|
4
|
-
class BaseClient
|
|
5
|
-
extend Forwardable
|
|
6
|
-
|
|
7
|
-
attr_reader :uri, :options
|
|
8
|
-
|
|
9
|
-
ID_CHARACTERS = (('a'..'z').to_a + ('0'..'9').to_a + ('A'..'Z').to_a).freeze
|
|
10
|
-
REQUEST_TYPES = [:request, :notification].freeze
|
|
11
|
-
|
|
12
|
-
def self.connect(uri, options)
|
|
13
|
-
client = new(uri, options)
|
|
14
|
-
yield(client)
|
|
15
|
-
ensure
|
|
16
|
-
client.close if client
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def initialize(uri, options)
|
|
20
|
-
@uri = uri
|
|
21
|
-
@options = options
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def method_missing(method, *params)
|
|
25
|
-
invoke_request(method, *params)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def perform_request(method, params: nil, type: :request, read_timeout: nil, write_timeout: nil)
|
|
29
|
-
validate_request(params, type)
|
|
30
|
-
request = create_message(method.to_s, params)
|
|
31
|
-
if type == :request
|
|
32
|
-
id = generate_id
|
|
33
|
-
request['id'] = id
|
|
34
|
-
response = send_command serialize_request(request), read_timeout: read_timeout, write_timeout: write_timeout
|
|
35
|
-
response = deserialize_response(response)
|
|
36
|
-
|
|
37
|
-
validate_response(response, id)
|
|
38
|
-
parse_error(response['error']) if response.has_key?('error')
|
|
39
|
-
|
|
40
|
-
response['result']
|
|
41
|
-
else
|
|
42
|
-
send_notification serialize_request(request), write_timeout: write_timeout
|
|
43
|
-
nil
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def invoke_request(method, *params)
|
|
48
|
-
warn '[DEPRECATION] `invoke_request` is deprecated. Please use `perform_request` instead.'
|
|
49
|
-
params = nil if params.empty?
|
|
50
|
-
perform_request(method, params: params)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def invoke_notification(method, *params)
|
|
54
|
-
warn '[DEPRECATION] `invoke_request` is deprecated. Please use `perform_request` instead.'
|
|
55
|
-
params = nil if params.empty?
|
|
56
|
-
perform_request(method, params: params, type: :notification)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
|
|
61
|
-
def serialize_request(request)
|
|
62
|
-
Oj.dump(request, mode: :compat)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def deserialize_response(response)
|
|
66
|
-
Oj.load(response)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def validate_response(response, id)
|
|
70
|
-
raise ClientError, 'Wrong response structure' unless response.is_a?(Hash)
|
|
71
|
-
raise ClientError, 'Wrong version' if response['jsonrpc'] != JRPC::JSON_RPC_VERSION
|
|
72
|
-
if id != response['id']
|
|
73
|
-
raise ClientError, "ID response mismatch. expected #{id.inspect} got #{response['id'].inspect}"
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def validate_request(params, type)
|
|
78
|
-
raise ClientError, 'invalid type' unless REQUEST_TYPES.include?(type)
|
|
79
|
-
raise ClientError, 'invalid params' if !params.nil? && !params.is_a?(Array) && !params.is_a?(Hash)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def parse_error(error)
|
|
83
|
-
case error['code']
|
|
84
|
-
when -32700
|
|
85
|
-
raise ParseError.new(error['message'])
|
|
86
|
-
when -32600
|
|
87
|
-
raise InvalidRequest.new(error['message'])
|
|
88
|
-
when -32601
|
|
89
|
-
raise MethodNotFound.new(error['message'])
|
|
90
|
-
when -32602
|
|
91
|
-
raise InvalidParams.new(error['message'])
|
|
92
|
-
when -32603
|
|
93
|
-
raise InternalError.new(error['message'])
|
|
94
|
-
when -32099..-32000
|
|
95
|
-
raise InternalServerError.new(error['message'], error['code'])
|
|
96
|
-
else
|
|
97
|
-
raise UnknownError.new(error['message'], error['code'])
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def send_command(json, options={})
|
|
102
|
-
raise NotImplementedError
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def send_notification(json, options={})
|
|
106
|
-
raise NotImplementedError
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def generate_id
|
|
110
|
-
size = ID_CHARACTERS.size
|
|
111
|
-
(0...32).map { ID_CHARACTERS.to_a[rand(size)] }.join
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def create_message(method, params)
|
|
115
|
-
message = {
|
|
116
|
-
'jsonrpc' => JSON_RPC_VERSION,
|
|
117
|
-
'method' => method
|
|
118
|
-
}
|
|
119
|
-
message['params'] = params unless params.nil?
|
|
120
|
-
message
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
data/lib/jrpc/error/error.rb
DELETED