jrpc 1.1.8 → 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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +55 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +228 -0
  5. data/CHANGELOG.md +45 -0
  6. data/Gemfile +17 -0
  7. data/README.md +163 -13
  8. data/Rakefile +3 -1
  9. data/bin/console +1 -0
  10. data/bin/jrpc +37 -26
  11. data/bin/jrpc-shell +34 -24
  12. data/jrpc.gemspec +6 -8
  13. data/lib/jrpc/errors.rb +65 -0
  14. data/lib/jrpc/id_generator.rb +22 -0
  15. data/lib/jrpc/message.rb +78 -0
  16. data/lib/jrpc/shared_client/outbound_queue.rb +71 -0
  17. data/lib/jrpc/shared_client/registry.rb +46 -0
  18. data/lib/jrpc/shared_client/ticket.rb +84 -0
  19. data/lib/jrpc/shared_client/transport_loop.rb +290 -0
  20. data/lib/jrpc/shared_client.rb +194 -0
  21. data/lib/jrpc/simple_client.rb +89 -0
  22. data/lib/jrpc/transport/base.rb +60 -0
  23. data/lib/jrpc/transport/tcp.rb +243 -0
  24. data/lib/jrpc/transport.rb +12 -0
  25. data/lib/jrpc/version.rb +3 -1
  26. data/lib/jrpc.rb +13 -16
  27. metadata +23 -71
  28. data/.travis.yml +0 -4
  29. data/lib/jrpc/base_client.rb +0 -123
  30. data/lib/jrpc/error/client_error.rb +0 -5
  31. data/lib/jrpc/error/connection_error.rb +0 -11
  32. data/lib/jrpc/error/error.rb +0 -5
  33. data/lib/jrpc/error/internal_error.rb +0 -9
  34. data/lib/jrpc/error/internal_server_error.rb +0 -5
  35. data/lib/jrpc/error/invalid_params.rb +0 -9
  36. data/lib/jrpc/error/invalid_request.rb +0 -9
  37. data/lib/jrpc/error/method_not_found.rb +0 -9
  38. data/lib/jrpc/error/parse_error.rb +0 -9
  39. data/lib/jrpc/error/server_error.rb +0 -11
  40. data/lib/jrpc/error/unknown_error.rb +0 -5
  41. data/lib/jrpc/tcp_client.rb +0 -112
  42. data/lib/jrpc/transport/socket_base.rb +0 -88
  43. data/lib/jrpc/transport/socket_tcp.rb +0 -132
  44. data/lib/jrpc/utils.rb +0 -9
@@ -1,5 +0,0 @@
1
- module JRPC
2
- class UnknownError < ServerError
3
-
4
- end
5
- end
@@ -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,132 +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
- check_fin_signal
12
- chunk = io_read[0].read_nonblock(length_to_read)
13
- received += chunk
14
- length_to_read -= chunk.bytesize
15
- end
16
- received
17
- rescue Errno::EPIPE, EOFError => e
18
- # EPIPE, in this case, means that the data connection was unexpectedly terminated.
19
- clear_socket!
20
- raise ReadFailedError, "#{e.class} #{e.message}"
21
- end
22
-
23
- def write(data, timeout = @write_timeout)
24
- length_written = 0
25
- data_to_write = data
26
- while data_to_write.bytesize > 0
27
- _, io_write, = IO.select([], [socket], [], timeout)
28
- raise WriteTimeoutError unless io_write
29
- check_fin_signal
30
- chunk_length = io_write[0].write_nonblock(data_to_write)
31
- length_written += chunk_length
32
- data_to_write = data.byteslice(length_written, data.length)
33
- end
34
- length_written
35
- rescue Errno::EPIPE => e
36
- # EPIPE, in this case, means that the data connection was unexpectedly terminated.
37
- clear_socket!
38
- raise WriteFailedError, "#{e.class} #{e.message}"
39
- end
40
-
41
- def close
42
- return if @socket.nil?
43
- socket.close
44
- end
45
-
46
- # Socket implementation allows client to send data to server after FIN,
47
- # but server will never receive this data.
48
- # So we consider socket closed when it have FIN event
49
- # and close it correctly from client side.
50
- def closed?
51
- return true if @socket.nil? || socket.closed?
52
-
53
- if fin_signal?
54
- close
55
- return true
56
- end
57
-
58
- false
59
- end
60
-
61
- # Socket implementation allows client to send data to server after FIN,
62
- # but server will never receive this data.
63
- # We correctly close socket from client side when FIN event received.
64
- # Should be checked before send data to socket or recv data from socket.
65
- def check_fin_signal
66
- close if socket && !socket.closed? && fin_signal?
67
- end
68
-
69
- def socket
70
- @socket ||= build_socket
71
- end
72
-
73
- private
74
-
75
- # when recv_nonblock(1) responds with empty string means that FIN event was received.
76
- # in other cases it will return 1 byte string or raise EAGAINWaitReadable.
77
- # MSG_PEEK means we do not move pointer when reading data.
78
- # see https://apidock.com/ruby/BasicSocket/recv_nonblock
79
- def fin_signal?
80
- begin
81
- resp = socket.recv_nonblock(1, Socket::MSG_PEEK)
82
- rescue IO::EAGAINWaitReadable => _
83
- resp = nil
84
- end
85
- resp == ''
86
- end
87
-
88
- def clear_socket!
89
- return if @socket.nil?
90
- @socket.close unless @socket.closed?
91
- @socket = nil
92
- end
93
-
94
- def set_timeout_to(socket, type, value)
95
- secs = Integer(value)
96
- u_secs = Integer((value - secs) * 1_000_000)
97
- opt_val = [secs, u_secs].pack('l_2')
98
- socket.setsockopt Socket::SOL_SOCKET, type, opt_val
99
- end
100
-
101
- def build_socket
102
- host = @server.split(':').first
103
- addr = Socket.getaddrinfo(host, nil)
104
- Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0)
105
- end
106
-
107
- def connect_socket
108
- host, port = @server.split(':')
109
- addr = Socket.getaddrinfo(host, nil)
110
- full_addr = Socket.pack_sockaddr_in(port, addr[0][3])
111
-
112
- begin
113
- socket.connect_nonblock(full_addr)
114
- rescue IO::WaitWritable => _
115
- if IO.select(nil, [socket], nil, @connect_timeout)
116
- socket.connect_nonblock(full_addr)
117
- else
118
- clear_socket!
119
- raise ConnectionFailedError, "Can't connect during #{@connect_timeout}"
120
- end
121
- end
122
-
123
- rescue Errno::EISCONN => _
124
- # already connected
125
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::EPIPE => e
126
- clear_socket!
127
- raise ConnectionFailedError, "#{e.class} #{e.message}"
128
- end
129
-
130
- end
131
- end
132
- end
data/lib/jrpc/utils.rb DELETED
@@ -1,9 +0,0 @@
1
- module JRPC
2
- class Utils
3
-
4
- def self.truncate(string, length, ommiter = '...')
5
- "#{string[0..length]}#{ommiter if string.length > length}"
6
- end
7
-
8
- end
9
- end