forest_admin_rpc_agent 1.17.0 → 1.18.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
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8ae3dd638af2e91737911aa2436d2b25c11d556863ed638b08ebfbb95ee61cdd
|
|
4
|
+
data.tar.gz: 1e6e61aa06ee5015b272364e9ce6f49c6e6ffb218a16ac6f981a7d1219d31767
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8589283a71fb4124c12c1efa1d30bdac436cd2bb595a2c309bea55b327e639bf25cddbb3775e353d6b528f7e167e56729a295ec49d8d5aa7967da86759e1dc2e
|
|
7
|
+
data.tar.gz: c3419b02182fec4b8ac8078df6efc4d56abbf5dea07120fe6770e50278ad1c00f41e486464c34d7115757988773562eff0b7d19502fb89650d77d8cc59976794
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: forest_admin_rpc_agent
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matthieu
|
|
@@ -176,10 +176,7 @@ files:
|
|
|
176
176
|
- lib/forest_admin_rpc_agent/routes/list.rb
|
|
177
177
|
- lib/forest_admin_rpc_agent/routes/native_query.rb
|
|
178
178
|
- lib/forest_admin_rpc_agent/routes/schema.rb
|
|
179
|
-
- lib/forest_admin_rpc_agent/routes/sse.rb
|
|
180
179
|
- lib/forest_admin_rpc_agent/routes/update.rb
|
|
181
|
-
- lib/forest_admin_rpc_agent/sse_connection_manager.rb
|
|
182
|
-
- lib/forest_admin_rpc_agent/sse_streamer.rb
|
|
183
180
|
- lib/forest_admin_rpc_agent/thor/install.rb
|
|
184
181
|
- lib/forest_admin_rpc_agent/version.rb
|
|
185
182
|
homepage: https://www.forestadmin.com
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
require 'jsonapi-serializers'
|
|
2
|
-
|
|
3
|
-
module ForestAdminRpcAgent
|
|
4
|
-
module Routes
|
|
5
|
-
class Sse
|
|
6
|
-
DEFAULT_HEARTBEAT_INTERVAL = 10
|
|
7
|
-
|
|
8
|
-
def initialize(url = 'sse', method = 'get', name = 'rpc_sse', heartbeat_interval: DEFAULT_HEARTBEAT_INTERVAL)
|
|
9
|
-
@url = url
|
|
10
|
-
@method = method
|
|
11
|
-
@name = name
|
|
12
|
-
@heartbeat_interval = heartbeat_interval
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def registered(app)
|
|
16
|
-
if defined?(Sinatra) && (app == Sinatra::Base || app.ancestors.include?(Sinatra::Base))
|
|
17
|
-
register_sinatra(app)
|
|
18
|
-
elsif defined?(Rails) && app.is_a?(ActionDispatch::Routing::Mapper)
|
|
19
|
-
register_rails(app)
|
|
20
|
-
else
|
|
21
|
-
raise NotImplementedError,
|
|
22
|
-
"Unsupported application type: #{app.class}. #{self} works with Sinatra::Base or ActionDispatch::Routing::Mapper."
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def register_sinatra(app)
|
|
27
|
-
route_instance = self
|
|
28
|
-
app.send(@method.to_sym, "/#{@url}") do
|
|
29
|
-
auth_middleware = ForestAdminRpcAgent::Middleware::Authentication.new(->(_env) { [200, {}, ['OK']] })
|
|
30
|
-
status, headers, response = auth_middleware.call(env)
|
|
31
|
-
|
|
32
|
-
halt status, headers, response if status != 200
|
|
33
|
-
|
|
34
|
-
content_type 'text/event-stream'
|
|
35
|
-
headers 'Cache-Control' => 'no-cache',
|
|
36
|
-
'Connection' => 'keep-alive',
|
|
37
|
-
'X-Accel-Buffering' => 'no'
|
|
38
|
-
|
|
39
|
-
stream(:keep_open) do |out|
|
|
40
|
-
# Register this connection; any previous connection will be terminated
|
|
41
|
-
connection = ForestAdminRpcAgent::SseConnectionManager.register_connection
|
|
42
|
-
|
|
43
|
-
server_stopped = false
|
|
44
|
-
received_signal = nil
|
|
45
|
-
stop_proc = proc do |sig|
|
|
46
|
-
connection.terminate
|
|
47
|
-
server_stopped = true
|
|
48
|
-
received_signal = sig
|
|
49
|
-
end
|
|
50
|
-
original_int_handler = trap('INT', stop_proc)
|
|
51
|
-
original_term_handler = trap('TERM', stop_proc)
|
|
52
|
-
|
|
53
|
-
begin
|
|
54
|
-
streamer = SseStreamer.new(out)
|
|
55
|
-
|
|
56
|
-
while connection.active?
|
|
57
|
-
streamer.write('', event: 'heartbeat')
|
|
58
|
-
sleep route_instance.instance_variable_get(:@heartbeat_interval)
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Send RpcServerStop only if server is stopping (not client disconnect)
|
|
62
|
-
if server_stopped
|
|
63
|
-
begin
|
|
64
|
-
streamer.write({ event: 'RpcServerStop' }, event: 'RpcServerStop')
|
|
65
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE] RpcServerStop event sent')
|
|
66
|
-
rescue StandardError => e
|
|
67
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', "[SSE] Error sending stop event: #{e.message}")
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
rescue IOError, Errno::EPIPE => e
|
|
71
|
-
# Client disconnected normally
|
|
72
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', "[SSE] Client disconnected: #{e.message}")
|
|
73
|
-
ensure
|
|
74
|
-
trap('INT', original_int_handler)
|
|
75
|
-
trap('TERM', original_term_handler)
|
|
76
|
-
ForestAdminRpcAgent::SseConnectionManager.unregister_connection(connection)
|
|
77
|
-
out.close if out.respond_to?(:close)
|
|
78
|
-
|
|
79
|
-
# Re-send the signal to allow proper server shutdown
|
|
80
|
-
Process.kill(received_signal, Process.pid) if received_signal
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def register_rails(router)
|
|
87
|
-
route_instance = self
|
|
88
|
-
handler = proc do |hash|
|
|
89
|
-
request = ActionDispatch::Request.new(hash)
|
|
90
|
-
auth_middleware = ForestAdminRpcAgent::Middleware::Authentication.new(->(_env) { [200, {}, ['OK']] })
|
|
91
|
-
status, headers, response = auth_middleware.call(request.env)
|
|
92
|
-
|
|
93
|
-
if status == 200
|
|
94
|
-
headers = {
|
|
95
|
-
'Content-Type' => 'text/event-stream',
|
|
96
|
-
'Cache-Control' => 'no-cache',
|
|
97
|
-
'Connection' => 'keep-alive',
|
|
98
|
-
'X-Accel-Buffering' => 'no'
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
# Register this connection; any previous connection will be terminated
|
|
102
|
-
connection = ForestAdminRpcAgent::SseConnectionManager.register_connection
|
|
103
|
-
|
|
104
|
-
server_stopped = false
|
|
105
|
-
received_signal = nil
|
|
106
|
-
stop_proc = proc do |sig|
|
|
107
|
-
connection.terminate
|
|
108
|
-
server_stopped = true
|
|
109
|
-
received_signal = sig
|
|
110
|
-
end
|
|
111
|
-
original_int_handler = trap('INT', stop_proc)
|
|
112
|
-
original_term_handler = trap('TERM', stop_proc)
|
|
113
|
-
|
|
114
|
-
body = Enumerator.new do |yielder|
|
|
115
|
-
stream = SseStreamer.new(yielder)
|
|
116
|
-
|
|
117
|
-
begin
|
|
118
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE] Starting stream')
|
|
119
|
-
|
|
120
|
-
while connection.active?
|
|
121
|
-
stream.write('', event: 'heartbeat')
|
|
122
|
-
sleep route_instance.instance_variable_get(:@heartbeat_interval)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Send RpcServerStop only if server is stopping (not client disconnect)
|
|
126
|
-
if server_stopped
|
|
127
|
-
begin
|
|
128
|
-
stream.write({ event: 'RpcServerStop' }, event: 'RpcServerStop')
|
|
129
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE] RpcServerStop event sent')
|
|
130
|
-
rescue StandardError => e
|
|
131
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', "[SSE] Error sending stop event: #{e.message}")
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
rescue IOError, Errno::EPIPE => e
|
|
135
|
-
# Client disconnected normally
|
|
136
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', "[SSE] Client disconnected: #{e.message}")
|
|
137
|
-
rescue StandardError => e
|
|
138
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Error', "[SSE] Unexpected error: #{e.message}")
|
|
139
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Error', e.backtrace.join("\n"))
|
|
140
|
-
ensure
|
|
141
|
-
trap('INT', original_int_handler)
|
|
142
|
-
trap('TERM', original_term_handler)
|
|
143
|
-
ForestAdminRpcAgent::SseConnectionManager.unregister_connection(connection)
|
|
144
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log('Debug', '[SSE] Stream stopped')
|
|
145
|
-
|
|
146
|
-
# Re-send the signal to allow proper server shutdown
|
|
147
|
-
Process.kill(received_signal, Process.pid) if received_signal
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
[status, headers, body]
|
|
152
|
-
else
|
|
153
|
-
[status, headers, response]
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
router.match @url,
|
|
158
|
-
defaults: { format: 'event-stream' },
|
|
159
|
-
to: handler,
|
|
160
|
-
via: @method,
|
|
161
|
-
as: @name,
|
|
162
|
-
route_alias: @name
|
|
163
|
-
end
|
|
164
|
-
end
|
|
165
|
-
end
|
|
166
|
-
end
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
module ForestAdminRpcAgent
|
|
2
|
-
# Manages SSE connections to ensure only one active connection at a time.
|
|
3
|
-
# When a new connection is established, the previous one is terminated.
|
|
4
|
-
# This prevents zombie loops when the master restarts and reconnects.
|
|
5
|
-
class SseConnectionManager
|
|
6
|
-
@mutex = Mutex.new
|
|
7
|
-
@current_connection = nil
|
|
8
|
-
|
|
9
|
-
class << self
|
|
10
|
-
# Registers a new SSE connection and terminates any existing one.
|
|
11
|
-
# Returns a connection object that can be used to check if the connection is still active.
|
|
12
|
-
def register_connection
|
|
13
|
-
connection = Connection.new
|
|
14
|
-
|
|
15
|
-
@mutex.synchronize do
|
|
16
|
-
# Terminate the previous connection if it exists
|
|
17
|
-
if @current_connection
|
|
18
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log(
|
|
19
|
-
'Debug',
|
|
20
|
-
'[SSE ConnectionManager] Terminating previous connection'
|
|
21
|
-
)
|
|
22
|
-
@current_connection.terminate
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
@current_connection = connection
|
|
26
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log(
|
|
27
|
-
'Debug',
|
|
28
|
-
"[SSE ConnectionManager] New connection registered (id: #{connection.id})"
|
|
29
|
-
)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
connection
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Unregisters a connection when it's closed normally.
|
|
36
|
-
def unregister_connection(connection)
|
|
37
|
-
@mutex.synchronize do
|
|
38
|
-
if @current_connection&.id == connection.id
|
|
39
|
-
@current_connection = nil
|
|
40
|
-
ForestAdminRpcAgent::Facades::Container.logger&.log(
|
|
41
|
-
'Debug',
|
|
42
|
-
"[SSE ConnectionManager] Connection unregistered (id: #{connection.id})"
|
|
43
|
-
)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Returns the current active connection (for testing purposes)
|
|
49
|
-
def current_connection
|
|
50
|
-
@mutex.synchronize { @current_connection }
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Resets the manager state (for testing purposes)
|
|
54
|
-
def reset!
|
|
55
|
-
@mutex.synchronize do
|
|
56
|
-
@current_connection&.terminate
|
|
57
|
-
@current_connection = nil
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Represents an individual SSE connection
|
|
63
|
-
class Connection
|
|
64
|
-
attr_reader :id
|
|
65
|
-
|
|
66
|
-
def initialize
|
|
67
|
-
@id = SecureRandom.uuid
|
|
68
|
-
@active = true
|
|
69
|
-
@mutex = Mutex.new
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def active?
|
|
73
|
-
@mutex.synchronize { @active }
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def terminate
|
|
77
|
-
@mutex.synchronize { @active = false }
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
require 'json'
|
|
2
|
-
|
|
3
|
-
module ForestAdminRpcAgent
|
|
4
|
-
class SseStreamer
|
|
5
|
-
def initialize(yielder)
|
|
6
|
-
@yielder = yielder
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def write(object, event: nil)
|
|
10
|
-
@yielder << "event: #{event}\n" if event
|
|
11
|
-
@yielder << "data: #{JSON.dump(object)}\n\n"
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|