ruflet_server 0.0.20 → 0.0.22
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/lib/ruflet/server/connection_protocol.rb +6 -7
- data/lib/ruflet/server/in_process_connection.rb +91 -0
- data/lib/ruflet/server/web_socket_connection.rb +10 -3
- data/lib/ruflet/server/wire_codec.rb +45 -0
- data/lib/ruflet/server.rb +29 -6
- data/lib/ruflet/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz: '
|
|
3
|
+
metadata.gz: cd0a28621f5068581ced0ccb30b88d298b3e3fbcccb9747d89835cc301f00117
|
|
4
|
+
data.tar.gz: '028d69d0d2186e6cbbe2e30fab7b87a9403ed73d0261e4129f14a4458ebf1018'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: afed8a7dfd432ca20e3303766599a5e960ec44a8037d9345c9fdd8c03d7f70dd74e801809f42e39958c9330e6ac3c83b95bbd252992017bac78c8984ee5e13e1
|
|
7
|
+
data.tar.gz: 73e7bbcd814572b1b395042ca164dd219a2d273b0998f73c2e43f54c901a4acfc372c25895ad312280a370604782b652b6e3d508d67f22d472cf5a421bd094d4
|
|
@@ -96,6 +96,11 @@ module Ruflet
|
|
|
96
96
|
on_update_control(ws, payload)
|
|
97
97
|
when Protocol::ACTIONS[:invoke_control_method]
|
|
98
98
|
on_invoke_control_method(ws, payload)
|
|
99
|
+
when Protocol::ACTIONS[:python_output]
|
|
100
|
+
# Flet clients can emit this legacy diagnostic action. It carries no
|
|
101
|
+
# server-side state, but it is a valid wire message and must not be
|
|
102
|
+
# treated as a protocol error or tear down host-adapter connections.
|
|
103
|
+
nil
|
|
99
104
|
else
|
|
100
105
|
raise "Unknown action: #{action.inspect}"
|
|
101
106
|
end
|
|
@@ -149,7 +154,7 @@ module Ruflet
|
|
|
149
154
|
|
|
150
155
|
if page
|
|
151
156
|
attach_sender(page, ws)
|
|
152
|
-
|
|
157
|
+
page.prepare_for_reconnect!
|
|
153
158
|
else
|
|
154
159
|
page = Page.new(
|
|
155
160
|
session_id: session_id,
|
|
@@ -251,12 +256,6 @@ module Ruflet
|
|
|
251
256
|
page.instance_variable_set(:@sender, sender_for(ws))
|
|
252
257
|
end
|
|
253
258
|
|
|
254
|
-
def reset_mount_state(page)
|
|
255
|
-
page.instance_variable_set(:@overlay_container_mounted, false)
|
|
256
|
-
page.instance_variable_set(:@dialogs_container_mounted, false)
|
|
257
|
-
page.instance_variable_set(:@services_container_mounted, false)
|
|
258
|
-
end
|
|
259
|
-
|
|
260
259
|
def disconnect_error?(error)
|
|
261
260
|
return true if error.is_a?(IOError)
|
|
262
261
|
return true if error.is_a?(Errno::EPIPE)
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ruflet
|
|
4
|
+
# Presents the embedded runtime bridge through the same connection contract
|
|
5
|
+
# used by the WebSocket transport. The server protocol therefore remains
|
|
6
|
+
# transport-agnostic: only the bytes' path changes.
|
|
7
|
+
class InProcessConnection
|
|
8
|
+
MAX_MESSAGE_BYTES = 16 * 1024 * 1024
|
|
9
|
+
TASK_PUMP_INTERVAL = 0.001
|
|
10
|
+
REQUIRED_BRIDGE_METHODS = %i[
|
|
11
|
+
__bridge_read_nonblock __bridge_write __bridge_close
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(bridge: nil)
|
|
15
|
+
@bridge = bridge || default_bridge
|
|
16
|
+
@closed = false
|
|
17
|
+
validate_bridge!
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def session_key
|
|
21
|
+
@session_key ||= object_id
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def closed?
|
|
25
|
+
@closed
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def read_message
|
|
29
|
+
return nil if closed?
|
|
30
|
+
|
|
31
|
+
payload = wait_for_message
|
|
32
|
+
if payload.nil?
|
|
33
|
+
@closed = true
|
|
34
|
+
return nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
checked_bytes(payload)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def send_binary(payload)
|
|
41
|
+
raise IOError, "Ruflet in-process connection is closed" if closed?
|
|
42
|
+
|
|
43
|
+
@bridge.__bridge_write(checked_bytes(payload))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def send_text(payload)
|
|
47
|
+
send_binary(payload)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def close
|
|
51
|
+
return if closed?
|
|
52
|
+
|
|
53
|
+
@closed = true
|
|
54
|
+
@bridge.__bridge_close
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def default_bridge
|
|
60
|
+
Object.const_get(:RubyRuntime)
|
|
61
|
+
rescue NameError
|
|
62
|
+
raise RuntimeError, "Ruflet in-process transport requires the embedded RubyRuntime bridge"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def validate_bridge!
|
|
66
|
+
missing = REQUIRED_BRIDGE_METHODS.reject { |method_name| @bridge.respond_to?(method_name) }
|
|
67
|
+
return if missing.empty?
|
|
68
|
+
|
|
69
|
+
raise RuntimeError, "Ruflet in-process bridge is missing: #{missing.join(', ')}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def wait_for_message
|
|
73
|
+
loop do
|
|
74
|
+
payload = @bridge.__bridge_read_nonblock
|
|
75
|
+
return payload unless payload == false
|
|
76
|
+
|
|
77
|
+
Thread.pass if Thread.respond_to?(:cooperative?) && Thread.cooperative?
|
|
78
|
+
sleep TASK_PUMP_INTERVAL
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def checked_bytes(payload)
|
|
83
|
+
bytes = payload.to_s.b
|
|
84
|
+
if bytes.bytesize > MAX_MESSAGE_BYTES
|
|
85
|
+
raise ArgumentError, "Ruflet in-process message exceeds #{MAX_MESSAGE_BYTES} bytes"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
bytes
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -22,7 +22,9 @@ module Ruflet
|
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def send_binary(payload)
|
|
25
|
-
|
|
25
|
+
bytes = payload.to_s.b
|
|
26
|
+
Ruflet::WireCodec.trace("ruby->client conn=#{session_key}", bytes)
|
|
27
|
+
send_frame(0x2, bytes)
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def send_text(payload)
|
|
@@ -46,7 +48,7 @@ module Ruflet
|
|
|
46
48
|
when 0xA
|
|
47
49
|
read_message
|
|
48
50
|
when 0x1, 0x2
|
|
49
|
-
return payload if frame[:fin]
|
|
51
|
+
return traced_incoming(payload) if frame[:fin]
|
|
50
52
|
|
|
51
53
|
message = payload.dup
|
|
52
54
|
loop do
|
|
@@ -61,7 +63,7 @@ module Ruflet
|
|
|
61
63
|
next
|
|
62
64
|
when 0x0
|
|
63
65
|
message << continuation[:payload]
|
|
64
|
-
return message if continuation[:fin]
|
|
66
|
+
return traced_incoming(message) if continuation[:fin]
|
|
65
67
|
return nil if message.bytesize > MAX_FRAME_PAYLOAD_BYTES
|
|
66
68
|
else
|
|
67
69
|
return nil
|
|
@@ -84,6 +86,11 @@ module Ruflet
|
|
|
84
86
|
|
|
85
87
|
private
|
|
86
88
|
|
|
89
|
+
def traced_incoming(payload)
|
|
90
|
+
Ruflet::WireCodec.trace("client->ruby conn=#{session_key}", payload)
|
|
91
|
+
payload
|
|
92
|
+
end
|
|
93
|
+
|
|
87
94
|
def read_frame
|
|
88
95
|
header = read_exact(2)
|
|
89
96
|
return nil if header.nil?
|
|
@@ -3,6 +3,30 @@
|
|
|
3
3
|
module Ruflet
|
|
4
4
|
class WireCodec
|
|
5
5
|
class << self
|
|
6
|
+
# Transport tracing is opt-in. Even compact summaries decode every frame
|
|
7
|
+
# and write to stdout, so enabling them by default would distort the
|
|
8
|
+
# renderer performance they are intended to diagnose.
|
|
9
|
+
#
|
|
10
|
+
# RUFLET_PROTOCOL_TRACE=summary compact frame metadata
|
|
11
|
+
# RUFLET_PROTOCOL_TRACE=1 exact bytes and decoded value
|
|
12
|
+
# RUFLET_PROTOCOL_TRACE=0 disabled (default)
|
|
13
|
+
def trace(direction, bytes)
|
|
14
|
+
mode = ENV.fetch("RUFLET_PROTOCOL_TRACE", "0")
|
|
15
|
+
return if mode == "0"
|
|
16
|
+
|
|
17
|
+
raw = bytes.to_s.b
|
|
18
|
+
decoded = unpack(raw)
|
|
19
|
+
action = decoded.is_a?(Array) ? decoded[0] : nil
|
|
20
|
+
prefix = "[RUFLET_PROTOCOL] t=#{format("%.6f", Process.clock_gettime(Process::CLOCK_MONOTONIC))} #{direction} bytes=#{raw.bytesize} action=#{action.inspect}"
|
|
21
|
+
if mode == "1" || mode == "full"
|
|
22
|
+
puts "#{prefix} hex=#{raw.unpack("H*").first} data=#{decoded.inspect}"
|
|
23
|
+
else
|
|
24
|
+
puts "#{prefix} #{trace_summary(action, decoded.is_a?(Array) ? decoded[1] : nil)}".rstrip
|
|
25
|
+
end
|
|
26
|
+
rescue StandardError => error
|
|
27
|
+
puts "[RUFLET_PROTOCOL] t=#{format("%.6f", Process.clock_gettime(Process::CLOCK_MONOTONIC))} #{direction} bytes=#{raw&.bytesize || 0} decode_error=#{error.class}: #{error.message}"
|
|
28
|
+
end
|
|
29
|
+
|
|
6
30
|
def pack(value)
|
|
7
31
|
case value
|
|
8
32
|
when Ruflet::Protocol::DateTimeValue
|
|
@@ -41,6 +65,27 @@ module Ruflet
|
|
|
41
65
|
|
|
42
66
|
private
|
|
43
67
|
|
|
68
|
+
def trace_summary(action, payload)
|
|
69
|
+
return "payload=#{payload.class}" unless payload.is_a?(Hash)
|
|
70
|
+
|
|
71
|
+
case action
|
|
72
|
+
when 1
|
|
73
|
+
"session_id=#{payload["session_id"].inspect} page_name=#{payload["page_name"].inspect}"
|
|
74
|
+
when 2
|
|
75
|
+
patch = payload["patch"]
|
|
76
|
+
"target=#{payload["id"].inspect} patch_items=#{patch.is_a?(Array) ? patch.length : 0}"
|
|
77
|
+
when 3
|
|
78
|
+
"target=#{payload["target"].inspect} name=#{payload["name"].inspect} data_type=#{payload["data"].class}"
|
|
79
|
+
when 4
|
|
80
|
+
properties = payload["props"]
|
|
81
|
+
"target=#{payload["id"].inspect} properties=#{properties.is_a?(Hash) ? properties.keys.sort.join(",") : ""}"
|
|
82
|
+
when 5
|
|
83
|
+
"target=#{payload["control_id"].inspect} name=#{payload["name"].inspect}"
|
|
84
|
+
else
|
|
85
|
+
"keys=#{payload.keys.map(&:to_s).sort.join(",")}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
44
89
|
def pack_extension(type, value)
|
|
45
90
|
bytes = value.to_s.b
|
|
46
91
|
length = bytes.bytesize
|
data/lib/ruflet/server.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "socket"
|
|
|
5
5
|
require "thread"
|
|
6
6
|
|
|
7
7
|
require "ruflet_core"
|
|
8
|
+
require_relative "server/in_process_connection"
|
|
8
9
|
require_relative "server/wire_codec"
|
|
9
10
|
require_relative "server/web_socket_connection"
|
|
10
11
|
|
|
@@ -15,9 +16,10 @@ module Ruflet
|
|
|
15
16
|
WEBSOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
16
17
|
TASK_IO_POLL_INTERVAL = 0.01
|
|
17
18
|
|
|
18
|
-
def initialize(host: "0.0.0.0", port: 8550, &app_block)
|
|
19
|
+
def initialize(host: "0.0.0.0", port: 8550, in_process_bridge: nil, &app_block)
|
|
19
20
|
@host = host
|
|
20
21
|
@port = port
|
|
22
|
+
@in_process_bridge = in_process_bridge
|
|
21
23
|
@app_block = app_block
|
|
22
24
|
@sessions = {}
|
|
23
25
|
@sessions_mutex = Mutex.new
|
|
@@ -44,10 +46,14 @@ module Ruflet
|
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
previous_signals = trap_stop_signals
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
if in_process_transport_requested?
|
|
50
|
+
start_in_process_transport
|
|
51
|
+
else
|
|
52
|
+
bind_server_socket!
|
|
53
|
+
@running = true
|
|
54
|
+
print_server_banner
|
|
55
|
+
accept_loop
|
|
56
|
+
end
|
|
51
57
|
rescue Interrupt
|
|
52
58
|
nil
|
|
53
59
|
ensure
|
|
@@ -175,6 +181,16 @@ module Ruflet
|
|
|
175
181
|
|
|
176
182
|
private
|
|
177
183
|
|
|
184
|
+
def in_process_transport_requested?
|
|
185
|
+
ENV["RUFLET_RUNTIME_TRANSPORT"] == "in_process"
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def start_in_process_transport
|
|
189
|
+
connection = Ruflet::InProcessConnection.new(bridge: @in_process_bridge)
|
|
190
|
+
@running = true
|
|
191
|
+
run_connection(connection)
|
|
192
|
+
end
|
|
193
|
+
|
|
178
194
|
def trap_stop_signals
|
|
179
195
|
{
|
|
180
196
|
"INT" => trap_signal("INT"),
|
|
@@ -380,7 +396,12 @@ module Ruflet
|
|
|
380
396
|
# opens its websocket on one origin. Without that the client cannot reach
|
|
381
397
|
# /ws at all.
|
|
382
398
|
def web_client_root
|
|
383
|
-
|
|
399
|
+
# Memoized with a separate flag rather than defined?(@web_client_root):
|
|
400
|
+
# the embedded mruby VM has no defined? keyword, so it parses as a method
|
|
401
|
+
# call and every request raises NoMethodError. The resolved value is
|
|
402
|
+
# legitimately nil when no web client is configured, so nil cannot double
|
|
403
|
+
# as "not resolved yet".
|
|
404
|
+
return @web_client_root if @web_client_root_resolved
|
|
384
405
|
|
|
385
406
|
configured = ENV["RUFLET_WEB_CLIENT_DIR"].to_s.strip
|
|
386
407
|
@web_client_root =
|
|
@@ -389,6 +410,8 @@ module Ruflet
|
|
|
389
410
|
else
|
|
390
411
|
File.expand_path(configured)
|
|
391
412
|
end
|
|
413
|
+
@web_client_root_resolved = true
|
|
414
|
+
@web_client_root
|
|
392
415
|
end
|
|
393
416
|
|
|
394
417
|
def serve_web_client(socket, request_path)
|
data/lib/ruflet/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruflet_server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.22
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- AdamMusa
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.0.
|
|
18
|
+
version: 0.0.22
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.0.
|
|
25
|
+
version: 0.0.22
|
|
26
26
|
description: Ruflet WebSocket server runtime compatible with Flet protocol.
|
|
27
27
|
email:
|
|
28
28
|
- adammusa2222@gmail.com
|
|
@@ -33,6 +33,7 @@ files:
|
|
|
33
33
|
- README.md
|
|
34
34
|
- lib/ruflet/server.rb
|
|
35
35
|
- lib/ruflet/server/connection_protocol.rb
|
|
36
|
+
- lib/ruflet/server/in_process_connection.rb
|
|
36
37
|
- lib/ruflet/server/web_socket_connection.rb
|
|
37
38
|
- lib/ruflet/server/wire_codec.rb
|
|
38
39
|
- lib/ruflet/version.rb
|