glottis 0.1.2 → 0.3.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 +5 -5
- data/CHANGELOG.md +19 -0
- data/README.md +48 -21
- data/Rakefile +11 -1
- data/exe/glottis +24 -22
- data/glottis.gemspec +38 -30
- data/lib/glottis/client.rb +56 -46
- data/lib/glottis/exceptions/glottis_exception.rb +9 -6
- data/lib/glottis/exceptions/user_exited_exception.rb +11 -8
- data/lib/glottis/handlers/console_input_handler.rb +50 -32
- data/lib/glottis/handlers/console_output_handler.rb +41 -21
- data/lib/glottis/handlers/remote_input_handler.rb +87 -53
- data/lib/glottis/handlers/remote_output_handler.rb +100 -107
- data/lib/glottis/version.rb +5 -4
- data/lib/glottis.rb +5 -2
- metadata +23 -42
- 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: c4738a04eaf3d36ff79c3b8b18e9408d825729e40a2314f76049aeb848e9fb23
|
|
4
|
+
data.tar.gz: 3bc4629dfdb5dbc0b9d55b78369fb4f5be2dc9032a0ac1b5b2f922fdff4b7a6d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8b36bbc01a00d869a1bd91f58ccc31348332244896e6bca5b6c2c2ba96ce8c1cdb8a16254a50fe4a4c005cc6b3d79a97f789bae8b4c01d6e084aa497f9bb4081
|
|
7
|
+
data.tar.gz: 74dcce5991937a677eddbe3ebd51de4f792cd0c47ecff50f4d606288fe12c880ef36b9e9c280de8c7f269bf1e82a50248b0d894e38c34b2be2924ad4de3b6070
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
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.0] - 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
|
+
- Modernized gemspec metadata and file list configuration.
|
|
15
|
+
- Updated RuboCop configuration for Ruby 3.0+ compatibility (`Layout/LineLength`).
|
|
16
|
+
- Migrated CI from Travis CI to GitHub Actions (`.github/workflows/ci.yml`).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Comprehensive RSpec test suite covering core client and handlers.
|
data/README.md
CHANGED
|
@@ -1,21 +1,48 @@
|
|
|
1
|
-
# glottis
|
|
2
|
-
|
|
3
|
-
A CLI for the [valyx](https://github.com/maxdeliso/valyx) message passing server.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
# glottis
|
|
2
|
+
|
|
3
|
+
A CLI for the [valyx](https://github.com/maxdeliso/valyx) message passing server.
|
|
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
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Install the gem by executing:
|
|
15
|
+
|
|
16
|
+
$ gem install glottis
|
|
17
|
+
|
|
18
|
+
Or add it to your application's Gemfile:
|
|
19
|
+
|
|
20
|
+
$ gem 'glottis', '~> 0.3'
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
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
|
|
31
|
+
|
|
32
|
+
## Development
|
|
33
|
+
|
|
34
|
+
To run the test suite and linter:
|
|
35
|
+
|
|
36
|
+
$ bundle exec rake
|
|
37
|
+
|
|
38
|
+
To install this gem onto your local machine:
|
|
39
|
+
|
|
40
|
+
$ bundle exec rake install
|
|
41
|
+
|
|
42
|
+
## Contributing
|
|
43
|
+
|
|
44
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/maxdeliso/glottis.
|
|
45
|
+
|
|
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 +1,11 @@
|
|
|
1
|
-
|
|
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]
|
|
11
|
+
|
data/exe/glottis
CHANGED
|
@@ -1,22 +1,24 @@
|
|
|
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
|
|
24
|
+
|
data/glottis.gemspec
CHANGED
|
@@ -1,30 +1,38 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
spec.
|
|
9
|
-
spec.
|
|
10
|
-
spec.
|
|
11
|
-
spec.
|
|
12
|
-
spec.
|
|
13
|
-
spec.
|
|
14
|
-
|
|
15
|
-
spec.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
spec.
|
|
20
|
-
spec.
|
|
21
|
-
spec.
|
|
22
|
-
|
|
23
|
-
spec.
|
|
24
|
-
spec.
|
|
25
|
-
|
|
26
|
-
spec.
|
|
27
|
-
spec.
|
|
28
|
-
spec.
|
|
29
|
-
|
|
30
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'glottis/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'glottis'
|
|
9
|
+
spec.version = Glottis::VERSION
|
|
10
|
+
spec.authors = ['Max DeLiso']
|
|
11
|
+
spec.email = ['maxdeliso@gmail.com']
|
|
12
|
+
spec.licenses = ['MIT']
|
|
13
|
+
spec.summary = 'simple http messaging client'
|
|
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'
|
|
22
|
+
|
|
23
|
+
spec.files = Dir.glob('{exe,lib}/**/*') + %w[Rakefile README.md CHANGELOG.md glottis.gemspec]
|
|
24
|
+
spec.files = spec.files.select { |f| File.file?(f) }
|
|
25
|
+
|
|
26
|
+
spec.bindir = 'exe'
|
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
28
|
+
spec.require_paths = ['lib']
|
|
29
|
+
|
|
30
|
+
spec.add_runtime_dependency 'slop', '~> 4.10'
|
|
31
|
+
|
|
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
|
+
end
|
|
37
|
+
|
|
38
|
+
|
data/lib/glottis/client.rb
CHANGED
|
@@ -1,46 +1,56 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require '
|
|
4
|
-
require 'glottis/handlers/
|
|
5
|
-
require 'glottis/handlers/
|
|
6
|
-
require 'glottis/handlers/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@
|
|
18
|
-
@
|
|
19
|
-
@
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Handlers::
|
|
30
|
-
Handlers::
|
|
31
|
-
Handlers::
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
rescue
|
|
38
|
-
Client.logger.
|
|
39
|
-
rescue
|
|
40
|
-
Client.logger.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'glottis/handlers/console_input_handler'
|
|
5
|
+
require 'glottis/handlers/console_output_handler'
|
|
6
|
+
require 'glottis/handlers/remote_input_handler'
|
|
7
|
+
require 'glottis/handlers/remote_output_handler'
|
|
8
|
+
|
|
9
|
+
module Glottis
|
|
10
|
+
# Client class used to interact with the specified host and port.
|
|
11
|
+
class Client
|
|
12
|
+
def self.logger
|
|
13
|
+
@logger ||= Logger.new($stdout)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(host, port, opts = {})
|
|
17
|
+
@host = host
|
|
18
|
+
@port = port
|
|
19
|
+
@outgoing = Queue.new
|
|
20
|
+
@incoming = Queue.new
|
|
21
|
+
@handlers = []
|
|
22
|
+
Client.logger.level = opts[:verbose] ? Logger::DEBUG : Logger::WARN
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def run
|
|
26
|
+
Client.logger.info('starting...')
|
|
27
|
+
|
|
28
|
+
@handlers = [
|
|
29
|
+
Handlers::ConsoleInputHandler.new(@outgoing),
|
|
30
|
+
Handlers::ConsoleOutputHandler.new(@incoming),
|
|
31
|
+
Handlers::RemoteInputHandler.new(@incoming, @host, @port),
|
|
32
|
+
Handlers::RemoteOutputHandler.new(@outgoing, @host, @port)
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
@handlers.each(&:start)
|
|
36
|
+
@handlers.each(&:join)
|
|
37
|
+
rescue Glottis::Exceptions::UserExitedException
|
|
38
|
+
Client.logger.info('User exited.')
|
|
39
|
+
rescue Interrupt
|
|
40
|
+
Client.logger.info('Received interrupt signal. Exiting...')
|
|
41
|
+
rescue Errno::ECONNREFUSED => conn_ref
|
|
42
|
+
Client.logger.error("Failed to connect to host: #{conn_ref.message}")
|
|
43
|
+
rescue StandardError => ex
|
|
44
|
+
Client.logger.warn("Shutting down due to error: #{ex.message}")
|
|
45
|
+
ensure
|
|
46
|
+
stop_handlers
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def stop_handlers
|
|
52
|
+
@handlers.each(&:cleanup)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
@@ -1,8 +1,11 @@
|
|
|
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
|
|
11
|
+
|
|
@@ -1,32 +1,50 @@
|
|
|
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
|
+
raise Glottis::Exceptions::UserExitedException, 'User exited readline stream' if user_input.nil?
|
|
40
|
+
|
|
41
|
+
@outgoing.push(user_input)
|
|
42
|
+
end
|
|
43
|
+
rescue Glottis::Exceptions::UserExitedException => ex
|
|
44
|
+
Client.logger.info("Console input ended: #{ex.message}")
|
|
45
|
+
raise ex
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
@@ -1,21 +1,41 @@
|
|
|
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
|
|
41
|
+
|
|
@@ -1,53 +1,87 @@
|
|
|
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 => ex
|
|
65
|
+
Client.logger.error("Error reading message stream: #{ex.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 => err
|
|
80
|
+
Client.logger.warn("Failed to parse JSON payload: #{err.message}")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
@@ -1,107 +1,100 @@
|
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if_not_ok(post_req) do |err|
|
|
102
|
-
Client.logger.warn("failed to post message: #{message_data} #{err}")
|
|
103
|
-
end
|
|
104
|
-
end # send_queued
|
|
105
|
-
end # RemoteOutputHandler
|
|
106
|
-
end # Handlers
|
|
107
|
-
end # Glottis
|
|
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
|
+
while @running
|
|
37
|
+
send_queued
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def join
|
|
44
|
+
@thread&.join
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def cleanup
|
|
48
|
+
@running = false
|
|
49
|
+
@http.finish if @http.started?
|
|
50
|
+
@thread&.kill if @thread&.alive?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def setup_http
|
|
56
|
+
@http = Net::HTTP.new(@host, @port)
|
|
57
|
+
@http.open_timeout = 1
|
|
58
|
+
@http.read_timeout = 1
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def request_session
|
|
62
|
+
Client.logger.info('making initial request for session...')
|
|
63
|
+
|
|
64
|
+
session_req = @http.get(REMOTE_PATHS.fetch(:get_session))
|
|
65
|
+
response = JSON.parse(session_req.body)
|
|
66
|
+
|
|
67
|
+
if Integer(session_req.code) != 201
|
|
68
|
+
raise "failed to retrieve session: #{response}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@sid = response['sid']
|
|
72
|
+
Client.logger.info("current session id: #{@sid}")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def if_not_ok(http_req, ok_code = 200)
|
|
76
|
+
yield http_req.body if Integer(http_req.code) != ok_code
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def send_queued
|
|
80
|
+
raw_msg = @outgoing.pop
|
|
81
|
+
return if raw_msg.nil? || raw_msg == :shutdown
|
|
82
|
+
|
|
83
|
+
Client.logger.info("processing message with #{@outgoing.size} messages outgoing")
|
|
84
|
+
|
|
85
|
+
message_data = {
|
|
86
|
+
from: @sid,
|
|
87
|
+
to: '*',
|
|
88
|
+
msg: raw_msg
|
|
89
|
+
}.to_json
|
|
90
|
+
|
|
91
|
+
post_req = @http.post(REMOTE_PATHS.fetch(:post_message), message_data)
|
|
92
|
+
|
|
93
|
+
if_not_ok(post_req) do |err|
|
|
94
|
+
Client.logger.warn("failed to post message: #{message_data} #{err}")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
data/lib/glottis/version.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glottis
|
|
4
|
+
VERSION = '0.3.0'
|
|
5
|
+
end
|
data/lib/glottis.rb
CHANGED
metadata
CHANGED
|
@@ -1,100 +1,84 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glottis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
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
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: rb-readline
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - "~>"
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: 0.5.3
|
|
20
|
-
type: :runtime
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - "~>"
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: 0.5.3
|
|
27
12
|
- !ruby/object:Gem::Dependency
|
|
28
13
|
name: slop
|
|
29
14
|
requirement: !ruby/object:Gem::Requirement
|
|
30
15
|
requirements:
|
|
31
16
|
- - "~>"
|
|
32
17
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '4.
|
|
18
|
+
version: '4.10'
|
|
34
19
|
type: :runtime
|
|
35
20
|
prerelease: false
|
|
36
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
22
|
requirements:
|
|
38
23
|
- - "~>"
|
|
39
24
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '4.
|
|
25
|
+
version: '4.10'
|
|
41
26
|
- !ruby/object:Gem::Dependency
|
|
42
27
|
name: bundler
|
|
43
28
|
requirement: !ruby/object:Gem::Requirement
|
|
44
29
|
requirements:
|
|
45
30
|
- - "~>"
|
|
46
31
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
32
|
+
version: '2.0'
|
|
48
33
|
type: :development
|
|
49
34
|
prerelease: false
|
|
50
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
36
|
requirements:
|
|
52
37
|
- - "~>"
|
|
53
38
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
39
|
+
version: '2.0'
|
|
55
40
|
- !ruby/object:Gem::Dependency
|
|
56
|
-
name:
|
|
41
|
+
name: rake
|
|
57
42
|
requirement: !ruby/object:Gem::Requirement
|
|
58
43
|
requirements:
|
|
59
44
|
- - "~>"
|
|
60
45
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 0
|
|
46
|
+
version: '13.0'
|
|
62
47
|
type: :development
|
|
63
48
|
prerelease: false
|
|
64
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
50
|
requirements:
|
|
66
51
|
- - "~>"
|
|
67
52
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 0
|
|
53
|
+
version: '13.0'
|
|
69
54
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name:
|
|
55
|
+
name: rspec
|
|
71
56
|
requirement: !ruby/object:Gem::Requirement
|
|
72
57
|
requirements:
|
|
73
58
|
- - "~>"
|
|
74
59
|
- !ruby/object:Gem::Version
|
|
75
|
-
version: '
|
|
60
|
+
version: '3.0'
|
|
76
61
|
type: :development
|
|
77
62
|
prerelease: false
|
|
78
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
79
64
|
requirements:
|
|
80
65
|
- - "~>"
|
|
81
66
|
- !ruby/object:Gem::Version
|
|
82
|
-
version: '
|
|
67
|
+
version: '3.0'
|
|
83
68
|
- !ruby/object:Gem::Dependency
|
|
84
69
|
name: rubocop
|
|
85
70
|
requirement: !ruby/object:Gem::Requirement
|
|
86
71
|
requirements:
|
|
87
72
|
- - "~>"
|
|
88
73
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: 0
|
|
74
|
+
version: '1.0'
|
|
90
75
|
type: :development
|
|
91
76
|
prerelease: false
|
|
92
77
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
78
|
requirements:
|
|
94
79
|
- - "~>"
|
|
95
80
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: 0
|
|
97
|
-
description:
|
|
81
|
+
version: '1.0'
|
|
98
82
|
email:
|
|
99
83
|
- maxdeliso@gmail.com
|
|
100
84
|
executables:
|
|
@@ -102,14 +86,9 @@ executables:
|
|
|
102
86
|
extensions: []
|
|
103
87
|
extra_rdoc_files: []
|
|
104
88
|
files:
|
|
105
|
-
-
|
|
106
|
-
- ".rubocop.yml"
|
|
107
|
-
- ".travis.yml"
|
|
108
|
-
- Gemfile
|
|
89
|
+
- CHANGELOG.md
|
|
109
90
|
- README.md
|
|
110
91
|
- Rakefile
|
|
111
|
-
- bin/console
|
|
112
|
-
- bin/setup
|
|
113
92
|
- exe/glottis
|
|
114
93
|
- glottis.gemspec
|
|
115
94
|
- lib/glottis.rb
|
|
@@ -124,8 +103,12 @@ files:
|
|
|
124
103
|
homepage: https://github.com/maxdeliso/glottis
|
|
125
104
|
licenses:
|
|
126
105
|
- MIT
|
|
127
|
-
metadata:
|
|
128
|
-
|
|
106
|
+
metadata:
|
|
107
|
+
homepage_uri: https://github.com/maxdeliso/glottis
|
|
108
|
+
source_code_uri: https://github.com/maxdeliso/glottis
|
|
109
|
+
changelog_uri: https://github.com/maxdeliso/glottis/blob/main/CHANGELOG.md
|
|
110
|
+
bug_tracker_uri: https://github.com/maxdeliso/glottis/issues
|
|
111
|
+
rubygems_mfa_required: 'true'
|
|
129
112
|
rdoc_options: []
|
|
130
113
|
require_paths:
|
|
131
114
|
- lib
|
|
@@ -133,16 +116,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
133
116
|
requirements:
|
|
134
117
|
- - ">="
|
|
135
118
|
- !ruby/object:Gem::Version
|
|
136
|
-
version:
|
|
119
|
+
version: 3.0.0
|
|
137
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
121
|
requirements:
|
|
139
122
|
- - ">="
|
|
140
123
|
- !ruby/object:Gem::Version
|
|
141
124
|
version: '0'
|
|
142
125
|
requirements: []
|
|
143
|
-
|
|
144
|
-
rubygems_version: 2.4.5.1
|
|
145
|
-
signing_key:
|
|
126
|
+
rubygems_version: 3.6.7
|
|
146
127
|
specification_version: 4
|
|
147
128
|
summary: simple http messaging client
|
|
148
129
|
test_files: []
|
data/.gitignore
DELETED
data/.rubocop.yml
DELETED
data/.travis.yml
DELETED
data/Gemfile
DELETED
data/bin/console
DELETED