glottis 0.1.3 → 0.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 +5 -5
- data/CHANGELOG.md +20 -0
- data/README.md +26 -5
- data/Rakefile +10 -6
- data/exe/glottis +23 -22
- data/glottis.gemspec +18 -10
- data/lib/glottis/client.rb +28 -13
- data/lib/glottis/exceptions/glottis_exception.rb +8 -6
- data/lib/glottis/exceptions/user_exited_exception.rb +10 -8
- data/lib/glottis/handlers/console_input_handler.rb +52 -32
- data/lib/glottis/handlers/console_output_handler.rb +40 -21
- data/lib/glottis/handlers/remote_input_handler.rb +86 -53
- data/lib/glottis/handlers/remote_output_handler.rb +96 -87
- data/lib/glottis/version.rb +3 -1
- data/lib/glottis.rb +4 -2
- metadata +41 -32
- data/.gitignore +0 -11
- data/.rubocop.yml +0 -5
- data/.travis.yml +0 -4
- data/Gemfile +0 -4
- data/bin/console +0 -7
- data/bin/setup +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4f9b168031125cddf48f2087d3e3cd6c849e9a16972866b21db5d38c9cd2f83e
|
|
4
|
+
data.tar.gz: 64495dc081d2130d83ab6f3992e6f40de2bcdde85e65c0b3580cd46adcbf4af5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef03824e6fc0c56a6d1df2a93965f851b1795a9892c9bc35a404cd795514f0a692bdb4dcfdcd6b06896f61241fb4010186395b1d845c88a896a709bef6c3c5d9
|
|
7
|
+
data.tar.gz: 77c0d6bd62771b0fd28af07625ef8f1cedb986655ff68eb9bb0260c92beb47ca1b26c42f0b0cc482b6bb503599363e5daa81304546a8680865d073476329d613
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semmle.com/semver/1.0.0/).
|
|
7
|
+
|
|
8
|
+
## [0.3.1] - 2026-07-24
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Bumped required Ruby version to `>= 3.0.0`.
|
|
12
|
+
- Removed `rb-readline` dependency in favor of Ruby standard library `reline`/`readline`.
|
|
13
|
+
- Updated `slop` runtime dependency to `~> 4.10`.
|
|
14
|
+
- Explicitly set `Content-Type: application/json` headers on outgoing HTTP request posts.
|
|
15
|
+
- Modernized gemspec metadata and file list configuration.
|
|
16
|
+
- Updated RuboCop configuration for Ruby 3.0+ compatibility (`Layout/LineLength`).
|
|
17
|
+
- Migrated CI from Travis CI to GitHub Actions (`.github/workflows/ci.yml`).
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
- Comprehensive RSpec test suite covering core client, handlers, and live HTTP mock server integration.
|
data/README.md
CHANGED
|
@@ -2,26 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
A CLI for the [valyx](https://github.com/maxdeliso/valyx) message passing server.
|
|
4
4
|
|
|
5
|
+
[](https://github.com/maxdeliso/glottis/actions/workflows/ci.yml)
|
|
6
|
+
[](https://badge.fury.io/rb/glottis)
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
- Ruby >= 3.0.0
|
|
11
|
+
|
|
5
12
|
## Installation
|
|
6
13
|
|
|
14
|
+
Install the gem by executing:
|
|
15
|
+
|
|
7
16
|
$ gem install glottis
|
|
8
17
|
|
|
18
|
+
Or add it to your application's Gemfile:
|
|
19
|
+
|
|
20
|
+
$ gem 'glottis', '~> 0.3'
|
|
21
|
+
|
|
9
22
|
## Usage
|
|
10
23
|
|
|
11
|
-
|
|
24
|
+
Run the `glottis` command-line executable:
|
|
25
|
+
|
|
26
|
+
$ glottis --host localhost --port 8080
|
|
27
|
+
|
|
28
|
+
For help and available command line flags:
|
|
29
|
+
|
|
30
|
+
$ glottis --help
|
|
12
31
|
|
|
13
32
|
## Development
|
|
14
33
|
|
|
15
|
-
To
|
|
34
|
+
To run the test suite and linter:
|
|
16
35
|
|
|
17
|
-
|
|
36
|
+
$ bundle exec rake
|
|
18
37
|
|
|
19
38
|
To install this gem onto your local machine:
|
|
20
39
|
|
|
21
|
-
|
|
40
|
+
$ bundle exec rake install
|
|
22
41
|
|
|
23
42
|
## Contributing
|
|
24
43
|
|
|
25
44
|
Bug reports and pull requests are welcome on GitHub at https://github.com/maxdeliso/glottis.
|
|
26
45
|
|
|
27
|
-
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler/gem_tasks'
|
|
4
|
+
require 'rubocop/rake_task'
|
|
5
|
+
require 'rspec/core/rake_task'
|
|
6
|
+
|
|
7
|
+
RuboCop::RakeTask.new(:rubocop)
|
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
9
|
+
|
|
10
|
+
task default: %i[rubocop spec]
|
data/exe/glottis
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
require '
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
o.
|
|
9
|
-
o.
|
|
10
|
-
o.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'slop'
|
|
5
|
+
require 'glottis'
|
|
6
|
+
|
|
7
|
+
opts = Slop.parse do |o|
|
|
8
|
+
o.string '--host', 'a hostname', default: 'localhost'
|
|
9
|
+
o.integer '--port', 'TCP port', default: 8080
|
|
10
|
+
o.bool '-v', '--verbose', 'enable verbose mode'
|
|
11
|
+
o.on '-V', '--version', 'print the version' do
|
|
12
|
+
puts "glottis #{Glottis::VERSION}"
|
|
13
|
+
exit
|
|
14
|
+
end
|
|
15
|
+
o.on '-h', '--help' do
|
|
16
|
+
puts o
|
|
17
|
+
exit
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
Glottis::Client.new(opts[:host],
|
|
22
|
+
opts[:port],
|
|
23
|
+
verbose: opts[:verbose]).run
|
data/glottis.gemspec
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
5
|
require 'glottis/version'
|
|
5
6
|
|
|
@@ -11,19 +12,26 @@ Gem::Specification.new do |spec|
|
|
|
11
12
|
spec.licenses = ['MIT']
|
|
12
13
|
spec.summary = 'simple http messaging client'
|
|
13
14
|
spec.homepage = 'https://github.com/maxdeliso/glottis'
|
|
15
|
+
spec.required_ruby_version = '>= 3.0.0'
|
|
16
|
+
|
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['source_code_uri'] = 'https://github.com/maxdeliso/glottis'
|
|
19
|
+
spec.metadata['changelog_uri'] = 'https://github.com/maxdeliso/glottis/blob/main/CHANGELOG.md'
|
|
20
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/maxdeliso/glottis/issues'
|
|
21
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
14
22
|
|
|
15
|
-
spec.files =
|
|
16
|
-
|
|
17
|
-
end
|
|
23
|
+
spec.files = Dir.glob('{exe,lib}/**/*').select { |f| File.file?(f) } +
|
|
24
|
+
%w[Rakefile README.md CHANGELOG.md glottis.gemspec]
|
|
18
25
|
|
|
19
26
|
spec.bindir = 'exe'
|
|
20
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
21
28
|
spec.require_paths = ['lib']
|
|
22
29
|
|
|
23
|
-
spec.
|
|
24
|
-
spec.add_runtime_dependency 'slop', '~> 4.2'
|
|
30
|
+
spec.add_dependency 'slop', '~> 4.10'
|
|
25
31
|
|
|
26
|
-
spec.add_development_dependency 'bundler', '
|
|
27
|
-
spec.add_development_dependency 'rake', '~>
|
|
28
|
-
spec.add_development_dependency '
|
|
32
|
+
spec.add_development_dependency 'bundler', '>= 2.0'
|
|
33
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
34
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
35
|
+
spec.add_development_dependency 'rubocop', '~> 1.0'
|
|
36
|
+
spec.add_development_dependency 'webrick', '~> 1.8'
|
|
29
37
|
end
|
data/lib/glottis/client.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require 'logger'
|
|
3
4
|
require 'glottis/handlers/console_input_handler'
|
|
4
5
|
require 'glottis/handlers/console_output_handler'
|
|
@@ -6,10 +7,10 @@ require 'glottis/handlers/remote_input_handler'
|
|
|
6
7
|
require 'glottis/handlers/remote_output_handler'
|
|
7
8
|
|
|
8
9
|
module Glottis
|
|
9
|
-
# Client class
|
|
10
|
+
# Client class used to interact with the specified host and port.
|
|
10
11
|
class Client
|
|
11
12
|
def self.logger
|
|
12
|
-
@logger ||= Logger.new(
|
|
13
|
+
@logger ||= Logger.new($stdout)
|
|
13
14
|
end
|
|
14
15
|
|
|
15
16
|
def initialize(host, port, opts = {})
|
|
@@ -17,13 +18,30 @@ module Glottis
|
|
|
17
18
|
@port = port
|
|
18
19
|
@outgoing = Queue.new
|
|
19
20
|
@incoming = Queue.new
|
|
21
|
+
@handlers = []
|
|
20
22
|
Client.logger.level = opts[:verbose] ? Logger::DEBUG : Logger::WARN
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def run
|
|
24
26
|
Client.logger.info('starting...')
|
|
25
|
-
Thread.abort_on_exception = true
|
|
26
27
|
|
|
28
|
+
start_handlers
|
|
29
|
+
@handlers.each(&:join)
|
|
30
|
+
rescue Glottis::Exceptions::UserExitedException
|
|
31
|
+
Client.logger.info('User exited.')
|
|
32
|
+
rescue Interrupt
|
|
33
|
+
Client.logger.info('Received interrupt signal. Exiting...')
|
|
34
|
+
rescue Errno::ECONNREFUSED => e
|
|
35
|
+
Client.logger.error("Failed to connect to host: #{e.message}")
|
|
36
|
+
rescue StandardError => e
|
|
37
|
+
Client.logger.warn("Shutting down due to error: #{e.message}")
|
|
38
|
+
ensure
|
|
39
|
+
stop_handlers
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def start_handlers
|
|
27
45
|
@handlers = [
|
|
28
46
|
Handlers::ConsoleInputHandler.new(@outgoing),
|
|
29
47
|
Handlers::ConsoleOutputHandler.new(@incoming),
|
|
@@ -31,14 +49,11 @@ module Glottis
|
|
|
31
49
|
Handlers::RemoteOutputHandler.new(@outgoing, @host, @port)
|
|
32
50
|
]
|
|
33
51
|
|
|
34
|
-
@handlers.each(&:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Client.logger.warn("shutting down: #{ex}")
|
|
39
|
-
ensure
|
|
52
|
+
@handlers.each(&:start)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stop_handlers
|
|
40
56
|
@handlers.each(&:cleanup)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
end # Client
|
|
57
|
+
end
|
|
58
|
+
end
|
|
44
59
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'glottis/exceptions/glottis_exception'
|
|
4
|
+
|
|
5
|
+
module Glottis
|
|
6
|
+
module Exceptions
|
|
7
|
+
class UserExitedException < GlottisException
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -1,32 +1,52 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'glottis/exceptions/user_exited_exception'
|
|
4
|
+
require 'readline'
|
|
5
|
+
|
|
6
|
+
module Glottis
|
|
7
|
+
module Handlers
|
|
8
|
+
# Holds a reference to the outgoing message queue and posts console input.
|
|
9
|
+
class ConsoleInputHandler
|
|
10
|
+
USER_PROMPT = '> '
|
|
11
|
+
|
|
12
|
+
def initialize(outgoing)
|
|
13
|
+
@outgoing = outgoing
|
|
14
|
+
@running = false
|
|
15
|
+
@thread = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start
|
|
19
|
+
@running = true
|
|
20
|
+
@thread = Thread.new { run_loop }
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def join
|
|
25
|
+
@thread&.join
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def cleanup
|
|
29
|
+
@running = false
|
|
30
|
+
@thread&.kill if @thread&.alive?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def run_loop
|
|
36
|
+
while @running
|
|
37
|
+
user_input = Readline.readline(USER_PROMPT, true)
|
|
38
|
+
|
|
39
|
+
if user_input.nil?
|
|
40
|
+
raise Glottis::Exceptions::UserExitedException,
|
|
41
|
+
'User exited readline stream'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@outgoing.push(user_input)
|
|
45
|
+
end
|
|
46
|
+
rescue Glottis::Exceptions::UserExitedException => e
|
|
47
|
+
Client.logger.info("Console input ended: #{e.message}")
|
|
48
|
+
raise e
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -1,21 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glottis
|
|
4
|
+
module Handlers
|
|
5
|
+
# Holds a reference to incoming message queue and prints messages to stdout.
|
|
6
|
+
class ConsoleOutputHandler
|
|
7
|
+
def initialize(incoming)
|
|
8
|
+
@incoming = incoming
|
|
9
|
+
@running = false
|
|
10
|
+
@thread = nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start
|
|
14
|
+
@running = true
|
|
15
|
+
@thread = Thread.new { run_loop }
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def join
|
|
20
|
+
@thread&.join
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def cleanup
|
|
24
|
+
@running = false
|
|
25
|
+
@thread&.kill if @thread&.alive?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def run_loop
|
|
31
|
+
while @running
|
|
32
|
+
new_msg = @incoming.pop
|
|
33
|
+
break if new_msg.nil? || new_msg == :shutdown
|
|
34
|
+
|
|
35
|
+
puts "#{new_msg['from']}, #{new_msg['to']}, #{new_msg['msg']}"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -1,53 +1,86 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require '
|
|
4
|
-
require '
|
|
5
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
@http.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Glottis
|
|
9
|
+
module Handlers
|
|
10
|
+
# Manages stream input from the remote valyx server.
|
|
11
|
+
class RemoteInputHandler
|
|
12
|
+
PROTOCOL = 'http'
|
|
13
|
+
STREAM_DELIMITER = "\0"
|
|
14
|
+
READ_TIMEOUT = 3600
|
|
15
|
+
REMOTE_PATHS = {
|
|
16
|
+
get_message_stream: '/api/messages/stream'
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
def initialize(incoming, host, port)
|
|
20
|
+
@incoming = incoming
|
|
21
|
+
@host = host
|
|
22
|
+
@port = port
|
|
23
|
+
@running = false
|
|
24
|
+
@thread = nil
|
|
25
|
+
@buffer = String.new
|
|
26
|
+
setup_http
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def start
|
|
30
|
+
@running = true
|
|
31
|
+
@thread = Thread.new do
|
|
32
|
+
@http.start
|
|
33
|
+
read_loop
|
|
34
|
+
end
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def join
|
|
39
|
+
@thread&.join
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cleanup
|
|
43
|
+
@running = false
|
|
44
|
+
@http.finish if @http.started?
|
|
45
|
+
@thread&.kill if @thread&.alive?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def setup_http
|
|
51
|
+
@http = Net::HTTP.new(@host, @port)
|
|
52
|
+
@http.open_timeout = 1
|
|
53
|
+
@http.read_timeout = READ_TIMEOUT
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def read_loop
|
|
57
|
+
Client.logger.info('reading stream...')
|
|
58
|
+
@http.get(REMOTE_PATHS.fetch(:get_message_stream)) do |chunk|
|
|
59
|
+
break unless @running
|
|
60
|
+
|
|
61
|
+
Client.logger.debug("got chunk of length: #{chunk.length}")
|
|
62
|
+
process_chunk(chunk)
|
|
63
|
+
end
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
Client.logger.error("Error reading message stream: #{e.message}") if @running
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def process_chunk(chunk)
|
|
69
|
+
@buffer << chunk
|
|
70
|
+
|
|
71
|
+
while (delimiter_index = @buffer.index(STREAM_DELIMITER))
|
|
72
|
+
msg_raw = @buffer.slice!(0..delimiter_index)
|
|
73
|
+
msg_data = msg_raw.chomp(STREAM_DELIMITER)
|
|
74
|
+
next if msg_data.empty?
|
|
75
|
+
|
|
76
|
+
begin
|
|
77
|
+
parsed_msg = JSON.parse(msg_data)
|
|
78
|
+
@incoming.push(parsed_msg)
|
|
79
|
+
rescue JSON::ParserError => e
|
|
80
|
+
Client.logger.warn("Failed to parse JSON payload: #{e.message}")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -1,87 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require '
|
|
4
|
-
require '
|
|
5
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def
|
|
42
|
-
@
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
Client.logger.info(
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'uri'
|
|
5
|
+
require 'net/http'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Glottis
|
|
9
|
+
module Handlers
|
|
10
|
+
# Manages posting outgoing messages to the remote valyx server.
|
|
11
|
+
class RemoteOutputHandler
|
|
12
|
+
POLL_INTERVAL = 0.1
|
|
13
|
+
PROTOCOL = 'http'
|
|
14
|
+
|
|
15
|
+
REMOTE_PATHS = {
|
|
16
|
+
get_session: '/api/session',
|
|
17
|
+
post_message: '/api/message'
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
def initialize(outgoing, host, port)
|
|
21
|
+
@outgoing = outgoing
|
|
22
|
+
@host = host
|
|
23
|
+
@port = port
|
|
24
|
+
@running = false
|
|
25
|
+
@thread = nil
|
|
26
|
+
@sid = nil
|
|
27
|
+
setup_http
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def start
|
|
31
|
+
@running = true
|
|
32
|
+
@thread = Thread.new do
|
|
33
|
+
@http.start
|
|
34
|
+
request_session
|
|
35
|
+
|
|
36
|
+
send_queued while @running
|
|
37
|
+
end
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def join
|
|
42
|
+
@thread&.join
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def cleanup
|
|
46
|
+
@running = false
|
|
47
|
+
@http.finish if @http.started?
|
|
48
|
+
@thread&.kill if @thread&.alive?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def setup_http
|
|
54
|
+
@http = Net::HTTP.new(@host, @port)
|
|
55
|
+
@http.open_timeout = 1
|
|
56
|
+
@http.read_timeout = 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def request_session
|
|
60
|
+
Client.logger.info('making initial request for session...')
|
|
61
|
+
|
|
62
|
+
session_req = @http.get(REMOTE_PATHS.fetch(:get_session))
|
|
63
|
+
response = JSON.parse(session_req.body)
|
|
64
|
+
|
|
65
|
+
raise "failed to retrieve session: #{response}" if Integer(session_req.code) != 201
|
|
66
|
+
|
|
67
|
+
@sid = response['sid']
|
|
68
|
+
Client.logger.info("current session id: #{@sid}")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def if_not_ok(http_req, ok_code = 200)
|
|
72
|
+
yield http_req.body if Integer(http_req.code) != ok_code
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def send_queued
|
|
76
|
+
raw_msg = @outgoing.pop
|
|
77
|
+
return if raw_msg.nil? || raw_msg == :shutdown
|
|
78
|
+
|
|
79
|
+
Client.logger.info("processing message with #{@outgoing.size} messages outgoing")
|
|
80
|
+
|
|
81
|
+
message_data = {
|
|
82
|
+
from: @sid,
|
|
83
|
+
to: '*',
|
|
84
|
+
msg: raw_msg
|
|
85
|
+
}.to_json
|
|
86
|
+
|
|
87
|
+
headers = { 'Content-Type' => 'application/json' }
|
|
88
|
+
post_req = @http.post(REMOTE_PATHS.fetch(:post_message), message_data, headers)
|
|
89
|
+
|
|
90
|
+
if_not_ok(post_req) do |err|
|
|
91
|
+
Client.logger.warn("failed to post message: #{message_data} #{err}")
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/glottis/version.rb
CHANGED
data/lib/glottis.rb
CHANGED
metadata
CHANGED
|
@@ -1,86 +1,98 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glottis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Max DeLiso
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
13
|
+
name: slop
|
|
15
14
|
requirement: !ruby/object:Gem::Requirement
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
18
|
+
version: '4.10'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
25
|
+
version: '4.10'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
27
|
+
name: bundler
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
29
42
|
requirement: !ruby/object:Gem::Requirement
|
|
30
43
|
requirements:
|
|
31
44
|
- - "~>"
|
|
32
45
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
34
|
-
type: :
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
35
48
|
prerelease: false
|
|
36
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
50
|
requirements:
|
|
38
51
|
- - "~>"
|
|
39
52
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '
|
|
53
|
+
version: '13.0'
|
|
41
54
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
55
|
+
name: rspec
|
|
43
56
|
requirement: !ruby/object:Gem::Requirement
|
|
44
57
|
requirements:
|
|
45
58
|
- - "~>"
|
|
46
59
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
60
|
+
version: '3.0'
|
|
48
61
|
type: :development
|
|
49
62
|
prerelease: false
|
|
50
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
64
|
requirements:
|
|
52
65
|
- - "~>"
|
|
53
66
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
67
|
+
version: '3.0'
|
|
55
68
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
69
|
+
name: rubocop
|
|
57
70
|
requirement: !ruby/object:Gem::Requirement
|
|
58
71
|
requirements:
|
|
59
72
|
- - "~>"
|
|
60
73
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: '
|
|
74
|
+
version: '1.0'
|
|
62
75
|
type: :development
|
|
63
76
|
prerelease: false
|
|
64
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
78
|
requirements:
|
|
66
79
|
- - "~>"
|
|
67
80
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: '
|
|
81
|
+
version: '1.0'
|
|
69
82
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
83
|
+
name: webrick
|
|
71
84
|
requirement: !ruby/object:Gem::Requirement
|
|
72
85
|
requirements:
|
|
73
86
|
- - "~>"
|
|
74
87
|
- !ruby/object:Gem::Version
|
|
75
|
-
version:
|
|
88
|
+
version: '1.8'
|
|
76
89
|
type: :development
|
|
77
90
|
prerelease: false
|
|
78
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
92
|
requirements:
|
|
80
93
|
- - "~>"
|
|
81
94
|
- !ruby/object:Gem::Version
|
|
82
|
-
version:
|
|
83
|
-
description:
|
|
95
|
+
version: '1.8'
|
|
84
96
|
email:
|
|
85
97
|
- maxdeliso@gmail.com
|
|
86
98
|
executables:
|
|
@@ -88,14 +100,9 @@ executables:
|
|
|
88
100
|
extensions: []
|
|
89
101
|
extra_rdoc_files: []
|
|
90
102
|
files:
|
|
91
|
-
-
|
|
92
|
-
- ".rubocop.yml"
|
|
93
|
-
- ".travis.yml"
|
|
94
|
-
- Gemfile
|
|
103
|
+
- CHANGELOG.md
|
|
95
104
|
- README.md
|
|
96
105
|
- Rakefile
|
|
97
|
-
- bin/console
|
|
98
|
-
- bin/setup
|
|
99
106
|
- exe/glottis
|
|
100
107
|
- glottis.gemspec
|
|
101
108
|
- lib/glottis.rb
|
|
@@ -110,8 +117,12 @@ files:
|
|
|
110
117
|
homepage: https://github.com/maxdeliso/glottis
|
|
111
118
|
licenses:
|
|
112
119
|
- MIT
|
|
113
|
-
metadata:
|
|
114
|
-
|
|
120
|
+
metadata:
|
|
121
|
+
homepage_uri: https://github.com/maxdeliso/glottis
|
|
122
|
+
source_code_uri: https://github.com/maxdeliso/glottis
|
|
123
|
+
changelog_uri: https://github.com/maxdeliso/glottis/blob/main/CHANGELOG.md
|
|
124
|
+
bug_tracker_uri: https://github.com/maxdeliso/glottis/issues
|
|
125
|
+
rubygems_mfa_required: 'true'
|
|
115
126
|
rdoc_options: []
|
|
116
127
|
require_paths:
|
|
117
128
|
- lib
|
|
@@ -119,16 +130,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
119
130
|
requirements:
|
|
120
131
|
- - ">="
|
|
121
132
|
- !ruby/object:Gem::Version
|
|
122
|
-
version:
|
|
133
|
+
version: 3.0.0
|
|
123
134
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
124
135
|
requirements:
|
|
125
136
|
- - ">="
|
|
126
137
|
- !ruby/object:Gem::Version
|
|
127
138
|
version: '0'
|
|
128
139
|
requirements: []
|
|
129
|
-
|
|
130
|
-
rubygems_version: 2.4.5.1
|
|
131
|
-
signing_key:
|
|
140
|
+
rubygems_version: 3.6.9
|
|
132
141
|
specification_version: 4
|
|
133
142
|
summary: simple http messaging client
|
|
134
143
|
test_files: []
|
data/.gitignore
DELETED
data/.rubocop.yml
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/bin/console
DELETED