io_request 2.2.0 → 2.5.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/lib/io_request.rb +10 -1
- data/lib/io_request/client.rb +63 -39
- data/lib/io_request/connection/ssl_sockets.rb +37 -16
- data/lib/io_request/message.rb +2 -2
- data/lib/io_request/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53fdb5fa823faf9d379e6c2180f0d47f79959caa144dd1c34295c81226a4566d
|
4
|
+
data.tar.gz: 8614bb17e707782adc8d13ea01d0328bac2432658d0dc91977ee728107dcfcb7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 234a5e6d21fe3b1214a719af4b52617d4f58a3cf205e17cd8fa2d045098ada47bae1a272b8ec1daadfe5909831408e754d6a84085cad3705b5e09c8d3f940ccf
|
7
|
+
data.tar.gz: ef33270ed7cbf3b2082f7845d1ca1bbab2b3e77a145cff65ec4de361fb9b728535b3bd7b6d63bfef28713a216daea9434a6b908459fc3d7be1002142e90e3386
|
data/lib/io_request.rb
CHANGED
@@ -1,7 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Main module.
|
4
|
-
module IORequest
|
4
|
+
module IORequest
|
5
|
+
# Client received message of zero size.
|
6
|
+
class ZeroSizeMessageError < RuntimeError; end
|
7
|
+
|
8
|
+
# Authorization failed.
|
9
|
+
class AuthorizationFailureError < RuntimeError; end
|
10
|
+
|
11
|
+
# Request timed out.
|
12
|
+
class RequestTimeoutError < RuntimeError; end
|
13
|
+
end
|
5
14
|
|
6
15
|
require_relative 'io_request/version'
|
7
16
|
require_relative 'io_request/logging'
|
data/lib/io_request/client.rb
CHANGED
@@ -45,15 +45,17 @@ module IORequest
|
|
45
45
|
# @param r [IO] object to read from.
|
46
46
|
# @param w [IO] object to write to.
|
47
47
|
# @param rw [IO] read-write object (replaces `r` and `w` arguments).
|
48
|
+
# @return [Object] data from {Authorizer}
|
48
49
|
def open(read: nil, write: nil, read_write: nil)
|
49
50
|
@io_r = read_write || read
|
50
51
|
@io_w = read_write || write
|
51
52
|
|
52
53
|
IORequest.logger.debug(prog_name) { 'Starting connection' }
|
53
54
|
|
54
|
-
authorization
|
55
|
+
auth_data = authorization
|
55
56
|
@open = true
|
56
57
|
@data_transition_thread = in_thread(name: 'connection') { data_transition_loop }
|
58
|
+
auth_data
|
57
59
|
end
|
58
60
|
|
59
61
|
def open?
|
@@ -63,30 +65,38 @@ module IORequest
|
|
63
65
|
# Close connection.
|
64
66
|
def close
|
65
67
|
close_internal
|
68
|
+
|
66
69
|
join_threads
|
67
|
-
@open = false
|
68
70
|
end
|
69
71
|
|
70
72
|
# @yieldparam [Hash]
|
71
73
|
# @yieldreturn [Hash]
|
72
|
-
def
|
73
|
-
IORequest.logger.debug(prog_name) { 'Saved
|
74
|
-
@
|
74
|
+
def on_request(&block)
|
75
|
+
IORequest.logger.debug(prog_name) { 'Saved on_request block' }
|
76
|
+
@on_request = block
|
77
|
+
end
|
78
|
+
alias respond on_request
|
79
|
+
|
80
|
+
# Code to execute after connection is closed.
|
81
|
+
def on_close(&block)
|
82
|
+
IORequest.logger.debug(prog_name) { 'Saved on_close block' }
|
83
|
+
@on_close = block
|
75
84
|
end
|
76
85
|
|
77
86
|
# If callback block is provided, request will be sent asynchroniously.
|
78
87
|
# @param data [Hash]
|
79
|
-
|
88
|
+
# @param timeout [Integer] timeout in seconds.
|
89
|
+
def request(data, timeout: nil, &callback)
|
80
90
|
message = Message.new(data, type: :request)
|
81
91
|
|
82
92
|
if block_given?
|
83
93
|
# Async execution of request
|
84
94
|
in_thread(callback, name: 'requesting') do |cb|
|
85
|
-
cb.call(send_request_and_wait_for_response(message).data)
|
95
|
+
cb.call(send_request_and_wait_for_response(message, timeout).data)
|
86
96
|
end
|
87
97
|
nil
|
88
98
|
else
|
89
|
-
send_request_and_wait_for_response(message).data
|
99
|
+
send_request_and_wait_for_response(message, timeout).data
|
90
100
|
end
|
91
101
|
end
|
92
102
|
|
@@ -96,50 +106,52 @@ module IORequest
|
|
96
106
|
|
97
107
|
def close_internal
|
98
108
|
IORequest.logger.debug(prog_name) { 'Closing connection' }
|
99
|
-
|
100
|
-
send_zero_size_request
|
101
|
-
rescue StandardError
|
102
|
-
IORequest.logger.debug(prog_name) { 'Failed to send zero-sized message. Closing anyway' }
|
103
|
-
end
|
104
|
-
stop_data_transition
|
109
|
+
send_zero_size_request
|
105
110
|
close_io
|
106
|
-
end
|
107
|
-
|
108
|
-
def stop_data_transition
|
109
|
-
return unless defined?(@data_transition_thread) && !@data_transition_thread.nil?
|
110
|
-
|
111
|
-
IORequest.logger.debug(prog_name) { 'Killing data transition thread' }
|
112
|
-
@data_transition_thread.kill
|
113
111
|
@data_transition_thread = nil
|
112
|
+
@open = false
|
113
|
+
@on_close&.call if defined?(@on_close)
|
114
114
|
end
|
115
115
|
|
116
116
|
def close_io
|
117
|
-
|
118
|
-
|
119
|
-
|
117
|
+
begin
|
118
|
+
@io_r&.close
|
119
|
+
rescue StandardError => e
|
120
|
+
IORequest.logger.debug "Failed to close read IO: #{e}"
|
121
|
+
end
|
122
|
+
begin
|
123
|
+
@io_w&.close
|
124
|
+
rescue StandardError => e
|
125
|
+
IORequest.logger.debug "Failed to close write IO: #{e}"
|
126
|
+
end
|
127
|
+
IORequest.logger.debug(prog_name) { 'Closed IO streams' }
|
120
128
|
end
|
121
129
|
|
122
130
|
def authorization
|
123
|
-
auth_successful =
|
131
|
+
auth_successful = false
|
132
|
+
data = nil
|
133
|
+
@mutex_r.synchronize do
|
124
134
|
@mutex_w.synchronize do
|
125
135
|
IORequest.logger.debug(prog_name) { 'Authorizing new client' }
|
126
|
-
@authorizer.authorize(@io_r, @io_w)
|
136
|
+
auth_successful = @authorizer.authorize(@io_r, @io_w)
|
137
|
+
data = @authorizer.data
|
127
138
|
end
|
128
139
|
end
|
129
|
-
unless auth_successful
|
130
|
-
IORequest.logger.debug(prog_name) { 'Authorization failed' }
|
131
|
-
raise 'Authorization failed'
|
132
|
-
end
|
140
|
+
raise AuthorizationFailureError unless auth_successful
|
133
141
|
|
134
|
-
IORequest.logger.debug(prog_name) { "New client authorized with data #{
|
142
|
+
IORequest.logger.debug(prog_name) { "New client authorized with data #{data}" }
|
143
|
+
data
|
135
144
|
end
|
136
145
|
|
137
146
|
def data_transition_loop
|
138
147
|
IORequest.logger.debug(prog_name) { 'Starting data transition loop' }
|
139
148
|
loop do
|
140
149
|
data_transition_iteration
|
150
|
+
rescue ZeroSizeMessageError
|
151
|
+
IORequest.logger.debug(prog_name) { 'Connection was closed from the other side' }
|
152
|
+
break
|
141
153
|
rescue StandardError => e
|
142
|
-
IORequest.logger.debug(prog_name) { "Data transition
|
154
|
+
IORequest.logger.debug(prog_name) { "Data transition unknown error: #{e}" }
|
143
155
|
break
|
144
156
|
end
|
145
157
|
close_internal
|
@@ -156,7 +168,8 @@ module IORequest
|
|
156
168
|
end
|
157
169
|
|
158
170
|
def handle_request(message)
|
159
|
-
data =
|
171
|
+
data = {}
|
172
|
+
data = @on_request&.call(message.data) if defined?(@on_request)
|
160
173
|
response = Message.new(data, type: :response, to: message.id)
|
161
174
|
send_response(response)
|
162
175
|
end
|
@@ -171,7 +184,11 @@ module IORequest
|
|
171
184
|
def send_response(response)
|
172
185
|
@mutex_w.synchronize do
|
173
186
|
IORequest.logger.debug(prog_name) { "Sending response: #{response}" }
|
174
|
-
|
187
|
+
begin
|
188
|
+
response.write_to(@io_w)
|
189
|
+
rescue IOError => e
|
190
|
+
IORequest.logger.debug(prog_name) { "Failed to write response message: #{e}" }
|
191
|
+
end
|
175
192
|
end
|
176
193
|
end
|
177
194
|
|
@@ -180,23 +197,30 @@ module IORequest
|
|
180
197
|
IORequest.logger.debug(prog_name) { 'Sending zero size message' }
|
181
198
|
@io_w.write([0].pack('S'))
|
182
199
|
end
|
200
|
+
rescue StandardError => e
|
201
|
+
IORequest.logger.debug(prog_name) { "Failed to send zero-sized message(#{e})" }
|
183
202
|
end
|
184
203
|
|
185
|
-
def send_request_and_wait_for_response(request)
|
204
|
+
def send_request_and_wait_for_response(request, timeout)
|
186
205
|
@mutex_w.synchronize do
|
187
206
|
IORequest.logger.debug(prog_name) { "Sending message: #{request}" }
|
188
207
|
request.write_to(@io_w)
|
189
208
|
end
|
190
|
-
wait_for_response(request)
|
209
|
+
wait_for_response(request, timeout)
|
191
210
|
end
|
192
211
|
|
193
|
-
def wait_for_response(request)
|
212
|
+
def wait_for_response(request, timeout)
|
194
213
|
IORequest.logger.debug(prog_name) { "Waiting for response for #{request}" }
|
214
|
+
waiting_start_time = Time.now
|
195
215
|
@responses_access_mutex.synchronize do
|
196
216
|
response = nil
|
197
217
|
until response
|
198
|
-
@responses_access_cv.wait(@responses_access_mutex)
|
199
|
-
|
218
|
+
@responses_access_cv.wait(@responses_access_mutex, 1)
|
219
|
+
if @responses_access_mutex.owned?
|
220
|
+
# NOTE: Only accessing responses hash if thread owns access mutex
|
221
|
+
response = @responses.delete(request.id.to_s)
|
222
|
+
end
|
223
|
+
raise RequestTimeoutError if timeout && (Time.now - waiting_start_time < timeout)
|
200
224
|
end
|
201
225
|
IORequest.logger.debug(prog_name) { "Found response: #{response}" }
|
202
226
|
response
|
@@ -32,11 +32,20 @@ module IORequest
|
|
32
32
|
end
|
33
33
|
|
34
34
|
# @return [Array<IORequest::Client>]
|
35
|
-
|
35
|
+
def clients
|
36
|
+
@clients_data.keys
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param client [IORequest::Client]
|
40
|
+
# @return [Hash, nil] you are free to store anything you want in hash.
|
41
|
+
# Only field you will find in it is `auth` with authenticator data.
|
42
|
+
def data(client)
|
43
|
+
@clients_data[client]
|
44
|
+
end
|
36
45
|
|
37
46
|
# Start server.
|
38
47
|
def start
|
39
|
-
@
|
48
|
+
@clients_data = {}
|
40
49
|
|
41
50
|
@server = TCPServer.new(@port)
|
42
51
|
|
@@ -45,8 +54,8 @@ module IORequest
|
|
45
54
|
|
46
55
|
# Fully stop server.
|
47
56
|
def stop
|
48
|
-
|
49
|
-
@
|
57
|
+
clients.each(&:close)
|
58
|
+
@clients_data.clear
|
50
59
|
|
51
60
|
@server.close
|
52
61
|
@server = nil
|
@@ -68,7 +77,7 @@ module IORequest
|
|
68
77
|
while (socket = @server.accept)
|
69
78
|
handle_socket(socket)
|
70
79
|
end
|
71
|
-
rescue
|
80
|
+
rescue StandardError
|
72
81
|
stop
|
73
82
|
end
|
74
83
|
|
@@ -76,17 +85,21 @@ module IORequest
|
|
76
85
|
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, @ctx)
|
77
86
|
ssl_socket.accept
|
78
87
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
88
|
+
handle_client(ssl_socket, IORequest::Client.new(authorizer: @authorizer))
|
89
|
+
rescue StandardError => e
|
90
|
+
IORequest.logger.warn "Unknown error while handling sockets: #{e}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def handle_client(ssl_socket, client)
|
94
|
+
auth_data = client.open read_write: ssl_socket
|
95
|
+
client.on_request { |data| @requests_handler.call(data, client) }
|
96
|
+
@clients_data[client] = { auth: auth_data }
|
97
|
+
client.on_close do
|
98
|
+
@clients_data.select! { |c, _d| c.open? }
|
87
99
|
end
|
88
100
|
rescue StandardError => e
|
89
|
-
IORequest.
|
101
|
+
IORequest.logger.debug "Failed to open client: #{e}"
|
102
|
+
ssl_socket.close
|
90
103
|
end
|
91
104
|
end
|
92
105
|
|
@@ -105,9 +118,15 @@ module IORequest
|
|
105
118
|
@authorizer = authorizer
|
106
119
|
@requests_handler = requests_handler
|
107
120
|
|
121
|
+
@client = nil
|
122
|
+
|
108
123
|
initialize_ssl_context(certificate, key)
|
109
124
|
end
|
110
125
|
|
126
|
+
def connected?
|
127
|
+
!@client.nil?
|
128
|
+
end
|
129
|
+
|
111
130
|
# Connect to server.
|
112
131
|
# @param host [String] host of server.
|
113
132
|
# @param port [Integer] port of server.
|
@@ -121,9 +140,11 @@ module IORequest
|
|
121
140
|
@client = IORequest::Client.new authorizer: @authorizer
|
122
141
|
begin
|
123
142
|
@client.open read_write: ssl_socket
|
124
|
-
@client.
|
143
|
+
@client.on_request(&@requests_handler)
|
144
|
+
@client.on_close do
|
145
|
+
@client = nil
|
146
|
+
end
|
125
147
|
rescue StandardError
|
126
|
-
IORequest.debug "Failed to open client: #{e}"
|
127
148
|
ssl_socket.close
|
128
149
|
@client = nil
|
129
150
|
end
|
data/lib/io_request/message.rb
CHANGED
@@ -81,8 +81,8 @@ module IORequest
|
|
81
81
|
# @param io_r [:read]
|
82
82
|
# @return [Message]
|
83
83
|
def self.read_from(io_r)
|
84
|
-
size = io_r.read(2)
|
85
|
-
raise
|
84
|
+
size = io_r.read(2)&.unpack1('S') || 0
|
85
|
+
raise ZeroSizeMessageError if size.zero?
|
86
86
|
|
87
87
|
json_string = io_r.read(size).unpack1("a#{size}")
|
88
88
|
msg = JSON.parse(json_string, symbolize_names: true)
|
data/lib/io_request/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: io_request
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fizvlad
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-07-
|
11
|
+
date: 2020-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|