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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5bc70dc523aa523749a9ff7ced4fb1e59243a70ab963026b76c0feada184adad
4
- data.tar.gz: fd2d048ddf79f0102bbc39ab72ad98529b090d40a2f681b72bb14d2149bea241
3
+ metadata.gz: e57d41dbda1d6665dd595e36da99ce52b60fd2d979957583570224e752b6e8ae
4
+ data.tar.gz: eb50b39358a127a3b6b231d37606a4835b92030b682987bdd6f5a973d3c5b8bd
5
5
  SHA512:
6
- metadata.gz: 4e348c7d960286264707730c07f46ed6de58edd8b27653271aa4b76f87718c1ca623364299a409d4179c71083158c076d9658dcfe7a7f1ef6b62610eae797da3
7
- data.tar.gz: 5d01d75cfdd3eea6b39b1e1a74556bd260ded06d699f72cbe22f1812d5680a3d4621170116d2bfc7700d0c6a8793c1e34959dd039fd2bb94566abae439816950
6
+ metadata.gz: d9680f420318ec5d177ada3d79cccd5bab722e1993f541e802d877603bfa9011fb22cd0df5b9f01602a7a4ba54131203d1f2a7645950cdc4ef0adc151fb85c15
7
+ data.tar.gz: 92ea531ada90eda834b847d86009c4ede9594185540acbe7f76a65d0f579b62ff739b9a5ef01acc3d3b08657b97a7ab3a8e36c0dacc90024bd60c53c53b85866
data/.gitignore CHANGED
@@ -1,8 +1,10 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ Gemfile.lock
@@ -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
+
@@ -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 "https://rubygems.org"
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 object
4
-
5
- ## Installation
6
-
7
- Add this line to your application's Gemfile:
8
-
9
- ```ruby
10
- gem 'io_request'
11
- ```
12
-
13
- And then execute:
14
-
15
- $ bundle
16
-
17
- Or install it yourself as:
18
-
19
- $ gem install io_request
20
-
21
- ## Usage
22
-
23
- Documentation is available at https://www.rubydoc.info/gems/io_request
24
-
25
- Some examples could be find in *examples* folder.
26
-
27
- ## Development
28
-
29
- 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.
30
-
31
- 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).
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 "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
- task :default => :test
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
@@ -1,14 +1,8 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "io_request"
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__)
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__)
@@ -1,37 +1,39 @@
1
- lib = File.expand_path("lib", __dir__)
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 "io_request/version"
5
+ require 'io_request/version'
4
6
 
5
7
  Gem::Specification.new do |spec|
6
- spec.name = "io_request"
8
+ spec.name = 'io_request'
7
9
  spec.version = IORequest::VERSION
8
- spec.authors = ["Fizvlad"]
9
- spec.email = ["fizvlad@mail.ru"]
10
+ spec.authors = ['Fizvlad']
11
+ spec.email = ['fizvlad@mail.ru']
10
12
 
11
- spec.summary = "Small gem to create JSON request/response type of connection over IO object"
12
- spec.homepage = "https://github.com/fizvlad/io-request-rb"
13
- spec.license = "MIT"
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 = ">=2.3.1"
17
+ spec.required_ruby_version = '>=2.6.5'
16
18
 
17
- spec.metadata["homepage_uri"] = spec.homepage
18
- spec.metadata["source_code_uri"] = "https://github.com/fizvlad/io-request-rb"
19
- spec.metadata["changelog_uri"] = "https://github.com/fizvlad/io-request-rb/releases"
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 = Dir.chdir(File.expand_path('..', __FILE__)) do
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 = "exe"
28
+ spec.bindir = 'exe'
27
29
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
- spec.require_paths = ["lib"]
30
+ spec.require_paths = ['lib']
29
31
 
30
- spec.add_development_dependency "bundler", "~> 2.0"
31
- spec.add_development_dependency "rake", "~> 13.0"
32
- spec.add_development_dependency "minitest", "~> 5.0"
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 "logger", "~>1.4"
35
- spec.add_runtime_dependency "timeout-extensions", "~>0.1.1"
36
- spec.add_runtime_dependency "json", "~>2.0"
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
@@ -1,8 +1,20 @@
1
- require_relative "io_request/version"
2
- require_relative "io_request/utility"
3
- require_relative "io_request/logging"
4
- require_relative "io_request/message"
5
- require_relative "io_request/client"
6
-
7
- # Main module.
8
- module IORequest; end
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
@@ -1,193 +1,213 @@
1
- require "base64"
2
- require "timeout"
3
- require "json"
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 over IO.
12
- #
13
- # @option options [:gets] read IO to read from.
14
- # @option options [:puts] write IO to write to.
15
- def initialize(read: nil, write: nil)
16
- @io_r = read
17
- @io_w = write
18
-
19
- @mutex = Mutex.new
20
- @responders = [] # Array of pairs [Subhash, Block]
21
- @out_requests = {} # Request => Proc
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
- private
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
- # Starts receiving loop and freezes thread.
64
- def receive_loop
65
- loop do
66
- h = receive(nil)
67
- break if h.nil?
68
- case h[:type]
69
- when "request"
70
- handle_in_request(Request.from_hash h)
71
- when "response"
72
- handle_in_response(Response.from_hash h)
73
- else
74
- IORequest.warn("Unknown message type: #{h[:type].inspect}", prog_name)
75
- end
76
- end
77
- IORequest.debug("Receive loop exited", prog_name)
78
- end
79
- # Handle incoming request.
80
- def handle_in_request(req)
81
- IORequest.debug("Handling request ##{req.id}", prog_name)
82
- in_thread(name: "request_handler") do
83
- responder = find_responder(req)
84
- data = nil
85
- data = begin
86
- if responder
87
- responder.call(req, self)
88
- else
89
- IORequest.warn "Responder not found!"
90
- nil
91
- end
92
- rescue Exception => e
93
- IORequest.warn "Provided block raised exception:\n#{e.full_message}", prog_name
94
- nil
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
- data ||= {}
97
- res = Response.new(data, req)
98
- send(res.to_hash)
93
+ nil
94
+ else
95
+ send_request_and_wait_for_response(message).data
99
96
  end
100
- nil
101
- end
102
- # Handle incoming response.
103
- def handle_in_response(res)
104
- req_id = res.request.to_i
105
- req = @out_requests.keys.find { |r| r.id == req_id }
106
- unless req
107
- IORequest.warn("Request ##{req_id} not found", prog_name)
108
- return
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
- IORequest.debug("Request ##{req_id} response received", prog_name)
111
- req.response = res
112
- # If block is not provided it's totally ok
113
- block = @out_requests.delete(req)
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
- # find responder for provided request.
126
- def find_responder(req)
127
- result = nil
128
- @responders.each do |subdata, block|
129
- if req.data.contains? subdata
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
- result
135
+ IORequest.logger.debug(prog_name) { "New client authorized with data #{@authorizer.data}" }
136
136
  end
137
137
 
138
- # Send data.
139
- #
140
- # @param [Hash]
141
- def send(data)
142
- IORequest.debug("Sending hash: #{data.inspect}", prog_name)
143
- send_raw(encode(data_to_string data))
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
- # Receive data.
147
- #
148
- # @param timeout [Integer, Float, nil] timeout size or +nil+ if no timeout required.
149
- #
150
- # @return [Hash, nil] hash or +nil+ if timed out or IO was closed.
151
- def receive(timeout)
152
- str = Timeout::timeout(timeout) do
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
- # Send string.
167
- def send_raw(str)
168
- @io_w.puts str
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
- # Receive string.
171
- def receive_raw
172
- @io_r.gets&.chomp
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
- # Encode string
176
- def encode(str)
177
- Base64::strict_encode64 str
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
- # Decode string
180
- def decode(str)
181
- Base64::strict_decode64 str
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
- # Turn data into string
185
- def data_to_string(data)
186
- JSON.generate(data)
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
- # Turn string into data
189
- def string_to_data(str)
190
- JSON.parse(str).symbolize_keys!
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