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
data/lib/jrpc/tcp_client.rb
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
require 'netstring'
|
|
2
|
-
require 'logger'
|
|
3
|
-
require 'benchmark'
|
|
4
|
-
module JRPC
|
|
5
|
-
class TcpClient < BaseClient
|
|
6
|
-
attr_reader :namespace, :transport
|
|
7
|
-
attr_accessor :logger
|
|
8
|
-
def_delegators :@transport, :close, :closed?, :connect
|
|
9
|
-
|
|
10
|
-
MAX_LOGGED_MESSAGE_LENGTH = 255
|
|
11
|
-
|
|
12
|
-
def initialize(uri, options = {})
|
|
13
|
-
super
|
|
14
|
-
@logger = @options.delete(:logger) || Logger.new($null)
|
|
15
|
-
@namespace = @options.delete(:namespace).to_s
|
|
16
|
-
|
|
17
|
-
timeout = @options.fetch(:timeout, 5)
|
|
18
|
-
connect_timeout = @options.fetch(:connect_timeout, timeout)
|
|
19
|
-
read_timeout = @options.fetch(:read_timeout, timeout)
|
|
20
|
-
write_timeout = @options.fetch(:write_timeout, 60) # default 60
|
|
21
|
-
connect_retry_count = @options.fetch(:connect_retry_count, 10) # default 10
|
|
22
|
-
@close_after_sent = @options.fetch(:close_after_sent, false)
|
|
23
|
-
|
|
24
|
-
@transport = JRPC::Transport::SocketTcp.new server: @uri,
|
|
25
|
-
connect_retry_count: connect_retry_count,
|
|
26
|
-
connect_timeout: connect_timeout,
|
|
27
|
-
read_timeout: read_timeout,
|
|
28
|
-
write_timeout: write_timeout
|
|
29
|
-
begin
|
|
30
|
-
@transport.connect
|
|
31
|
-
rescue JRPC::Transport::SocketTcp::Error
|
|
32
|
-
raise ConnectionError, "Can't connect to #{@uri}"
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
def ensure_connected
|
|
39
|
-
if @transport.closed?
|
|
40
|
-
logger.debug { 'Connecting transport...' }
|
|
41
|
-
@transport.connect
|
|
42
|
-
logger.debug { 'Connected.' }
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def send_command(request, options = {})
|
|
47
|
-
ensure_connected
|
|
48
|
-
read_timeout = options.fetch(:read_timeout)
|
|
49
|
-
write_timeout = options.fetch(:write_timeout)
|
|
50
|
-
response = nil
|
|
51
|
-
t = Benchmark.realtime do
|
|
52
|
-
logger.debug { "Request address: #{uri}" }
|
|
53
|
-
logger.debug { "Request message: #{Utils.truncate(request, MAX_LOGGED_MESSAGE_LENGTH)}" }
|
|
54
|
-
logger.debug { "Request read_timeout: #{read_timeout}" }
|
|
55
|
-
logger.debug { "Request write_timeout: #{write_timeout}" }
|
|
56
|
-
send_request(request, write_timeout)
|
|
57
|
-
response = receive_response(read_timeout)
|
|
58
|
-
end
|
|
59
|
-
logger.debug do
|
|
60
|
-
"(#{'%.2f' % (t * 1000)}ms) Response message: #{Utils.truncate(response, MAX_LOGGED_MESSAGE_LENGTH)}"
|
|
61
|
-
end
|
|
62
|
-
response
|
|
63
|
-
ensure
|
|
64
|
-
@transport.close if @close_after_sent
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def send_notification(request, options = {})
|
|
68
|
-
ensure_connected
|
|
69
|
-
write_timeout = options.fetch(:write_timeout)
|
|
70
|
-
logger.debug { "Request address: #{uri}" }
|
|
71
|
-
logger.debug { "Request message: #{Utils.truncate(request, MAX_LOGGED_MESSAGE_LENGTH)}" }
|
|
72
|
-
logger.debug { "Request write_timeout: #{write_timeout}" }
|
|
73
|
-
send_request(request, write_timeout)
|
|
74
|
-
logger.debug { 'No response required' }
|
|
75
|
-
ensure
|
|
76
|
-
@transport.close if @close_after_sent
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def create_message(method, params)
|
|
80
|
-
super("#{namespace}#{method}", params)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def send_request(request, timeout)
|
|
84
|
-
timeout ||= @transport.write_timeout
|
|
85
|
-
@transport.write Netstring.dump(request.to_s), timeout
|
|
86
|
-
rescue ::SocketError
|
|
87
|
-
raise ConnectionError, "Can't send request to #{uri}"
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def receive_response(timeout)
|
|
91
|
-
timeout ||= @transport.read_timeout
|
|
92
|
-
length = get_msg_length(timeout)
|
|
93
|
-
response = @transport.read(length + 1, timeout)
|
|
94
|
-
raise ClientError.new('invalid response. missed comma as terminator') if response[-1] != ','
|
|
95
|
-
response.chomp(',')
|
|
96
|
-
rescue ::SocketError
|
|
97
|
-
raise ConnectionError, "Can't receive response from #{uri}"
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def get_msg_length(timeout)
|
|
101
|
-
length = ''
|
|
102
|
-
while true do
|
|
103
|
-
character = @transport.read(1, timeout)
|
|
104
|
-
break if character == ':'
|
|
105
|
-
length += character
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
Integer(length)
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
end
|
|
112
|
-
end
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
module JRPC
|
|
2
|
-
module Transport
|
|
3
|
-
class SocketBase
|
|
4
|
-
|
|
5
|
-
class Error < ::JRPC::Error
|
|
6
|
-
end
|
|
7
|
-
|
|
8
|
-
class TimeoutError < Error
|
|
9
|
-
def initialize
|
|
10
|
-
super(self.class.to_s.split('::').last)
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
class ReadTimeoutError < TimeoutError
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
class WriteTimeoutError < TimeoutError
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
class ConnectionTimeoutError < TimeoutError
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
class ConnectionFailedError < Error
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
class WriteFailedError < Error
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
class ReadFailedError < Error
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
attr_reader :options, :read_timeout, :write_timeout
|
|
33
|
-
|
|
34
|
-
def self.connect(options)
|
|
35
|
-
connection = new(options)
|
|
36
|
-
yield(connection)
|
|
37
|
-
ensure
|
|
38
|
-
connection.close if connection
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def initialize(options)
|
|
42
|
-
@server = options.fetch(:server)
|
|
43
|
-
@read_timeout = options.fetch(:read_timeout, nil)
|
|
44
|
-
@write_timeout = options.fetch(:write_timeout, nil)
|
|
45
|
-
@connect_timeout = options.fetch(:connect_timeout, nil)
|
|
46
|
-
@connect_retry_count = options.fetch(:connect_retry_count, 0)
|
|
47
|
-
@options = options
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def connect
|
|
51
|
-
retries = @connect_retry_count
|
|
52
|
-
|
|
53
|
-
while retries >= 0
|
|
54
|
-
begin
|
|
55
|
-
connect_socket
|
|
56
|
-
break
|
|
57
|
-
rescue Error => e
|
|
58
|
-
retries -= 1
|
|
59
|
-
raise e if retries < 0
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def read(_length, _timeout = @read_timeout)
|
|
65
|
-
raise NotImplementedError
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def write(_data, _timeout = @write_timeout)
|
|
69
|
-
raise NotImplementedError
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def close
|
|
73
|
-
raise NotImplementedError
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def closed?
|
|
77
|
-
raise NotImplementedError
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
private
|
|
81
|
-
|
|
82
|
-
def connect_socket
|
|
83
|
-
raise NotImplementedError
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
end
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
module JRPC
|
|
2
|
-
module Transport
|
|
3
|
-
class SocketTcp < SocketBase
|
|
4
|
-
|
|
5
|
-
def read(length, timeout = @read_timeout)
|
|
6
|
-
received = ''
|
|
7
|
-
length_to_read = length
|
|
8
|
-
while length_to_read > 0
|
|
9
|
-
io_read, = IO.select([socket], [], [], timeout)
|
|
10
|
-
raise ReadTimeoutError unless io_read
|
|
11
|
-
chunk = io_read[0].read_nonblock(length_to_read)
|
|
12
|
-
received += chunk
|
|
13
|
-
length_to_read -= chunk.bytesize
|
|
14
|
-
end
|
|
15
|
-
received
|
|
16
|
-
rescue Errno::EPIPE, EOFError => e
|
|
17
|
-
# EPIPE, in this case, means that the data connection was unexpectedly terminated.
|
|
18
|
-
clear_socket!
|
|
19
|
-
raise ReadFailedError, "#{e.class} #{e.message}"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def write(data, timeout = @write_timeout)
|
|
23
|
-
length_written = 0
|
|
24
|
-
data_to_write = data
|
|
25
|
-
while data_to_write.bytesize > 0
|
|
26
|
-
_, io_write, = IO.select([], [socket], [], timeout)
|
|
27
|
-
raise WriteTimeoutError unless io_write
|
|
28
|
-
chunk_length = io_write[0].write_nonblock(data_to_write)
|
|
29
|
-
length_written += chunk_length
|
|
30
|
-
data_to_write = data.byteslice(length_written, data.length)
|
|
31
|
-
end
|
|
32
|
-
length_written
|
|
33
|
-
rescue Errno::EPIPE => e
|
|
34
|
-
# EPIPE, in this case, means that the data connection was unexpectedly terminated.
|
|
35
|
-
clear_socket!
|
|
36
|
-
raise WriteFailedError, "#{e.class} #{e.message}"
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def close
|
|
40
|
-
return if @socket.nil?
|
|
41
|
-
socket.close
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def closed?
|
|
45
|
-
@socket.nil? || socket.closed?
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def socket
|
|
49
|
-
@socket ||= build_socket
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
def clear_socket!
|
|
55
|
-
return if @socket.nil?
|
|
56
|
-
@socket.close unless @socket.closed?
|
|
57
|
-
@socket = nil
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def set_timeout_to(socket, type, value)
|
|
61
|
-
secs = Integer(value)
|
|
62
|
-
u_secs = Integer((value - secs) * 1_000_000)
|
|
63
|
-
opt_val = [secs, u_secs].pack('l_2')
|
|
64
|
-
socket.setsockopt Socket::SOL_SOCKET, type, opt_val
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def build_socket
|
|
68
|
-
host = @server.split(':').first
|
|
69
|
-
addr = Socket.getaddrinfo(host, nil)
|
|
70
|
-
Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def connect_socket
|
|
74
|
-
host, port = @server.split(':')
|
|
75
|
-
addr = Socket.getaddrinfo(host, nil)
|
|
76
|
-
full_addr = Socket.pack_sockaddr_in(port, addr[0][3])
|
|
77
|
-
|
|
78
|
-
begin
|
|
79
|
-
socket.connect_nonblock(full_addr)
|
|
80
|
-
rescue IO::WaitWritable => _
|
|
81
|
-
if IO.select(nil, [socket], nil, @connect_timeout)
|
|
82
|
-
socket.connect_nonblock(full_addr)
|
|
83
|
-
else
|
|
84
|
-
clear_socket!
|
|
85
|
-
raise ConnectionFailedError, "Can't connect during #{@connect_timeout}"
|
|
86
|
-
end
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
rescue Errno::EISCONN => _
|
|
90
|
-
# already connected
|
|
91
|
-
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::EPIPE => e
|
|
92
|
-
clear_socket!
|
|
93
|
-
raise ConnectionFailedError, "#{e.class} #{e.message}"
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|