morph 0.1.0
Asynchronous bridge between GUI thread and business models
Loading...
Searching...
No Matches
morph::net::SocketBackend Class Reference

IBackend implementation that communicates with a RemoteServer over a raw-socket RFC 6455 WebSocket connection — no Qt, no GUI event loop required. More...

#include <socket_backend.hpp>

Inheritance diagram for morph::net::SocketBackend:
morph::backend::IEnvelopeTransport

Public Types

using Config = SocketBackendConfig
 Alias for the reconnect configuration struct.

Public Member Functions

 SocketBackend (::morph::exec::IoLoop &loop, std::string_view serverUrl, Config cfg={})
 Parses serverUrl and starts connecting on loop.
 SocketBackend (std::string_view serverUrl, Config cfg={})
 Parses serverUrl and starts connecting on a loop of its own, for a caller with no IoLoop to share.
 SocketBackend (const SocketBackend &)=delete
SocketBackend & operator= (const SocketBackend &)=delete
 SocketBackend (SocketBackend &&)=delete
SocketBackend & operator= (SocketBackend &&)=delete
 ~SocketBackend () override
 Closes the connection and rejects every pending call with DisconnectedError.
bool waitForConnected (std::chrono::milliseconds timeout=std::chrono::milliseconds{5000})
 Blocks the calling thread until connected or timeout elapses.
::morph::exec::detail::ModelId registerModel (const std::string &typeId, std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory) override
 Registers a private instance and waits for the reply.
::morph::exec::detail::ModelId registerModelWithContext (const std::string &typeId, std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory, std::string_view contextKey) override
 Registers a private instance carrying contextKey and waits for the reply.
void assignPrimary (::morph::exec::detail::ModelId mid, const std::string &typeId, std::string_view primary) override
 Files a live server-side instance under primary and waits for the reply.
::morph::async::Completion<::morph::exec::detail::ModelId > bindModel (::morph::backend::detail::BindRequest request, ::morph::exec::IExecutor &cbExec) override
 Acquires a model instance without blocking the calling thread.
::morph::async::Completion<::morph::exec::detail::ModelId > promoteModel (::morph::backend::detail::PromoteRequest request, ::morph::exec::IExecutor &cbExec) override
 Files an already-live server-side instance under a key, without blocking the calling thread.
std::vector< std::string > listInstances (const std::string &typeId) override
 Asks the server for the live shared primary keys of typeId and waits for the reply, never on the loop's own thread.
::morph::async::Completion<::morph::wire::Envelope > request (::morph::wire::Envelope envelope, ::morph::exec::IExecutor &cbExec) override
 Sends one request envelope of a kind the other verbs do not build (IEnvelopeTransport::request), on the callId-multiplexed control path, and settles with its reply.
::morph::async::Completion<::morph::wire::ProtocolNegotiationResult > negotiateProtocolVersion (::morph::exec::IExecutor &replyExec)
 Sends a "hello" envelope and classifies the server's reply.
void deregisterModel (::morph::exec::detail::ModelId mid) override
 Sends a deregister message fire-and-forget (does not wait for a reply).
::morph::async::Completion< std::shared_ptr< void > > execute (::morph::exec::detail::ModelId mid, ::morph::backend::detail::ActionCall call, ::morph::exec::IExecutor *cbExec) override
 Sends an execute message and returns a Completion resolved on reply.
void notifyBackendChanged () override
 No-op — this backend holds no local model objects.
void cancelPending (const std::exception_ptr &exc) override
 Resolves every pending call's Completion with exc.
void setReconnectHandler (std::function< void()> handler, ::morph::exec::IExecutor *exec) override
 Installs the handler posted to exec after each subsequent successful (re)connect.
void setConnectHandler (const std::function< void()> &handler) override
 Installs the handler run after every successful connect, the first included.
void setEventHandler (std::function< void(::morph::wire::EventBody)> handler, ::morph::exec::IExecutor *exec) override
 Installs the handler each server-initiated event is posted to. See IBackend::setEventHandler.
void setSession (::morph::session::Context session) override
 Installs the session stamped onto every control envelope this backend subsequently builds (register, registerShared, attach, assign, deregister, instances). See IBackend::setSession.
Public Member Functions inherited from morph::backend::IEnvelopeTransport
 IEnvelopeTransport (IEnvelopeTransport const &)=delete
IEnvelopeTransport & operator= (IEnvelopeTransport const &)=delete
 IEnvelopeTransport (IEnvelopeTransport &&)=delete
IEnvelopeTransport & operator= (IEnvelopeTransport &&)=delete

Detailed Description

IBackend implementation that communicates with a RemoteServer over a raw-socket RFC 6455 WebSocket connection — no Qt, no GUI event loop required.

Mirrors morph::qt::QtWebSocketBackend's observable behavior (deregisterModel fire-and-forget, execute and bindModel asynchronous and callId-multiplexed, DisconnectedError/reconnect semantics). Its socket, its pending calls, its session, its handlers and its reconnect state machine live on an exec::IoLoop, which it shares with every other socket, timer and probe the application builds on that loop.

Cross-thread surface: execute, bindModel, promoteModel, request, deregisterModel, cancelPending, setSession, setReconnectHandler, setConnectHandler and setEventHandler post to the loop and return, so calls from one thread take effect in the order they were made; waitForConnected posts a waiter and blocks the caller, never the loop; the synchronous control verbs (registerModel, registerModelWithContext, assignPrimary, listInstances) issue the same posted request and wait for its reply, never on the loop's own thread. Every field is touched only on the loop, except the connected flag.

TLS
Not supported. serverUrl must be ws://; wss:// throws from the constructor. See docs/spec/core/backend.md's morph::net section.

Definition at line 96 of file socket_backend.hpp.

Member Typedef Documentation

◆ Config

Alias for the reconnect configuration struct.

Definition at line 99 of file socket_backend.hpp.

Constructor & Destructor Documentation

◆ SocketBackend() [1/2]

morph::net::SocketBackend::SocketBackend ( ::morph::exec::IoLoop & loop,
std::string_view serverUrl,
Config cfg = {} )
inline

Parses serverUrl and starts connecting on loop.

Parameters
loopThe application's I/O loop. Borrowed: it must outlive this backend.
serverUrlws://host:port[/path] URL of the remote RemoteServer.
cfgReconnect tuning. Default: enabled, 500ms initial / 30s cap, 2x backoff.
Exceptions
std::runtime_errorimmediately if serverUrl is not a well-formed ws:// URL (see parseWsUrl).

Definition at line 108 of file socket_backend.hpp.

◆ SocketBackend() [2/2]

morph::net::SocketBackend::SocketBackend ( std::string_view serverUrl,
Config cfg = {} )
inlineexplicit

Parses serverUrl and starts connecting on a loop of its own, for a caller with no IoLoop to share.

Parameters
serverUrlws://host:port[/path] URL of the remote RemoteServer.
cfgReconnect tuning. Default: enabled, 500ms initial / 30s cap, 2x backoff.
Exceptions
std::runtime_errorimmediately, before any loop is started, if serverUrl is not a well-formed ws:// URL (see parseWsUrl).

Definition at line 121 of file socket_backend.hpp.

◆ ~SocketBackend()

morph::net::SocketBackend::~SocketBackend ( )
inlineoverride

Closes the connection and rejects every pending call with DisconnectedError.

The close runs on the loop: inline when this runs on the loop's own thread (from a callback), posted and waited for otherwise. It never waits for a connect in progress: a dial that completes afterwards finds the backend closed and drops its socket.

Definition at line 142 of file socket_backend.hpp.

Member Function Documentation

◆ waitForConnected()

bool morph::net::SocketBackend::waitForConnected ( std::chrono::milliseconds timeout = std::chrono::milliseconds{5000})
inline

Blocks the calling thread until connected or timeout elapses.

Posts a waiter to the loop, which releases it when a connection completes. On the loop's own thread it cannot wait for the loop, so it answers at once.

Warning
The backend must outlive this call. See docs/spec/core/backend.md's "Lifetime & ownership" section.
Parameters
timeoutMaximum time to wait.
Returns
true if connected before the timeout, false otherwise.

Definition at line 156 of file socket_backend.hpp.

◆ registerModel()

::morph::exec::detail::ModelId morph::net::SocketBackend::registerModel ( const std::string & typeId,
std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory )
inlineoverride

Registers a private instance and waits for the reply.

The same request bindModel posts, waited for on the calling thread — which must not be the I/O loop's, whose reply this would wait on. The factory argument is ignored — model construction is delegated to the server.

Parameters
typeIdString type-id of the model to register.
factoryIgnored — the server constructs via its own registry.
Returns
ModelId assigned by the server.
Exceptions
std::runtime_errorif the server replies with an error, the socket is not connected, or this is the loop's own thread.

Definition at line 178 of file socket_backend.hpp.

◆ registerModelWithContext()

::morph::exec::detail::ModelId morph::net::SocketBackend::registerModelWithContext ( const std::string & typeId,
std::function< std::unique_ptr<::morph::model::detail::IModelHolder >()> factory,
std::string_view contextKey )
inlineoverride

Registers a private instance carrying contextKey and waits for the reply.

The server constructs the holder itself, so contextKey is the only channel by which the instance's identity reaches it: RemoteServer::attachLogIfConfigured consults its LogProvider only for a non-empty one. Same waiting rule as registerModel.

Parameters
typeIdString type-id of the model to register.
factoryIgnored — the server constructs via its own registry.
contextKeyStable identity of the new instance; empty if none.
Returns
ModelId assigned by the server.
Exceptions
std::runtime_errorif the server replies with an error, the socket is not connected, or this is the loop's own thread.

Definition at line 196 of file socket_backend.hpp.

◆ assignPrimary()

void morph::net::SocketBackend::assignPrimary ( ::morph::exec::detail::ModelId mid,
const std::string & typeId,
std::string_view primary )
inlineoverride

Files a live server-side instance under primary and waits for the reply.

Parameters
midLive instance to promote.
typeIdModel type id.
primaryCanonical string encoding of the key to file it under.

Definition at line 213 of file socket_backend.hpp.

◆ bindModel()

::morph::async::Completion<::morph::exec::detail::ModelId > morph::net::SocketBackend::bindModel ( ::morph::backend::detail::BindRequest request,
::morph::exec::IExecutor & cbExec )
inlineoverride

Acquires a model instance without blocking the calling thread.

Sends the control envelope request's shape names (see backend::detail::BindRequest's table) carrying a non-zero callId drawn from the same counter execute uses, and settles the returned Completion on the I/O loop when the matching reply arrives. No thread is parked anywhere.

An empty primary with a live current gives that instance up first, and an empty primary binds a private instance.

A private bind made while the socket is not connected is queued, not failed, and sent in FIFO order the next time it connects, the first connect included: a single-threaded client binds before the socket is up and cannot block waiting for it. cancelPending and the backend's destruction reject what is still queued. A keyed bind is not queued: it may re-point a live instance, and replayed on a later connection it would attach from a current that connection never issued.

Parameters
requestOwning bind request; moved from.
cbExecExecutor the continuation is delivered on. Borrowed: it must outlive the returned Completion.
Returns
A Completion resolved with the bound ModelId; rejected with backend::DisconnectedError if a keyed bind finds the socket down, if the socket drops before the reply, or if a queued bind is cancelled; or with a std::runtime_error carrying the server's own error message.

Definition at line 261 of file socket_backend.hpp.

◆ promoteModel()

::morph::async::Completion<::morph::exec::detail::ModelId > morph::net::SocketBackend::promoteModel ( ::morph::backend::detail::PromoteRequest request,
::morph::exec::IExecutor & cbExec )
inlineoverride

Files an already-live server-side instance under a key, without blocking the calling thread.

The promote counterpart of bindModel, on the same callId-multiplexed path. Resolves with request.mid echoed back exactly as IBackend::promoteModel documents — including for the two guards assignPrimary applies locally (empty primary, zero mid), which resolve without sending anything.

Parameters
requestOwning promote request; moved from.
cbExecExecutor the continuation is delivered on. Borrowed: it must outlive the returned Completion.
Returns
A Completion resolved with request.mid, or rejected as bindModel documents.

Definition at line 295 of file socket_backend.hpp.

◆ listInstances()

std::vector< std::string > morph::net::SocketBackend::listInstances ( const std::string & typeId)
inlineoverride

Asks the server for the live shared primary keys of typeId and waits for the reply, never on the loop's own thread.

Parameters
typeIdString type-id to enumerate.
Returns
Canonical key strings of the live shared instances.
Exceptions
std::runtime_errorif the server replies with an error, the socket is down, or this is the loop's own thread.

Definition at line 313 of file socket_backend.hpp.

◆ request()

::morph::async::Completion<::morph::wire::Envelope > morph::net::SocketBackend::request ( ::morph::wire::Envelope envelope,
::morph::exec::IExecutor & cbExec )
inlineoverridevirtual

Sends one request envelope of a kind the other verbs do not build (IEnvelopeTransport::request), on the callId-multiplexed control path, and settles with its reply.

Posted to the loop, which assigns the callId and sends the envelope with the session it carries. Made while the socket is not connected, it is held and sent at the next connect, the first included, as a private bind is, so a client can ask before the socket is up. cancelPending, a drop and the backend's destruction reject what is pending or held.

Parameters
envelopeThe request; its callId is replaced.
cbExecExecutor the answer is delivered on. Borrowed: it must outlive the returned Completion.
Returns
A Completion resolved with the reply envelope, ok or err; rejected with backend::DisconnectedError when the connection drops before the reply or the request is cancelled.

Implements morph::backend::IEnvelopeTransport.

Definition at line 342 of file socket_backend.hpp.

◆ negotiateProtocolVersion()

::morph::async::Completion<::morph::wire::ProtocolNegotiationResult > morph::net::SocketBackend::negotiateProtocolVersion ( ::morph::exec::IExecutor & replyExec)
inlinenodiscard

Sends a "hello" envelope and classifies the server's reply.

Opt-in, as on the other backends: a backend that never calls it sends no "hello". Calling it also learns whether the server honours "cancel" (wire::kCapabilityCancel), which is the only way this backend ever sends one; and it re-sends "hello" on every reconnect, since a new connection may reach a different server.

Parameters
replyExecExecutor the answer is delivered on. Borrowed: it must outlive the returned Completion.
Returns
A Completion resolved with Negotiated or LegacyPeer; rejected with a std::runtime_error if the server refuses the version, and with backend::DisconnectedError if the socket is down or drops before the reply.

Definition at line 372 of file socket_backend.hpp.

◆ deregisterModel()

void morph::net::SocketBackend::deregisterModel ( ::morph::exec::detail::ModelId mid)
inlineoverride

Sends a deregister message fire-and-forget (does not wait for a reply).

Posted to the loop, which gives it a real, non-zero callId drawn from the same counter execute() uses and stamps the session.

The reply router drops any callId that is not pending, and a deregister files none, so its reply is dropped.

Parameters
midId of the model to remove on the server.

Definition at line 394 of file socket_backend.hpp.

◆ execute()

::morph::async::Completion< std::shared_ptr< void > > morph::net::SocketBackend::execute ( ::morph::exec::detail::ModelId mid,
::morph::backend::detail::ActionCall call,
::morph::exec::IExecutor * cbExec )
inlineoverride

Sends an execute message and returns a Completion resolved on reply.

The action is serialised here, on the calling thread; the loop then assigns the call id, files the pending record and writes the frame.

Parameters
midTarget model id on the server.
callBundled action; serializeAction and deserializeResult are used.
cbExecExecutor for delivering the completion callbacks.
Returns
Completion resolved asynchronously when the server's reply arrives, or with DisconnectedError if the backend is not connected.

Definition at line 412 of file socket_backend.hpp.

◆ notifyBackendChanged()

void morph::net::SocketBackend::notifyBackendChanged ( )
inlineoverride

No-op — this backend holds no local model objects.

Definition at line 447 of file socket_backend.hpp.

◆ cancelPending()

void morph::net::SocketBackend::cancelPending ( const std::exception_ptr & exc)
inlineoverride

Resolves every pending call's Completion with exc.

Posted to the loop, so it covers every call issued before it — the loop runs posts in order. Covers both in-flight tables: the execute calls and the bindModel/promoteModel control calls. A bind left out of this sweep would hang forever on a disconnect, since its reply can now only arrive on a connection that is gone. When the server advertised "cancel", each abandoned execute is also cancelled on the server, from the loop, behind whatever the loop had queued before.

Parameters
excException delivered to every pending completion's error sink.

Definition at line 459 of file socket_backend.hpp.

◆ setReconnectHandler()

void morph::net::SocketBackend::setReconnectHandler ( std::function< void()> handler,
::morph::exec::IExecutor * exec )
inlineoverride

Installs the handler posted to exec after each subsequent successful (re)connect.

Stored on the loop. After a reconnect the loop posts the handler to exec and goes on: whatever the handler does — a Bridge re-registering its bindings through bindModel — never runs on the loop whose replies it needs.

Parameters
handlerCallable posted to exec. Pass nullptr to clear.
execExecutor the handler runs on. Borrowed: it must outlive the installation.

Definition at line 477 of file socket_backend.hpp.

◆ setConnectHandler()

void morph::net::SocketBackend::setConnectHandler ( const std::function< void()> & handler)
inlineoverride

Installs the handler run after every successful connect, the first included.

Stored on the loop, and run there: after the requests held while the socket was down have been sent, and before the reconnect handler is posted. A handler that throws is logged; the connection carries on.

Parameters
handlerCallable run on the backend's I/O loop. Pass nullptr to clear.

Definition at line 493 of file socket_backend.hpp.

◆ setEventHandler()

void morph::net::SocketBackend::setEventHandler ( std::function< void(::morph::wire::EventBody)> handler,
::morph::exec::IExecutor * exec )
inlineoverride

Installs the handler each server-initiated event is posted to. See IBackend::setEventHandler.

Stored on the loop. An event envelope is recognised by its kind before any reply matching, so whatever its callId, it never settles a call. Events are posted to exec in the order they arrived.

Parameters
handlerCallable posted to exec once per event. Pass nullptr to clear.
execExecutor the handler is posted to. Borrowed: it must outlive the installation. May be null only when handler is.

Definition at line 511 of file socket_backend.hpp.

◆ setSession()

void morph::net::SocketBackend::setSession ( ::morph::session::Context session)
inlineoverride

Installs the session stamped onto every control envelope this backend subsequently builds (register, registerShared, attach, assign, deregister, instances). See IBackend::setSession.

Stored on the loop, which stamps every control envelope as it files it: a verb called after this, from the same thread, carries it.

Parameters
sessionSession to stamp; typically pushed by Bridge::setDefaultSession().

Definition at line 530 of file socket_backend.hpp.


The documentation for this class was generated from the following file: