glottis 0.3.0 → 0.4.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 +4 -4
- data/CHANGELOG.md +11 -2
- data/README.md +1 -1
- data/Rakefile +10 -11
- data/exe/glottis +23 -24
- data/glottis.gemspec +5 -6
- data/lib/glottis/client.rb +23 -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 +46 -16
- data/lib/glottis/handlers/remote_output_handler.rb +48 -22
- 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: b19cc9121495467f570a8989b7cbd66aba5069f70bfa7044e36541ae8d407483
|
|
4
|
+
data.tar.gz: a854ee9fe9e2cfa2b0969e495b9853884738d202e7a1b21ccae2f8b36bc7f9e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dc5f81d11d5ab1a4fee50a7e1384534799abb05d24ea34c47460d8c05d07834f3ce3a5684a945f824315d8e9df83e11fe67df1511b0dee8d08000b486eb090e6
|
|
7
|
+
data.tar.gz: 2de12c2620eda6634c55775794408f6569aaab040cfaa64de49639f17e7434d8dce573582b5e5cc7cd6f6fecca794a12aec10559c63f8a45d87f153fc9525269
|
data/CHANGELOG.md
CHANGED
|
@@ -5,15 +5,24 @@ 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.
|
|
8
|
+
## [0.4.0] - 2026-07-24
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Minor version release for full Ruby 3.4+ compatibility and modernized dependency stack.
|
|
12
|
+
- Enhanced `RemoteInputHandler` with Server-Sent Events (SSE) stream parsing (`data: {...}`).
|
|
13
|
+
- Enhanced `RemoteOutputHandler` with automatic session cookie forwarding (`SID`) and strict JSON payload formatting (`{"to": "*", "body": "..."}`).
|
|
14
|
+
- Verified live end-to-end message streaming against the modernized Valyx server.
|
|
15
|
+
|
|
16
|
+
## [0.3.1] - 2026-07-24
|
|
9
17
|
|
|
10
18
|
### Changed
|
|
11
19
|
- Bumped required Ruby version to `>= 3.0.0`.
|
|
12
20
|
- Removed `rb-readline` dependency in favor of Ruby standard library `reline`/`readline`.
|
|
13
21
|
- Updated `slop` runtime dependency to `~> 4.10`.
|
|
22
|
+
- Explicitly set `Content-Type: application/json` headers on outgoing HTTP request posts.
|
|
14
23
|
- Modernized gemspec metadata and file list configuration.
|
|
15
24
|
- Updated RuboCop configuration for Ruby 3.0+ compatibility (`Layout/LineLength`).
|
|
16
25
|
- Migrated CI from Travis CI to GitHub Actions (`.github/workflows/ci.yml`).
|
|
17
26
|
|
|
18
27
|
### Added
|
|
19
|
-
- Comprehensive RSpec test suite covering core client and
|
|
28
|
+
- Comprehensive RSpec test suite covering core client, handlers, and live HTTP mock server integration.
|
data/README.md
CHANGED
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,42 @@ 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
|
+
remote_output = Handlers::RemoteOutputHandler.new(@outgoing, @host, @port)
|
|
46
|
+
remote_output.start
|
|
47
|
+
|
|
48
|
+
sleep 0.1 until remote_output.cookie || remote_output.sid
|
|
49
|
+
|
|
50
|
+
cookie = remote_output.cookie
|
|
51
|
+
remote_input = Handlers::RemoteInputHandler.new(@incoming, @host, @port, cookie)
|
|
52
|
+
remote_input.start
|
|
53
|
+
|
|
54
|
+
@handlers = [
|
|
55
|
+
Handlers::ConsoleInputHandler.new(@outgoing).start,
|
|
56
|
+
Handlers::ConsoleOutputHandler.new(@incoming).start,
|
|
57
|
+
remote_input,
|
|
58
|
+
remote_output
|
|
59
|
+
]
|
|
60
|
+
end
|
|
61
|
+
|
|
51
62
|
def stop_handlers
|
|
52
63
|
@handlers.each(&:cleanup)
|
|
53
64
|
end
|
|
54
65
|
end
|
|
55
66
|
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
|
|
@@ -13,13 +13,15 @@ module Glottis
|
|
|
13
13
|
STREAM_DELIMITER = "\0"
|
|
14
14
|
READ_TIMEOUT = 3600
|
|
15
15
|
REMOTE_PATHS = {
|
|
16
|
-
get_message_stream: '/api/messages/stream'
|
|
16
|
+
get_message_stream: '/api/v1/messages/stream',
|
|
17
|
+
legacy_message_stream: '/api/messages/stream'
|
|
17
18
|
}.freeze
|
|
18
19
|
|
|
19
|
-
def initialize(incoming, host, port)
|
|
20
|
+
def initialize(incoming, host, port, cookie = nil)
|
|
20
21
|
@incoming = incoming
|
|
21
22
|
@host = host
|
|
22
23
|
@port = port
|
|
24
|
+
@cookie = cookie
|
|
23
25
|
@running = false
|
|
24
26
|
@thread = nil
|
|
25
27
|
@buffer = String.new
|
|
@@ -49,39 +51,67 @@ module Glottis
|
|
|
49
51
|
|
|
50
52
|
def setup_http
|
|
51
53
|
@http = Net::HTTP.new(@host, @port)
|
|
52
|
-
@http.open_timeout =
|
|
54
|
+
@http.open_timeout = 2
|
|
53
55
|
@http.read_timeout = READ_TIMEOUT
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def read_loop
|
|
57
59
|
Client.logger.info('reading stream...')
|
|
58
|
-
@
|
|
60
|
+
headers = @cookie ? { 'Cookie' => @cookie } : {}
|
|
61
|
+
|
|
62
|
+
stream_request(headers) do |chunk|
|
|
59
63
|
break unless @running
|
|
60
64
|
|
|
61
|
-
Client.logger.debug("got chunk of length: #{chunk.length}")
|
|
62
65
|
process_chunk(chunk)
|
|
63
66
|
end
|
|
64
|
-
rescue StandardError =>
|
|
65
|
-
Client.logger.error("Error reading message stream: #{
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
Client.logger.error("Error reading message stream: #{e.message}") if @running
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def stream_request(headers, &block)
|
|
72
|
+
req = Net::HTTP::Get.new(REMOTE_PATHS.fetch(:get_message_stream), headers)
|
|
73
|
+
|
|
74
|
+
@http.request(req) do |response|
|
|
75
|
+
if Integer(response.code) == 404
|
|
76
|
+
legacy_req = Net::HTTP::Get.new(REMOTE_PATHS.fetch(:legacy_message_stream), headers)
|
|
77
|
+
@http.request(legacy_req) { |legacy_resp| legacy_resp.read_body(&block) }
|
|
78
|
+
else
|
|
79
|
+
response.read_body(&block)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
66
82
|
end
|
|
67
83
|
|
|
68
84
|
def process_chunk(chunk)
|
|
69
85
|
@buffer << chunk
|
|
70
86
|
|
|
87
|
+
# Process SSE format ("data: {...}\n\n")
|
|
88
|
+
while (line_end = @buffer.index("\n"))
|
|
89
|
+
line = @buffer.slice!(0..line_end).strip
|
|
90
|
+
next if line.empty? || line.start_with?(':')
|
|
91
|
+
|
|
92
|
+
if line.start_with?('data:')
|
|
93
|
+
json_str = line.sub(/^data:\s*/, '').chomp
|
|
94
|
+
parse_and_push(json_str)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Process null-delimited format
|
|
71
99
|
while (delimiter_index = @buffer.index(STREAM_DELIMITER))
|
|
72
100
|
msg_raw = @buffer.slice!(0..delimiter_index)
|
|
73
101
|
msg_data = msg_raw.chomp(STREAM_DELIMITER)
|
|
74
|
-
|
|
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
|
|
102
|
+
parse_and_push(msg_data)
|
|
82
103
|
end
|
|
83
104
|
end
|
|
105
|
+
|
|
106
|
+
def parse_and_push(raw)
|
|
107
|
+
return if raw.empty?
|
|
108
|
+
|
|
109
|
+
parsed_msg = JSON.parse(raw)
|
|
110
|
+
parsed_msg['msg'] ||= parsed_msg['body']
|
|
111
|
+
@incoming.push(parsed_msg)
|
|
112
|
+
rescue JSON::ParserError => e
|
|
113
|
+
Client.logger.warn("Failed to parse JSON payload: #{e.message}")
|
|
114
|
+
end
|
|
84
115
|
end
|
|
85
116
|
end
|
|
86
117
|
end
|
|
87
|
-
|
|
@@ -13,10 +13,14 @@ module Glottis
|
|
|
13
13
|
PROTOCOL = 'http'
|
|
14
14
|
|
|
15
15
|
REMOTE_PATHS = {
|
|
16
|
-
get_session: '/api/session',
|
|
17
|
-
|
|
16
|
+
get_session: '/api/v1/session',
|
|
17
|
+
legacy_session: '/api/session',
|
|
18
|
+
post_message: '/api/v1/messages',
|
|
19
|
+
legacy_post_message: '/api/message'
|
|
18
20
|
}.freeze
|
|
19
21
|
|
|
22
|
+
attr_reader :cookie, :sid
|
|
23
|
+
|
|
20
24
|
def initialize(outgoing, host, port)
|
|
21
25
|
@outgoing = outgoing
|
|
22
26
|
@host = host
|
|
@@ -24,6 +28,7 @@ module Glottis
|
|
|
24
28
|
@running = false
|
|
25
29
|
@thread = nil
|
|
26
30
|
@sid = nil
|
|
31
|
+
@cookie = nil
|
|
27
32
|
setup_http
|
|
28
33
|
end
|
|
29
34
|
|
|
@@ -33,9 +38,7 @@ module Glottis
|
|
|
33
38
|
@http.start
|
|
34
39
|
request_session
|
|
35
40
|
|
|
36
|
-
while @running
|
|
37
|
-
send_queued
|
|
38
|
-
end
|
|
41
|
+
send_queued while @running
|
|
39
42
|
end
|
|
40
43
|
self
|
|
41
44
|
end
|
|
@@ -54,26 +57,47 @@ module Glottis
|
|
|
54
57
|
|
|
55
58
|
def setup_http
|
|
56
59
|
@http = Net::HTTP.new(@host, @port)
|
|
57
|
-
@http.open_timeout =
|
|
58
|
-
@http.read_timeout =
|
|
60
|
+
@http.open_timeout = 2
|
|
61
|
+
@http.read_timeout = 2
|
|
59
62
|
end
|
|
60
63
|
|
|
61
64
|
def request_session
|
|
62
65
|
Client.logger.info('making initial request for session...')
|
|
63
66
|
|
|
64
|
-
|
|
65
|
-
|
|
67
|
+
headers = { 'Content-Type' => 'application/json' }
|
|
68
|
+
session_req = fetch_session(headers)
|
|
69
|
+
|
|
70
|
+
code = Integer(session_req.code)
|
|
71
|
+
raise "failed to retrieve session: #{session_req.body}" unless [200, 201].include?(code)
|
|
72
|
+
|
|
73
|
+
parse_session_response(session_req)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def fetch_session(headers)
|
|
77
|
+
req = @http.post(REMOTE_PATHS.fetch(:get_session), '', headers)
|
|
78
|
+
return req if Integer(req.code) != 404
|
|
79
|
+
|
|
80
|
+
@http.get(REMOTE_PATHS.fetch(:legacy_session))
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def parse_session_response(session_req)
|
|
84
|
+
if session_req['set-cookie']
|
|
85
|
+
raw_cookie = session_req['set-cookie']
|
|
86
|
+
@cookie = raw_cookie.split(';').first
|
|
87
|
+
end
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
|
|
89
|
+
response = begin
|
|
90
|
+
JSON.parse(session_req.body)
|
|
91
|
+
rescue StandardError
|
|
92
|
+
{}
|
|
69
93
|
end
|
|
70
94
|
|
|
71
|
-
@sid = response['sid']
|
|
95
|
+
@sid = response['username'] || response['sid'] || @cookie
|
|
72
96
|
Client.logger.info("current session id: #{@sid}")
|
|
73
97
|
end
|
|
74
98
|
|
|
75
|
-
def if_not_ok(http_req,
|
|
76
|
-
yield http_req.body
|
|
99
|
+
def if_not_ok(http_req, ok_codes = [200, 201])
|
|
100
|
+
yield http_req.body unless ok_codes.include?(Integer(http_req.code))
|
|
77
101
|
end
|
|
78
102
|
|
|
79
103
|
def send_queued
|
|
@@ -82,19 +106,21 @@ module Glottis
|
|
|
82
106
|
|
|
83
107
|
Client.logger.info("processing message with #{@outgoing.size} messages outgoing")
|
|
84
108
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
to: '*',
|
|
88
|
-
msg: raw_msg
|
|
89
|
-
}.to_json
|
|
109
|
+
headers = { 'Content-Type' => 'application/json' }
|
|
110
|
+
headers['Cookie'] = @cookie if @cookie
|
|
90
111
|
|
|
91
|
-
|
|
112
|
+
modern_data = { to: '*', body: raw_msg }.to_json
|
|
113
|
+
post_req = @http.post(REMOTE_PATHS.fetch(:post_message), modern_data, headers)
|
|
114
|
+
|
|
115
|
+
if Integer(post_req.code) == 404
|
|
116
|
+
legacy_data = { from: @sid, to: '*', msg: raw_msg }.to_json
|
|
117
|
+
post_req = @http.post(REMOTE_PATHS.fetch(:legacy_post_message), legacy_data, headers)
|
|
118
|
+
end
|
|
92
119
|
|
|
93
120
|
if_not_ok(post_req) do |err|
|
|
94
|
-
Client.logger.warn("failed to post message: #{
|
|
121
|
+
Client.logger.warn("failed to post message: #{raw_msg} #{err}")
|
|
95
122
|
end
|
|
96
123
|
end
|
|
97
124
|
end
|
|
98
125
|
end
|
|
99
126
|
end
|
|
100
|
-
|
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.
|
|
4
|
+
version: 0.4.0
|
|
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: []
|