jrpc 1.1.7 → 1.1.8
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/.gitignore +1 -0
- data/CHANGELOG.md +39 -0
- data/bin/console +14 -0
- data/bin/jrpc +100 -0
- data/bin/jrpc-shell +99 -0
- data/bin/setup +8 -0
- data/jrpc.gemspec +6 -3
- data/lib/jrpc.rb +2 -0
- data/lib/jrpc/transport/socket_tcp.rb +35 -1
- data/lib/jrpc/version.rb +1 -1
- metadata +18 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e116c8eef34ce5bd8a4ced0e2e693ad37df69895e67c3d28d68537c60711d012
|
4
|
+
data.tar.gz: 8a60df19ba2676a28c03925d0be33b8cda720f9da147e4854a87cba9f25dd251
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10a940570df20f1246c61c6df6d2c950e9acf5e16e397727c6693822f121bfeff14409529b732e385f2f8806ed9c26d32ddcd9c545406ab54c3b26b5e9ee6db2
|
7
|
+
data.tar.gz: e79fc4f45058b4aa8570928866d97fb9e473b805fbc2c730879d0d6478824e91c9a37728900fde9e827a58753978b3b9468dd03d145d6c2cd09310bafceb49e8
|
data/.gitignore
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
### Unreleased
|
4
|
+
|
5
|
+
### 1.1.8
|
6
|
+
* handling FIN signal for TCP socket [didww/jrpc#19](https://github.com/didww/jrpc/pull/19)
|
7
|
+
* add gem executables [didww/jrpc#19](https://github.com/didww/jrpc/pull/19)
|
8
|
+
|
9
|
+
### 1.1.7
|
10
|
+
* connect ot socket in nonblock mode
|
11
|
+
|
12
|
+
### 1.1.6
|
13
|
+
* update oj version to ~> 3.0
|
14
|
+
|
15
|
+
### 1.1.5
|
16
|
+
* update oj version to ~> 2.0
|
17
|
+
|
18
|
+
### 1.1.4
|
19
|
+
* handle EOF on read
|
20
|
+
* fix jrpc error require
|
21
|
+
* use JRPC::Error as base class for JRPC::Transport::SocketBase::Error
|
22
|
+
|
23
|
+
### 1.1.3
|
24
|
+
* close socket when clearing socket if it's not closed
|
25
|
+
|
26
|
+
### 1.1.2
|
27
|
+
* reset socket when broken pipe error appears
|
28
|
+
|
29
|
+
### 1.1.1
|
30
|
+
* fix rescuing error in TcpClient initializer
|
31
|
+
|
32
|
+
### 1.1.0
|
33
|
+
* use own socket wrapper
|
34
|
+
|
35
|
+
### 1.0.1
|
36
|
+
* Net::TCPClient#read method process data with buffer variable
|
37
|
+
|
38
|
+
### 1.0.0
|
39
|
+
* stable release
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'jrpc'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/jrpc
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'jrpc'
|
6
|
+
|
7
|
+
Options = Struct.new(
|
8
|
+
:host,
|
9
|
+
:port,
|
10
|
+
:type,
|
11
|
+
:method,
|
12
|
+
:params,
|
13
|
+
:id,
|
14
|
+
:debug,
|
15
|
+
:namespace,
|
16
|
+
:timeout
|
17
|
+
)
|
18
|
+
|
19
|
+
class Parser
|
20
|
+
def self.parse(argv)
|
21
|
+
args = Options.new
|
22
|
+
args.host = '127.0.0.1'
|
23
|
+
args.port = 7080
|
24
|
+
args.type = 'request'
|
25
|
+
args.timeout = 5
|
26
|
+
|
27
|
+
opt_parser = OptionParser.new do |opts|
|
28
|
+
opts.banner = 'Usage: jrpc [options] method [params, ...]'
|
29
|
+
|
30
|
+
opts.on('--host=HOST', 'host (default 127.0.0.1)') do |host|
|
31
|
+
args.host = host
|
32
|
+
end
|
33
|
+
|
34
|
+
opts.on('-p=PORT', '--port=PORT', 'port (default 7080)') do |port|
|
35
|
+
args.port = port
|
36
|
+
end
|
37
|
+
|
38
|
+
opts.on('-r', '--request', 'Sets type to request (default true)') do
|
39
|
+
args.type = 'request'
|
40
|
+
end
|
41
|
+
|
42
|
+
opts.on('-n', '--notification', 'Sets type to is notification (default false)') do
|
43
|
+
args.type = 'notification'
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on('--namespace=NAMESPACE', 'Sets method namespace') do |namespace|
|
47
|
+
args.namespace = namespace
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on('--id=ID', 'Request ID (will be generated randomly by default)') do |id|
|
51
|
+
args.id = id
|
52
|
+
end
|
53
|
+
|
54
|
+
opts.on('--timeout=TIMEOUT', 'timeout for socket') do |timeout|
|
55
|
+
args.timeout = timeout
|
56
|
+
end
|
57
|
+
|
58
|
+
opts.on('-d', '--debug', 'Debug output') do
|
59
|
+
args.debug = true
|
60
|
+
end
|
61
|
+
|
62
|
+
opts.on('-h', '--help', 'Prints this help and exit') do
|
63
|
+
puts opts
|
64
|
+
exit
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on('-v', '--version', 'Prints version and exit') do
|
68
|
+
puts "JRPC version: #{JRPC::VERSION}"
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
opt_parser.parse!(argv)
|
74
|
+
args.method = argv.first
|
75
|
+
args.params = argv[1..-1]
|
76
|
+
# puts "PARSED:\n#{args.inspect}\n#{argv.inspect}"
|
77
|
+
return args
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
options = Parser.parse(ARGV.dup)
|
82
|
+
|
83
|
+
logger = Logger.new($stdout)
|
84
|
+
logger.level = options.debug ? Logger::DEBUG : Logger::INFO
|
85
|
+
addr = "#{options.host}:#{options.port}"
|
86
|
+
logger.debug { "Connecting to #{addr} ..." }
|
87
|
+
client = JRPC::TcpClient.new(addr, namespace: options.namespace, timeout: options.timeout, logger: logger)
|
88
|
+
|
89
|
+
logger.debug { "Sending #{options.type} #{options.method} #{options.params} ..." }
|
90
|
+
response = client.perform_request(options.method, params: options.params, type: options.type.to_sym)
|
91
|
+
|
92
|
+
if options.type == 'request'
|
93
|
+
logger.debug { "Request was sent. Response: #{response.inspect}" }
|
94
|
+
puts JSON.pretty_generate(response)
|
95
|
+
else
|
96
|
+
logger.debug 'Notification was sent.'
|
97
|
+
end
|
98
|
+
|
99
|
+
client.close
|
100
|
+
logger.debug 'Exited'
|
data/bin/jrpc-shell
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
require 'readline'
|
5
|
+
require 'singleton'
|
6
|
+
require 'jrpc'
|
7
|
+
|
8
|
+
class Command
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
def self.call(command, args)
|
12
|
+
meth = "cmd_#{command}"
|
13
|
+
if instance.respond_to?(meth)
|
14
|
+
instance.public_send(meth, *args)
|
15
|
+
else
|
16
|
+
"ERROR: invalid command #{command.inspect}\n#{instance.help_usage}"
|
17
|
+
end
|
18
|
+
rescue ArgumentError => e
|
19
|
+
"ERROR: ArgumentError #{e.message}\n#{instance.help_usage}"
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :logger, :client, :help_usage
|
23
|
+
instance.logger = Logger.new(STDOUT)
|
24
|
+
instance.logger.level = Logger::INFO
|
25
|
+
instance.help_usage = [
|
26
|
+
'Usage:',
|
27
|
+
' connect host port',
|
28
|
+
' disconnect',
|
29
|
+
' request method param1 param2',
|
30
|
+
' request method {"param1": 1. "param2": 2}',
|
31
|
+
' notification method param1 param2',
|
32
|
+
' notification method {"param1": 1. "param2": 2}',
|
33
|
+
' help',
|
34
|
+
' version'
|
35
|
+
].join("\n")
|
36
|
+
|
37
|
+
def cmd_help
|
38
|
+
help_usage
|
39
|
+
end
|
40
|
+
|
41
|
+
def cmd_version
|
42
|
+
"JRPC version: #{JRPC::VERSION}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def cmd_connect(host, port)
|
46
|
+
client&.close
|
47
|
+
self.client = JRPC::TcpClient.new("#{host}:#{port}", namespace: '', timeout: 5, logger: logger)
|
48
|
+
'Connected.'
|
49
|
+
rescue JRPC::Error => e
|
50
|
+
"ERROR: JRPC #{e.message}\n#{help_usage}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def cmd_disconnect
|
54
|
+
return "ERROR: Not connected\n#{help_usage}" if client.nil?
|
55
|
+
|
56
|
+
client.close
|
57
|
+
self.client = nil
|
58
|
+
'Disconnected'
|
59
|
+
end
|
60
|
+
|
61
|
+
def cmd_request(method, *params)
|
62
|
+
return "ERROR: Not connected\n#{help_usage}" if client.nil?
|
63
|
+
|
64
|
+
params = JSON.parse(params.first) if params.size == 1 && params[0] == '{'
|
65
|
+
|
66
|
+
response = client.perform_request(method, params: params)
|
67
|
+
JSON.pretty_generate(response)
|
68
|
+
rescue JRPC::Error => e
|
69
|
+
"ERROR: JRPC #{e.message}\n#{help_usage}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def cmd_notification(method, *params)
|
73
|
+
return "ERROR: Not connected\n#{help_usage}" if client.nil?
|
74
|
+
|
75
|
+
params = JSON.parse(params.first) if params.size == 1 && params[0] == '{'
|
76
|
+
|
77
|
+
response = client.perform_request(method, params: params, type: :notification)
|
78
|
+
JSON.pretty_generate(response)
|
79
|
+
rescue JRPC::Error => e
|
80
|
+
"ERROR: JRPC #{e.message}\n#{help_usage}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
puts 'Welcome to JRPC shell'
|
85
|
+
while input = Readline.readline('> ', true)
|
86
|
+
if %w[exit close quit].include?(input)
|
87
|
+
break
|
88
|
+
elsif input == 'hist'
|
89
|
+
puts Readline::HISTORY.to_a
|
90
|
+
elsif input == ''
|
91
|
+
# Remove blank lines from history
|
92
|
+
Readline::HISTORY.pop
|
93
|
+
else
|
94
|
+
command, *args = input.split(' ')
|
95
|
+
puts Command.call(command, args)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
puts 'Shell Exited'
|
data/bin/setup
ADDED
data/jrpc.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.summary = 'JSON RPC client'
|
13
13
|
spec.description = 'JSON RPC client over TCP'
|
14
|
-
spec.homepage = 'https://github.com/
|
14
|
+
spec.homepage = 'https://github.com/didww/jrpc'
|
15
15
|
spec.license = 'MIT'
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
@@ -20,7 +20,10 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.add_dependency 'netstring', '~> 0'
|
21
21
|
spec.add_dependency 'oj', '~> 3.0'
|
22
22
|
|
23
|
-
spec.
|
24
|
-
spec.
|
23
|
+
spec.executables << 'jrpc'
|
24
|
+
spec.executables << 'jrpc-shell'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler'
|
27
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
25
28
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
29
|
end
|
data/lib/jrpc.rb
CHANGED
@@ -8,6 +8,7 @@ module JRPC
|
|
8
8
|
while length_to_read > 0
|
9
9
|
io_read, = IO.select([socket], [], [], timeout)
|
10
10
|
raise ReadTimeoutError unless io_read
|
11
|
+
check_fin_signal
|
11
12
|
chunk = io_read[0].read_nonblock(length_to_read)
|
12
13
|
received += chunk
|
13
14
|
length_to_read -= chunk.bytesize
|
@@ -25,6 +26,7 @@ module JRPC
|
|
25
26
|
while data_to_write.bytesize > 0
|
26
27
|
_, io_write, = IO.select([], [socket], [], timeout)
|
27
28
|
raise WriteTimeoutError unless io_write
|
29
|
+
check_fin_signal
|
28
30
|
chunk_length = io_write[0].write_nonblock(data_to_write)
|
29
31
|
length_written += chunk_length
|
30
32
|
data_to_write = data.byteslice(length_written, data.length)
|
@@ -41,8 +43,27 @@ module JRPC
|
|
41
43
|
socket.close
|
42
44
|
end
|
43
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.
|
44
50
|
def closed?
|
45
|
-
@socket.nil? || socket.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?
|
46
67
|
end
|
47
68
|
|
48
69
|
def socket
|
@@ -51,6 +72,19 @@ module JRPC
|
|
51
72
|
|
52
73
|
private
|
53
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
|
+
|
54
88
|
def clear_socket!
|
55
89
|
return if @socket.nil?
|
56
90
|
@socket.close unless @socket.closed?
|
data/lib/jrpc/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jrpc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1.
|
4
|
+
version: 1.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Denis Talakevich
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: netstring
|
@@ -42,30 +42,30 @@ dependencies:
|
|
42
42
|
name: bundler
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - ">="
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '13.0'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '13.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -83,17 +83,24 @@ dependencies:
|
|
83
83
|
description: JSON RPC client over TCP
|
84
84
|
email:
|
85
85
|
- senid231@gmail.com
|
86
|
-
executables:
|
86
|
+
executables:
|
87
|
+
- jrpc
|
88
|
+
- jrpc-shell
|
87
89
|
extensions: []
|
88
90
|
extra_rdoc_files: []
|
89
91
|
files:
|
90
92
|
- ".gitignore"
|
91
93
|
- ".rspec"
|
92
94
|
- ".travis.yml"
|
95
|
+
- CHANGELOG.md
|
93
96
|
- Gemfile
|
94
97
|
- LICENSE.txt
|
95
98
|
- README.md
|
96
99
|
- Rakefile
|
100
|
+
- bin/console
|
101
|
+
- bin/jrpc
|
102
|
+
- bin/jrpc-shell
|
103
|
+
- bin/setup
|
97
104
|
- jrpc.gemspec
|
98
105
|
- lib/jrpc.rb
|
99
106
|
- lib/jrpc/base_client.rb
|
@@ -113,7 +120,7 @@ files:
|
|
113
120
|
- lib/jrpc/transport/socket_tcp.rb
|
114
121
|
- lib/jrpc/utils.rb
|
115
122
|
- lib/jrpc/version.rb
|
116
|
-
homepage: https://github.com/
|
123
|
+
homepage: https://github.com/didww/jrpc
|
117
124
|
licenses:
|
118
125
|
- MIT
|
119
126
|
metadata: {}
|
@@ -132,7 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
139
|
- !ruby/object:Gem::Version
|
133
140
|
version: '0'
|
134
141
|
requirements: []
|
135
|
-
rubygems_version: 3.
|
142
|
+
rubygems_version: 3.0.8
|
136
143
|
signing_key:
|
137
144
|
specification_version: 4
|
138
145
|
summary: JSON RPC client
|