jrpc 1.1.7 → 1.1.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|