glottis 0.3.0 → 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 +4 -4
- data/CHANGELOG.md +3 -2
- data/Rakefile +10 -11
- data/exe/glottis +23 -24
- data/glottis.gemspec +5 -6
- data/lib/glottis/client.rb +16 -13
- data/lib/glottis/exceptions/glottis_exception.rb +8 -9
- data/lib/glottis/exceptions/user_exited_exception.rb +10 -11
- data/lib/glottis/handlers/console_input_handler.rb +52 -50
- data/lib/glottis/handlers/console_output_handler.rb +40 -41
- data/lib/glottis/handlers/remote_input_handler.rb +86 -87
- data/lib/glottis/handlers/remote_output_handler.rb +96 -100
- data/lib/glottis/version.rb +1 -1
- data/lib/glottis.rb +4 -5
- metadata +18 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
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
CHANGED
|
@@ -5,15 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semmle.com/semver/1.0.0/).
|
|
7
7
|
|
|
8
|
-
## [0.3.
|
|
8
|
+
## [0.3.1] - 2026-07-24
|
|
9
9
|
|
|
10
10
|
### Changed
|
|
11
11
|
- Bumped required Ruby version to `>= 3.0.0`.
|
|
12
12
|
- Removed `rb-readline` dependency in favor of Ruby standard library `reline`/`readline`.
|
|
13
13
|
- Updated `slop` runtime dependency to `~> 4.10`.
|
|
14
|
+
- Explicitly set `Content-Type: application/json` headers on outgoing HTTP request posts.
|
|
14
15
|
- Modernized gemspec metadata and file list configuration.
|
|
15
16
|
- Updated RuboCop configuration for Ruby 3.0+ compatibility (`Layout/LineLength`).
|
|
16
17
|
- Migrated CI from Travis CI to GitHub Actions (`.github/workflows/ci.yml`).
|
|
17
18
|
|
|
18
19
|
### Added
|
|
19
|
-
- Comprehensive RSpec test suite covering core client and
|
|
20
|
+
- Comprehensive RSpec test suite covering core client, handlers, and live HTTP mock server integration.
|
data/Rakefile
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
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
|
-
|
|
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,24 +1,23 @@
|
|
|
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
|
-
|
|
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
|
@@ -20,19 +20,18 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
spec.metadata['bug_tracker_uri'] = 'https://github.com/maxdeliso/glottis/issues'
|
|
21
21
|
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
22
22
|
|
|
23
|
-
spec.files = Dir.glob('{exe,lib}/**/*')
|
|
24
|
-
|
|
23
|
+
spec.files = Dir.glob('{exe,lib}/**/*').select { |f| File.file?(f) } +
|
|
24
|
+
%w[Rakefile README.md CHANGELOG.md glottis.gemspec]
|
|
25
25
|
|
|
26
26
|
spec.bindir = 'exe'
|
|
27
27
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
28
28
|
spec.require_paths = ['lib']
|
|
29
29
|
|
|
30
|
-
spec.
|
|
30
|
+
spec.add_dependency 'slop', '~> 4.10'
|
|
31
31
|
|
|
32
|
-
spec.add_development_dependency 'bundler', '
|
|
32
|
+
spec.add_development_dependency 'bundler', '>= 2.0'
|
|
33
33
|
spec.add_development_dependency 'rake', '~> 13.0'
|
|
34
34
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
35
35
|
spec.add_development_dependency 'rubocop', '~> 1.0'
|
|
36
|
+
spec.add_development_dependency 'webrick', '~> 1.8'
|
|
36
37
|
end
|
|
37
|
-
|
|
38
|
-
|
data/lib/glottis/client.rb
CHANGED
|
@@ -25,32 +25,35 @@ module Glottis
|
|
|
25
25
|
def run
|
|
26
26
|
Client.logger.info('starting...')
|
|
27
27
|
|
|
28
|
-
|
|
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)
|
|
28
|
+
start_handlers
|
|
36
29
|
@handlers.each(&:join)
|
|
37
30
|
rescue Glottis::Exceptions::UserExitedException
|
|
38
31
|
Client.logger.info('User exited.')
|
|
39
32
|
rescue Interrupt
|
|
40
33
|
Client.logger.info('Received interrupt signal. Exiting...')
|
|
41
|
-
rescue Errno::ECONNREFUSED =>
|
|
42
|
-
Client.logger.error("Failed to connect to host: #{
|
|
43
|
-
rescue StandardError =>
|
|
44
|
-
Client.logger.warn("Shutting down due to error: #{
|
|
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}")
|
|
45
38
|
ensure
|
|
46
39
|
stop_handlers
|
|
47
40
|
end
|
|
48
41
|
|
|
49
42
|
private
|
|
50
43
|
|
|
44
|
+
def start_handlers
|
|
45
|
+
@handlers = [
|
|
46
|
+
Handlers::ConsoleInputHandler.new(@outgoing),
|
|
47
|
+
Handlers::ConsoleOutputHandler.new(@incoming),
|
|
48
|
+
Handlers::RemoteInputHandler.new(@incoming, @host, @port),
|
|
49
|
+
Handlers::RemoteOutputHandler.new(@outgoing, @host, @port)
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
@handlers.each(&:start)
|
|
53
|
+
end
|
|
54
|
+
|
|
51
55
|
def stop_handlers
|
|
52
56
|
@handlers.each(&:cleanup)
|
|
53
57
|
end
|
|
54
58
|
end
|
|
55
59
|
end
|
|
56
|
-
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Glottis
|
|
4
|
-
module Exceptions
|
|
5
|
-
class GlottisException < StandardError
|
|
6
|
-
end
|
|
7
|
-
end
|
|
8
|
-
end
|
|
9
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Glottis
|
|
4
|
+
module Exceptions
|
|
5
|
+
class GlottisException < StandardError
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -1,11 +1,10 @@
|
|
|
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
|
+
# 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,50 +1,52 @@
|
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
end
|
|
50
|
-
|
|
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,41 +1,40 @@
|
|
|
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
|
+
# 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,87 +1,86 @@
|
|
|
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 =>
|
|
65
|
-
Client.logger.error("Error reading message stream: #{
|
|
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 =>
|
|
80
|
-
Client.logger.warn("Failed to parse JSON payload: #{
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
end
|
|
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 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,100 +1,96 @@
|
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
@
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@http =
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
response
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
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
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'glottis/version'
|
|
4
|
-
require 'glottis/client'
|
|
5
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'glottis/version'
|
|
4
|
+
require 'glottis/client'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glottis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Max DeLiso
|
|
@@ -27,14 +27,14 @@ dependencies:
|
|
|
27
27
|
name: bundler
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- - "
|
|
30
|
+
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '2.0'
|
|
33
33
|
type: :development
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
|
-
- - "
|
|
37
|
+
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '2.0'
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
@@ -79,6 +79,20 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '1.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: webrick
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '1.8'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '1.8'
|
|
82
96
|
email:
|
|
83
97
|
- maxdeliso@gmail.com
|
|
84
98
|
executables:
|
|
@@ -123,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
123
137
|
- !ruby/object:Gem::Version
|
|
124
138
|
version: '0'
|
|
125
139
|
requirements: []
|
|
126
|
-
rubygems_version: 3.6.
|
|
140
|
+
rubygems_version: 3.6.9
|
|
127
141
|
specification_version: 4
|
|
128
142
|
summary: simple http messaging client
|
|
129
143
|
test_files: []
|