io_request 1.2.0 → 2.3.1
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 +10 -8
- data/.rubocop.yml +37 -0
- data/.rubocop_todo.yml +7 -0
- data/Gemfile +17 -4
- data/README.md +31 -39
- data/Rakefile +31 -10
- data/bin/console +8 -14
- data/io_request.gemspec +23 -21
- data/lib/io_request.rb +20 -8
- data/lib/io_request/authorizer.rb +42 -0
- data/lib/io_request/client.rb +177 -157
- data/lib/io_request/connection/ssl_sockets.rb +163 -0
- data/lib/io_request/logging.rb +13 -42
- data/lib/io_request/message.rb +68 -85
- data/lib/io_request/utility/multi_thread.rb +64 -0
- data/lib/io_request/utility/with_id.rb +60 -0
- data/lib/io_request/utility/with_prog_name.rb +14 -0
- data/lib/io_request/version.rb +3 -1
- metadata +26 -22
- data/Gemfile.lock +0 -29
- data/examples/simple_example.rb +0 -52
- data/lib/io_request/utility.rb +0 -104
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e57d41dbda1d6665dd595e36da99ce52b60fd2d979957583570224e752b6e8ae
|
4
|
+
data.tar.gz: eb50b39358a127a3b6b231d37606a4835b92030b682987bdd6f5a973d3c5b8bd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9680f420318ec5d177ada3d79cccd5bab722e1993f541e802d877603bfa9011fb22cd0df5b9f01602a7a4ba54131203d1f2a7645950cdc4ef0adc151fb85c15
|
7
|
+
data.tar.gz: 92ea531ada90eda834b847d86009c4ede9594185540acbe7f76a65d0f579b62ff739b9a5ef01acc3d3b08657b97a7ab3a8e36c0dacc90024bd60c53c53b85866
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
2
|
+
|
3
|
+
require:
|
4
|
+
- rubocop-performance
|
5
|
+
|
6
|
+
AllCops:
|
7
|
+
DisplayCopNames: true
|
8
|
+
DisplayStyleGuide: true
|
9
|
+
ExtraDetails: false
|
10
|
+
TargetRubyVersion: 2.6
|
11
|
+
Exclude:
|
12
|
+
- config/**/*
|
13
|
+
- tmp/**/*
|
14
|
+
- Capfile
|
15
|
+
- Gemfile
|
16
|
+
- Rakefile
|
17
|
+
|
18
|
+
# EOL is handled by git settings
|
19
|
+
Layout/EndOfLine:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
# Just enough
|
23
|
+
Layout/LineLength:
|
24
|
+
Max: 100
|
25
|
+
|
26
|
+
# Disable some common cops for tests
|
27
|
+
Style/Documentation:
|
28
|
+
Exclude:
|
29
|
+
- test/**/*
|
30
|
+
Metrics/AbcSize:
|
31
|
+
Exclude:
|
32
|
+
- test/**/*
|
33
|
+
Metrics/MethodLength:
|
34
|
+
Max: 15 # Extended limit of lines in method
|
35
|
+
Exclude:
|
36
|
+
- test/**/*
|
37
|
+
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2020-07-15 18:13:41 UTC using RuboCop version 0.88.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
data/Gemfile
CHANGED
@@ -1,4 +1,17 @@
|
|
1
|
-
source
|
2
|
-
|
3
|
-
# Specify your gem's dependencies in io_request.gemspec
|
4
|
-
gemspec
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
# Specify your gem's dependencies in io_request.gemspec
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rubocop', '~> 0.88.0', group: :development
|
7
|
+
gem 'rubocop-performance', '~> 1.6', group: :development, require: false
|
8
|
+
|
9
|
+
gem 'json', '~> 2.3'
|
10
|
+
|
11
|
+
gem 'timeout', '~> 0.1.0'
|
12
|
+
|
13
|
+
gem 'logger', '~> 1.4'
|
14
|
+
|
15
|
+
gem 'openssl', '~> 2.2'
|
16
|
+
|
17
|
+
gem 'pry', '~> 0.13.1'
|
data/README.md
CHANGED
@@ -1,39 +1,31 @@
|
|
1
|
-
# IORequest
|
2
|
-
|
3
|
-
Small gem to create JSON request/response type of connection over IO
|
4
|
-
|
5
|
-
## Installation
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
```
|
10
|
-
|
11
|
-
```
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
## Contributing
|
34
|
-
|
35
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/fizvlad/io-request-rb.
|
36
|
-
|
37
|
-
## License
|
38
|
-
|
39
|
-
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
1
|
+
# IORequest
|
2
|
+
|
3
|
+
Small gem to create JSON request/response type of connection over different IO objects.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Using [bundler](https://bundler.io/):
|
8
|
+
|
9
|
+
```
|
10
|
+
$ bundle add io_request
|
11
|
+
```
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Documentation is available at https://www.rubydoc.info/gems/io_request
|
16
|
+
|
17
|
+
Some examples could be find in *examples* folder.
|
18
|
+
|
19
|
+
## Development
|
20
|
+
|
21
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
22
|
+
|
23
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fizvlad/io-request-rb.
|
28
|
+
|
29
|
+
## License
|
30
|
+
|
31
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -1,10 +1,31 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
|
4
|
-
Rake::TestTask.new(:test) do |t|
|
5
|
-
t.libs <<
|
6
|
-
t.libs <<
|
7
|
-
t.test_files = FileList[
|
8
|
-
end
|
9
|
-
|
10
|
-
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << 'test'
|
6
|
+
t.libs << 'lib'
|
7
|
+
t.test_files = FileList['test/**/*_test.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'rubocop/rake_task'
|
11
|
+
RuboCop::RakeTask.new(:rubocop) {}
|
12
|
+
namespace 'rubocop' do
|
13
|
+
desc 'Generate rubocop TODO file.'
|
14
|
+
task 'todo' do
|
15
|
+
puts `rubocop --auto-gen-config`
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
namespace 'yardoc' do
|
20
|
+
desc 'Generate documentation'
|
21
|
+
task 'generate' do
|
22
|
+
puts `yardoc lib/*`
|
23
|
+
end
|
24
|
+
|
25
|
+
desc 'List undocumented elements'
|
26
|
+
task 'undoc' do
|
27
|
+
puts `yardoc stats --list-undoc lib/*`
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
task :default => :test
|
data/bin/console
CHANGED
@@ -1,14 +1,8 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
|
4
|
-
require
|
5
|
-
|
6
|
-
|
7
|
-
|
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__)
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'io_request'
|
6
|
+
|
7
|
+
require 'pry'
|
8
|
+
Pry.start(__FILE__)
|
data/io_request.gemspec
CHANGED
@@ -1,37 +1,39 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
5
|
+
require 'io_request/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
8
|
+
spec.name = 'io_request'
|
7
9
|
spec.version = IORequest::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
10
|
+
spec.authors = ['Fizvlad']
|
11
|
+
spec.email = ['fizvlad@mail.ru']
|
10
12
|
|
11
|
-
spec.summary =
|
12
|
-
spec.homepage =
|
13
|
-
spec.license =
|
13
|
+
spec.summary = 'Small gem to create JSON request/response type of connection over IO object'
|
14
|
+
spec.homepage = 'https://github.com/fizvlad/io-request-rb'
|
15
|
+
spec.license = 'MIT'
|
14
16
|
|
15
|
-
spec.required_ruby_version =
|
17
|
+
spec.required_ruby_version = '>=2.6.5'
|
16
18
|
|
17
|
-
spec.metadata[
|
18
|
-
spec.metadata[
|
19
|
-
spec.metadata[
|
19
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
20
|
+
spec.metadata['source_code_uri'] = 'https://github.com/fizvlad/io-request-rb'
|
21
|
+
spec.metadata['changelog_uri'] = 'https://github.com/fizvlad/io-request-rb/releases'
|
20
22
|
|
21
23
|
# Specify which files should be added to the gem when it is released.
|
22
24
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
23
|
-
spec.files
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
24
26
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
25
27
|
end
|
26
|
-
spec.bindir =
|
28
|
+
spec.bindir = 'exe'
|
27
29
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
-
spec.require_paths = [
|
30
|
+
spec.require_paths = ['lib']
|
29
31
|
|
30
|
-
spec.add_development_dependency
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
32
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
33
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
34
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
33
35
|
|
34
|
-
spec.add_runtime_dependency
|
35
|
-
spec.add_runtime_dependency
|
36
|
-
spec.add_runtime_dependency
|
36
|
+
spec.add_runtime_dependency 'json', '~>2.0'
|
37
|
+
spec.add_runtime_dependency 'logger', '~>1.4'
|
38
|
+
spec.add_runtime_dependency 'timeout-extensions', '~>0.1.1'
|
37
39
|
end
|
data/lib/io_request.rb
CHANGED
@@ -1,8 +1,20 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Main module.
|
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
|
+
end
|
11
|
+
|
12
|
+
require_relative 'io_request/version'
|
13
|
+
require_relative 'io_request/logging'
|
14
|
+
require_relative 'io_request/utility/multi_thread'
|
15
|
+
require_relative 'io_request/utility/with_id'
|
16
|
+
require_relative 'io_request/utility/with_prog_name'
|
17
|
+
|
18
|
+
require_relative 'io_request/authorizer'
|
19
|
+
require_relative 'io_request/message'
|
20
|
+
require_relative 'io_request/client'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IORequest
|
4
|
+
# Class to authorize client connection.
|
5
|
+
class Authorizer
|
6
|
+
# @yieldparam io_r [IO] input stream.
|
7
|
+
# @yieldparam io_w [IO] output stream.
|
8
|
+
# @yieldreturn [Object, nil] if `nil` is returned, authorization will be
|
9
|
+
# considered as failed one. Otherwise data will be saved into `data`.
|
10
|
+
def initialize(&block)
|
11
|
+
@block = block
|
12
|
+
@data = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Object] literally any non-nil data from block.
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
# @return [Boolean] authorization status.
|
19
|
+
def authorize(io_r, io_w)
|
20
|
+
@data = nil
|
21
|
+
@data = @block.call(io_r, io_w)
|
22
|
+
!@data.nil?
|
23
|
+
rescue StandardError => e
|
24
|
+
IORequest.logger.error(e.full_message)
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# No authorization.
|
30
|
+
def Authorizer.empty
|
31
|
+
Authorizer.new { |_io_r, _io_w| true }
|
32
|
+
end
|
33
|
+
|
34
|
+
# Secret key authorization.
|
35
|
+
def Authorizer.by_secret_key(key)
|
36
|
+
Authorizer.new do |io_r, io_w|
|
37
|
+
io_w.write(key)
|
38
|
+
other = io_r.read(key.size)
|
39
|
+
key == other ? other : nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/io_request/client.rb
CHANGED
@@ -1,193 +1,213 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
require 'json'
|
4
5
|
|
5
6
|
module IORequest
|
6
7
|
# Connection client.
|
8
|
+
#
|
9
|
+
# General scheme:
|
10
|
+
# Client 1 Client 2
|
11
|
+
# | |
|
12
|
+
# ( Authorization ) See `Authorizer` class. Error in authorization should close
|
13
|
+
# | | connection
|
14
|
+
# | |
|
15
|
+
# [ Data transition loop ] Loop runs until someone sends 0 sized data. Then everyone
|
16
|
+
# | | should close connection. Any R/W errors should also finish the
|
17
|
+
# | | loop
|
18
|
+
# | |
|
19
|
+
# |-> uint(2 bytes) ->| Specifies size of following JSON string
|
20
|
+
# |-> Mesage as JSON ->| Message itself. It should contain its `type`, `id` and some
|
21
|
+
# | | data hash
|
22
|
+
# | |
|
23
|
+
# | (Message handling) See `Handler` class
|
24
|
+
# | |
|
25
|
+
# |<- uint(2 bytes) <-|
|
26
|
+
# |<- Mesage as JSON <-|
|
7
27
|
class Client
|
8
28
|
include Utility::WithProgName
|
9
29
|
include Utility::MultiThread
|
10
30
|
|
11
|
-
# Initialize new client
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@
|
17
|
-
@
|
18
|
-
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
|
23
|
-
@receive_thread = Thread.new { receive_loop }
|
24
|
-
IORequest.debug("New IORequest client initialized", prog_name)
|
25
|
-
end
|
26
|
-
|
27
|
-
# Send request.
|
28
|
-
#
|
29
|
-
# Optional block can be provided. It will be called when response received.
|
30
|
-
#
|
31
|
-
# @option options [Hash] data data to send.
|
32
|
-
# @option options [Boolean] sync whether to join request after sending.
|
33
|
-
# @option options [Integer, Float] timeout timeout for {Request#join}.
|
34
|
-
#
|
35
|
-
# @yieldparam request [Response] response for request.
|
36
|
-
#
|
37
|
-
# @return [Request]
|
38
|
-
def request(data: {}, sync: false, timeout: nil, &block)
|
39
|
-
req = Request.new(data)
|
40
|
-
@out_requests[req] = block
|
41
|
-
IORequest.debug("Sending request ##{req.id}", prog_name)
|
42
|
-
send(req.to_hash)
|
43
|
-
req.join(timeout) if sync
|
44
|
-
req
|
45
|
-
end
|
46
|
-
|
47
|
-
# Setup block for answering incoming requests.
|
48
|
-
#
|
49
|
-
# @param subdata [Hash] provided block will be called only if received data
|
50
|
-
# includes this hash.
|
51
|
-
#
|
52
|
-
# @yieldparam request [Request] incoming request.
|
53
|
-
# @yieldreturn [Hash] data to be sent in response.
|
54
|
-
#
|
55
|
-
# @return [nil]
|
56
|
-
def respond(subdata = {}, &block)
|
57
|
-
@responders << [subdata, block]
|
58
|
-
nil
|
31
|
+
# Initialize new client.
|
32
|
+
def initialize(authorizer: Authorizer.empty)
|
33
|
+
@open = false
|
34
|
+
@authorizer = authorizer
|
35
|
+
|
36
|
+
@mutex_r = Mutex.new
|
37
|
+
@mutex_w = Mutex.new
|
38
|
+
|
39
|
+
@responses = {}
|
40
|
+
@responses_access_mutex = Mutex.new
|
41
|
+
@responses_access_cv = ConditionVariable.new
|
59
42
|
end
|
60
43
|
|
61
|
-
|
44
|
+
# Start new client connection.
|
45
|
+
# @param r [IO] object to read from.
|
46
|
+
# @param w [IO] object to write to.
|
47
|
+
# @param rw [IO] read-write object (replaces `r` and `w` arguments).
|
48
|
+
def open(read: nil, write: nil, read_write: nil)
|
49
|
+
@io_r = read_write || read
|
50
|
+
@io_w = read_write || write
|
62
51
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
52
|
+
IORequest.logger.debug(prog_name) { 'Starting connection' }
|
53
|
+
|
54
|
+
authorization
|
55
|
+
@open = true
|
56
|
+
@data_transition_thread = in_thread(name: 'connection') { data_transition_loop }
|
57
|
+
end
|
58
|
+
|
59
|
+
def open?
|
60
|
+
@open
|
61
|
+
end
|
62
|
+
|
63
|
+
# Close connection.
|
64
|
+
def close
|
65
|
+
close_internal
|
66
|
+
|
67
|
+
join_threads
|
68
|
+
end
|
69
|
+
|
70
|
+
# @yieldparam [Hash]
|
71
|
+
# @yieldreturn [Hash]
|
72
|
+
def on_request(&block)
|
73
|
+
IORequest.logger.debug(prog_name) { 'Saved on_request block' }
|
74
|
+
@on_request = block
|
75
|
+
end
|
76
|
+
alias respond on_request
|
77
|
+
|
78
|
+
def on_close(&block)
|
79
|
+
IORequest.logger.debug(prog_name) { 'Saved on_close block' }
|
80
|
+
@on_close = block
|
81
|
+
end
|
82
|
+
|
83
|
+
# If callback block is provided, request will be sent asynchroniously.
|
84
|
+
# @param data [Hash]
|
85
|
+
def request(data = {}, &callback)
|
86
|
+
message = Message.new(data, type: :request)
|
87
|
+
|
88
|
+
if block_given?
|
89
|
+
# Async execution of request
|
90
|
+
in_thread(callback, name: 'requesting') do |cb|
|
91
|
+
cb.call(send_request_and_wait_for_response(message).data)
|
95
92
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
93
|
+
nil
|
94
|
+
else
|
95
|
+
send_request_and_wait_for_response(message).data
|
99
96
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
97
|
+
end
|
98
|
+
|
99
|
+
attr_reader :authorizer
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def close_internal
|
104
|
+
IORequest.logger.debug(prog_name) { 'Closing connection' }
|
105
|
+
send_zero_size_request
|
106
|
+
close_io
|
107
|
+
@data_transition_thread = nil
|
108
|
+
@open = false
|
109
|
+
@on_close&.call if defined?(@on_close)
|
110
|
+
end
|
111
|
+
|
112
|
+
def close_io
|
113
|
+
begin
|
114
|
+
@io_r&.close
|
115
|
+
rescue StandardError => e
|
116
|
+
IORequest.logger.debug "Failed to close read IO: #{e}"
|
109
117
|
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
if block
|
115
|
-
in_thread(name: "response_handle") do
|
116
|
-
begin
|
117
|
-
block.call(res)
|
118
|
-
rescue Exception => e
|
119
|
-
IORequest.warn("Provided block raised exception:\n#{e.full_message}", prog_name)
|
120
|
-
end
|
121
|
-
end
|
118
|
+
begin
|
119
|
+
@io_w&.close
|
120
|
+
rescue StandardError => e
|
121
|
+
IORequest.logger.debug "Failed to close write IO: #{e}"
|
122
122
|
end
|
123
|
+
IORequest.logger.debug(prog_name) { 'Closed IO streams' }
|
123
124
|
end
|
124
125
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
result = block
|
131
|
-
break
|
126
|
+
def authorization
|
127
|
+
auth_successful = @mutex_r.synchronize do
|
128
|
+
@mutex_w.synchronize do
|
129
|
+
IORequest.logger.debug(prog_name) { 'Authorizing new client' }
|
130
|
+
@authorizer.authorize(@io_r, @io_w)
|
132
131
|
end
|
133
132
|
end
|
133
|
+
raise AuthorizationFailureError unless auth_successful
|
134
134
|
|
135
|
-
|
135
|
+
IORequest.logger.debug(prog_name) { "New client authorized with data #{@authorizer.data}" }
|
136
136
|
end
|
137
137
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
138
|
+
def data_transition_loop
|
139
|
+
IORequest.logger.debug(prog_name) { 'Starting data transition loop' }
|
140
|
+
loop do
|
141
|
+
data_transition_iteration
|
142
|
+
rescue ZeroSizeMessageError
|
143
|
+
IORequest.logger.debug(prog_name) { 'Connection was closed from the other side' }
|
144
|
+
break
|
145
|
+
rescue StandardError => e
|
146
|
+
IORequest.logger.debug(prog_name) { "Data transition unknown error: #{e}" }
|
147
|
+
break
|
148
|
+
end
|
149
|
+
close_internal
|
144
150
|
end
|
145
151
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
receive_raw
|
152
|
+
def data_transition_iteration
|
153
|
+
message = @mutex_r.synchronize { Message.read_from(@io_r) }
|
154
|
+
IORequest.logger.debug(prog_name) { "Received message: #{message}" }
|
155
|
+
if message.request?
|
156
|
+
in_thread(name: 'responding') { handle_request(message) }
|
157
|
+
else
|
158
|
+
handle_response(message)
|
154
159
|
end
|
155
|
-
return nil if str.nil?
|
156
|
-
string_to_data(decode(str))
|
157
|
-
rescue Timeout::Error
|
158
|
-
nil
|
159
|
-
rescue IOError
|
160
|
-
nil
|
161
|
-
rescue Exception => e
|
162
|
-
IORequest.warn "Exception of #{e.class} encountered while trying to receive message. Suggesting IO was closed. Full trace: #{e.full_message}", prog_name
|
163
|
-
nil
|
164
160
|
end
|
165
161
|
|
166
|
-
|
167
|
-
|
168
|
-
@
|
162
|
+
def handle_request(message)
|
163
|
+
data = {}
|
164
|
+
data = @on_request&.call(message.data) if defined?(@on_request)
|
165
|
+
response = Message.new(data, type: :response, to: message.id)
|
166
|
+
send_response(response)
|
169
167
|
end
|
170
|
-
|
171
|
-
def
|
172
|
-
@
|
168
|
+
|
169
|
+
def handle_response(message)
|
170
|
+
@responses_access_mutex.synchronize do
|
171
|
+
@responses[message.to.to_s] = message
|
172
|
+
@responses_access_cv.broadcast
|
173
|
+
end
|
173
174
|
end
|
174
175
|
|
175
|
-
|
176
|
-
|
177
|
-
|
176
|
+
def send_response(response)
|
177
|
+
@mutex_w.synchronize do
|
178
|
+
IORequest.logger.debug(prog_name) { "Sending response: #{response}" }
|
179
|
+
response.write_to(@io_w)
|
180
|
+
end
|
178
181
|
end
|
179
|
-
|
180
|
-
def
|
181
|
-
|
182
|
+
|
183
|
+
def send_zero_size_request
|
184
|
+
@mutex_w.synchronize do
|
185
|
+
IORequest.logger.debug(prog_name) { 'Sending zero size message' }
|
186
|
+
@io_w.write([0].pack('S'))
|
187
|
+
end
|
188
|
+
rescue StandardError => e
|
189
|
+
IORequest.logger.debug(prog_name) { "Failed to send zero-sized message(#{e})" }
|
182
190
|
end
|
183
191
|
|
184
|
-
|
185
|
-
|
186
|
-
|
192
|
+
def send_request_and_wait_for_response(request)
|
193
|
+
@mutex_w.synchronize do
|
194
|
+
IORequest.logger.debug(prog_name) { "Sending message: #{request}" }
|
195
|
+
request.write_to(@io_w)
|
196
|
+
end
|
197
|
+
wait_for_response(request)
|
187
198
|
end
|
188
|
-
|
189
|
-
def
|
190
|
-
|
199
|
+
|
200
|
+
def wait_for_response(request)
|
201
|
+
IORequest.logger.debug(prog_name) { "Waiting for response for #{request}" }
|
202
|
+
@responses_access_mutex.synchronize do
|
203
|
+
response = nil
|
204
|
+
until response
|
205
|
+
@responses_access_cv.wait(@responses_access_mutex)
|
206
|
+
response = @responses[request.id.to_s]
|
207
|
+
end
|
208
|
+
IORequest.logger.debug(prog_name) { "Found response: #{response}" }
|
209
|
+
response
|
210
|
+
end
|
191
211
|
end
|
192
212
|
end
|
193
213
|
end
|