http_websocket_service
This library provides higher-level callback-driven WebSocket session
loops on top of the http_websocket_session state-machine library and
upgraded http_socket WebSocket connections. It adds connection
lifecycle ownership, optional auto-pong, keepalive, and idle-timeout
policies, client- and server-side conveniences that collapse the opening
handshake and the session loop into a single call, and a registry-backed
broadcast helper for multi-session servers.
This library can be used with backend Prolog systems that supports
unbound integer arithmetic and the sockets library: ECLiPSe, SICStus
Prolog, SWI-Prolog, Trealla Prolog, and XVM. On backends that don’t
support threads, only a subset of the library is usable; see the
“Backends without thread support” section below for the details.
API documentation
Open the ../../apis/library_index.html#http_websocket_service link in a web browser.
Loading
To load the library, load the loader.lgt file:
| ?- logtalk_load(http_websocket_service(loader)).
Testing
To test this library, load the tester.lgt file:
| ?- logtalk_load(http_websocket_service(tester)).
Current scope
The current implementation provides:
run_session/3-4for higher-level callback-driven loops over upgradedhttp_socketWebSocket connection handles that keep reading until the close handshake completes or the peer closes the stream and then close the upgraded connection automatically, with optional auto-pong, keepalive, idle-timeout, and maximum payload length policies.open/4-5in thehttp_websocket_client_serviceobject as a client-side convenience that combineshttp_client::open_websocket/4, optional initial outbound messages, and the higher-level session loop.serve_once/6-7in thehttp_websocket_server_serviceobject as a server-side convenience that combineshttp_socket::serve_websocket_once/5with the higher-level session loop.serve_until_shutdown/5-6andrequest_shutdown/1in thehttp_websocket_server_serviceobject for registry-backed multi-session servers with queued broadcast delivery.open/1,close/1,register/2,unregister/2,send/3,broadcast/2,broadcast_except/3, andtake_pending/3in thehttp_websocket_service_registryobject for managing active sessions and queued outbound messages.
Higher-level session loop
The run_session/3-4 predicates accept an upgraded http_socket
WebSocket connection handle together with a handler object that
implements the http_websocket_service_handler_protocol protocol:
handle(Message, Replies)
For each received normalized message, the handler returns a list of normalized reply messages to write before the next read. The loop:
starts from the initial session state,
reuses the stateful session orchestration including automatic close replies and optional auto-pong policy,
writes the handler replies using the stateful session write predicates,
continues until the close handshake reaches
closed(SentPayload, ReceivedPayload)or the peer closes the stream, andcloses the upgraded connection automatically before returning.
The run_session/4 predicate accepts these loop options:
auto_pong(on)orauto_pong(off)keepalive_interval(Seconds)idle_timeout(Seconds)max_payload_length(Bytes)
When keepalive_interval(Seconds) is used, the loop sends empty ping
messages after Seconds of inbound silence. When
idle_timeout(Seconds) is used, the loop sends
message(close, status(1001, idle_timeout)) after Seconds of
inbound silence and then waits one more idle interval for the peer close
reply. These timed options require backend thread support. The keepalive
and idle-timeout policies are tracked using absolute wall-clock
deadlines, making the loop robust to scheduling jitter and slow reads.
A small server-side session handler can look like:
:- object(chat_session_handler,
implements(http_websocket_service_handler_protocol)).
handle(message(text, ping), [message(text, pong)]) :-
!.
handle(message(text, Text), [message(text, Text)]) :-
!.
handle(message(close, _Payload), []) :-
!.
handle(_Message, []).
:- end_object.
After a successful opening handshake returns an upgraded Connection
handle, run the loop with either:
| ?- http_websocket_server_service::run_session(Connection, chat_session_handler, FinalState).
or:
| ?- http_websocket_server_service::run_session(Connection, chat_session_handler, FinalState, [auto_pong(on)]).
or with timed loop policies:
| ?- http_websocket_server_service::run_session(Connection, chat_session_handler, FinalState, [auto_pong(on), keepalive_interval(30), idle_timeout(120)]).
The run_session/3-4 predicates take ownership of the upgraded
connection, so no explicit http_socket::close_connection/1 call is
needed afterwards.
For a client that wants to collapse the opening handshake and the
callback loop into a single call, the http_websocket_client_service
object provides:
open(URL, SessionHandler, Response, FinalState)
or:
open(URL, SessionHandler, Response, FinalState, [protocols([chat]), initial_messages([message(text, hello)]), auto_pong(on)])
The initial_messages(Messages) option writes the given list of
normalized outbound messages immediately after the handshake and before
the first session read. The open/4-5 predicates also accept the
keepalive_interval/1, idle_timeout/1, and
max_payload_length/1 session-loop options. They take ownership of
the upgraded connection and close it automatically when the session loop
finishes.
For a server that wants to collapse the opening handshake and the
callback loop into a single call, the http_websocket_server_service
object provides:
serve_once(Listener, HandshakeHandler, SessionHandler, Response, FinalState, ClientInfo)
or:
serve_once(Listener, HandshakeHandler, SessionHandler, Response, FinalState, ClientInfo, [auto_pong(on)])
These predicates accept one incoming socket connection, serve one valid
opening handshake via the given HTTP handler, run the session loop via
the given session handler, and then close the upgraded connection
automatically. They also accept the keepalive_interval/1,
idle_timeout/1, and max_payload_length/1 session-loop options.
Registry-backed server helper
For multi-session servers that need queued outbound delivery and
broadcast, the http_websocket_server_service object also provides:
serve_until_shutdown(Listener, HandshakeHandler, SessionHandler, Registry, Control)
or:
serve_until_shutdown(Listener, HandshakeHandler, SessionHandler, Registry, Control, [auto_pong(on), keepalive_interval(30), idle_timeout(120)])
This helper accepts opening handshakes until:
http_websocket_server_service::request_shutdown(Control)is called, orthe listener is closed externally.
Each accepted upgraded connection is registered in Registry and
handled in its own session worker. The registry queues outbound messages
so that each worker only writes to its own connection.
When used with serve_until_shutdown/5-6, the session handler may
still return plain normalized reply messages, but it may also return
these action wrappers:
reply(Message)broadcast(Message)broadcast_others(Message)
For example, a simple chat-style broadcast handler can look like:
:- object(chat_broadcast_handler,
implements(http_websocket_service_handler_protocol)).
handle(message(text, Text), [broadcast_others(message(text, Text))]) :-
!.
handle(message(close, _Payload), []) :-
!.
handle(_Message, []).
:- end_object.
The http_websocket_service_registry object provides the registry
handle and queue-management predicates used by this helper. The
registry-backed server helper requires backend thread support.
Backends without thread support
The library loads on any backend that supports the sockets library
and unbounded integer arithmetic, but the available functionality
depends on whether the backend supports threads.
Available without thread support:
The
run_session/3-4,http_websocket_client_service::open/4-5, andhttp_websocket_server_service::serve_once/6-7predicates when called without thekeepalive_interval/1andidle_timeout/1options. In this case, the session loop reads from the connection using plain blocking reads and no background reader is required.The
http_websocket_service_registrydata predicates (open/1,close/1,register/2,unregister/2,send/3,broadcast/2,broadcast_except/3, andtake_pending/3), which are plain database operations.
Requiring thread support:
The
keepalive_interval/1andidle_timeout/1loop options. These policies need a timed session loop that polls a background reader, which is implemented using threads. When used on a backend without thread support, the loop predicates throw anot_available(http_websocket_service_timing)error before reading or writing any frames.Registry-driven session loops (i.e., sessions run on behalf of the registry-backed server helper). These impose a polling interval so that messages and broadcasts queued by other sessions can be flushed promptly, which also requires the timed session loop and thus thread support.
The
serve_until_shutdown/5-6andrequest_shutdown/1predicates, which run each accepted session in its own worker thread. On backends without thread support, these predicates throw anot_available(http_websocket_server_service_registry)error.
In all cases, unsupported usage fails fast with a not_available/1
error at validation time instead of hanging or silently misbehaving.
Current workflow
Use the
http_websocket_sessionlibrary directly when you need stateful reads, close-state tracking, or role-aware writes on binary streams that you manage yourself.When a single callback object should own the connection lifecycle, pass the upgraded handle to
run_session/3-4and let the service layer close it when the close handshake completes.When client-side code should own the full upgraded connection lifecycle, call
http_websocket_client_service::open/4-5and let the service layer handle the opening handshake, any configured initial outbound messages, and the final connection close.When server-side code already owns a listener and wants a single entry point for handshake plus session execution, call
http_websocket_server_service::serve_once/6-7.When server-side code needs multiple active sessions plus queued broadcast delivery, create a registry with
http_websocket_service_registry::open/1and callhttp_websocket_server_service::serve_until_shutdown/5-6.
Current limitations
The
run_session/3-4andserve_once/6-7helpers are synchronous, single-connection callback loops.On backends without thread support, the timed loop options
keepalive_interval/1andidle_timeout/1, registry-driven session loops, and theserve_until_shutdown/5-6helper are not available and thrownot_available/1errors; see the “Backends without thread support” section above.