Class: Dommy::Rack::WebSocketTransport
- Inherits:
-
Object
- Object
- Dommy::Rack::WebSocketTransport
- Defined in:
- lib/dommy/rack/web_socket_transport.rb
Overview
In-process WebSocket transport: connects a page's new WebSocket(url)
to the Rack app ITSELF, the same way fetch/XHR resolve through the app.
The server side receives a real RFC 6455 upgrade request and a real
socket (one end of a socketpair, handed over via rack.hijack), so
ActionCable's full stack runs unmodified — connection auth sees the
session's cookies, the origin check sees the session's origin, and the
cable event loop reads/writes actual WebSocket frames.
The client side of the protocol lives here: frame semantics over the
WebSocketFrame codec (text/close/ping handling; ActionCable messages are
single-frame text) plus a reader thread that parses server frames and
marshals them onto the page thread via scheduler.post_external, where
they fire the WebSocket's open/message/close events. settle /
advance_time deliver them, like any other external completion.
Lifetime: a transport belongs to the page (realm) that opened it; the session closes all live transports on dispose. Note that ActionCable sends its JSON pings on REAL time (an every-3s event-loop timer), not the page's virtual clock — harmless for tests, which settle on the subscription/broadcast messages they wait for.
Defined Under Namespace
Classes: HandshakeFailed
Class Method Summary collapse
-
.rack_target(url, base:) ⇒ Object
Resolve
urlfor the connector: an absolute ws(s) URL (http(s) is accepted and treated the same) that is same-origin withbase.
Instance Method Summary collapse
- #close(code = 1000, reason = "") ⇒ Object
-
#dispose ⇒ Object
Hard teardown (session dispose): drop the socket; the reader thread exits on EOF.
-
#initialize(app:, ws:, scheduler:, url:, origin:, cookie_string: "") ⇒ WebSocketTransport
constructor
A new instance of WebSocketTransport.
-
#send_text(data) ⇒ Object
--- API the WebSocket delegates to (page thread) ---.
Constructor Details
#initialize(app:, ws:, scheduler:, url:, origin:, cookie_string: "") ⇒ WebSocketTransport
Returns a new instance of WebSocketTransport.
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/dommy/rack/web_socket_transport.rb', line 51 def initialize(app:, ws:, scheduler:, url:, origin:, cookie_string: "") @ws = ws @scheduler = scheduler @url = url @write_mutex = Mutex.new @sent_close = false @closed = false @client_io, server_io = ::Socket.pair(:UNIX, :STREAM) env = handshake_env(url, origin, , server_io) status, _headers, _body = app.call(env) if env["rack.hijack_io"].nil? && status != -1 # The app answered with a normal HTTP response (no cable mounted at # this path, or the upgrade was rejected): fail like a browser — # error then close, deferred so `onerror` handlers attach first. fail_connection else @reader = Thread.new { run_reader } end end |
Class Method Details
.rack_target(url, base:) ⇒ Object
Resolve url for the connector: an absolute ws(s) URL (http(s) is
accepted and treated the same) that is same-origin with base.
Returns the URI, or nil (the WebSocket then falls back to the
in-memory stub).
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/dommy/rack/web_socket_transport.rb', line 35 def self.rack_target(url, base:) target = URI.join(base.to_s, url.to_s) scheme = {"ws" => "http", "wss" => "https"}[target.scheme] || target.scheme return nil unless %w[http https].include?(scheme) b = URI.parse(base.to_s) return nil unless b.host == target.host && b.port == target.port target.scheme = scheme # Re-parse so the return value is a URI::HTTP(S), not a URI::WS whose # scheme string was swapped (URI classes compare by class + value). URI.parse(target.to_s) rescue URI::Error nil end |
Instance Method Details
#close(code = 1000, reason = "") ⇒ Object
78 79 80 |
# File 'lib/dommy/rack/web_socket_transport.rb', line 78 def close(code = 1000, reason = "") send_close_frame(code == 1005 ? 1000 : code, reason) end |
#dispose ⇒ Object
Hard teardown (session dispose): drop the socket; the reader thread exits on EOF. Safe to call repeatedly.
84 85 86 87 88 89 90 |
# File 'lib/dommy/rack/web_socket_transport.rb', line 84 def dispose @closed = true @client_io&.close unless @client_io&.closed? @reader&.join(1) rescue IOError nil end |
#send_text(data) ⇒ Object
--- API the WebSocket delegates to (page thread) ---
74 75 76 |
# File 'lib/dommy/rack/web_socket_transport.rb', line 74 def send_text(data) write_frame(WebSocketFrame::TEXT, data.b) end |