Class: Terminalwire::V2::Server::Rack
- Inherits:
-
Object
- Object
- Terminalwire::V2::Server::Rack
- Defined in:
- lib/terminalwire/v2/server/rack.rb
Overview
A Rack endpoint that serves a Terminalwire CLI over a WebSocket. Mounting it is the entire integration:
# config/routes.rb
mount Terminalwire::V2::Server::Rack.new(MyCLI), at: "/terminal"
It runs on threaded servers (Puma, and friends) and on async servers (Falcon) alike. The server runtime is thread-based, so each connection runs its CLI on its own thread; this class owns that thread, the WebSocket framing, and the teardown — the host app never sees any of it.
Two server worlds, picked per request by whether we're inside an async reactor (Async::Task.current?):
* Threaded (Puma): a raw RFC 6455 upgrade whose streaming body does the
framing on plain blocking socket I/O in threads — exactly what the
thread-based runtime and Puma's socket want (no async reactor fighting
Puma's write-timeout watchdog).
* Async (Falcon): async-websocket drives the connection in reactor fibers,
bridged to the runtime's threads via a queue + a wake pipe.
Opt-in require: require "terminalwire/v2/server/rack". The async stack is pulled in lazily and only on the Falcon path, so Puma deployments (and the frame parser in unit tests) never load async/async-websocket at all.
Defined Under Namespace
Modules: Frame Classes: Parser, ReactorBridge, ThreadBridge
Constant Summary collapse
- WS_GUID =
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"- SUBPROTOCOLS =
WebSocket subprotocols this server speaks, best first. The handshake echoes the first one the client also offered (RFC 6455 negotiation) — the v1 handler did this (
protocols: ['ws']) and some edges/proxies (e.g. Fly) drop a WebSocket whose Sec-WebSocket-Protocol the server never echoes back. %w[terminalwire.v2 ws].freeze
Instance Method Summary collapse
- #call(env) ⇒ Object
-
#initialize(cli_class, verbose: false, report: nil) ⇒ Rack
constructor
A new instance of Rack.
Constructor Details
Instance Method Details
#call(env) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/terminalwire/v2/server/rack.rb', line 53 def call(env) return upgrade_required unless websocket?(env) # The incoming connection profile, captured at the upgrade: host (so URL # helpers can build absolute URLs, as v1 did), real client IP, User-Agent, # and the raw headers. Threaded into the session for server code + `about`. request = request_info(env) if async_reactor? # Async server (Falcon): let async-websocket own the connection. Pull the # adapter in here — this is the only path that needs the async stack. # :nocov: Falcon transport wiring — exercised live by the conformance suite, not units. require "async/websocket/adapters/rack" Async::WebSocket::Adapters::Rack.open(env, protocols: SUBPROTOCOLS) { |connection| ReactorBridge.new(connection, @handler, request: request).run } # :nocov: else # Threaded server (Puma & friends): hand-roll the upgrade and stream. [101, upgrade_headers(env), ThreadBridge.new(@handler, request: request)] end end |