# `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 WebSocket connections provided by a selected `http_transport_protocol` implementation. 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.

By default, the convenience objects use the `http_socket_transport` transport. The parametric `http_websocket_service(_HTTPSocket_, _Role_, _TextRepresentation_)`, `http_websocket_client_service(_HTTPSocket_)`, and `http_websocket_server_service(_HTTPSocket_)` objects can also use alternative `http_transport_protocol` implementations such as `http_process_transport`.

This library can be used with backend Prolog systems that support 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-4` for higher-level callback-driven loops over upgraded WebSocket connection handles produced by the selected transport parameterization 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-5` in the `http_websocket_client_service` object as a client-side convenience that combines `http_client::open_websocket/4`, optional initial outbound messages, and the higher-level session loop. The accepted URL schemes depend on the selected transport parameterization, e.g. `ws://` for `http_socket_transport` and both `ws://` and `wss://` for `http_process_transport`.

- `serve_once/6-7` in the `http_websocket_server_service` object as a server-side convenience that combines the selected transport parameterization `serve_websocket_once/5` predicate with the higher-level session loop.

- `serve_until_shutdown/5-6` and `request_shutdown/1` in the `http_websocket_server_service` object 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`, and `take_pending/3` in the `http_websocket_service_registry` object for managing active sessions and queued outbound messages.

## Higher-level session loop

The `run_session/3-4` predicates accept an upgraded WebSocket connection handle produced by the selected transport parameterization 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, and

- closes the upgraded connection automatically before returning.

The `run_session/4` predicate accepts these loop options:

- `auto_pong(on)` or `auto_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 transport `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 accepted WebSocket URL schemes depend on the selected transport parameterization. For example, the default `http_websocket_client_service` object uses `http_socket_transport` and therefore accepts `ws://` URLs, while `http_websocket_client_service(http_process_transport)` also accepts `wss://` URLs.

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.

For server-side helpers, the `Listener` must come from the same selected transport parameterization as the service object, e.g. `http_websocket_server_service(http_process_transport)` must be paired with a listener opened by `http_process_transport::open_listener/4`.

## 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, or

- the 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`, and `http_websocket_server_service::serve_once/6-7` predicates when called without the `keepalive_interval/1` and `idle_timeout/1` options. In this case, the session loop reads from the connection using plain blocking reads and no background reader is required.

- The `http_websocket_service_registry` data predicates (`open/1`, `close/1`, `register/2`, `unregister/2`, `send/3`, `broadcast/2`, `broadcast_except/3`, and `take_pending/3`), which are plain database operations.

Requiring thread support:

- The `keepalive_interval/1` and `idle_timeout/1` loop 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 a `existence_error(http_websocket_service,timing)` exception 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-6` and `request_shutdown/1` predicates, which run each accepted session in its own worker thread. On backends without thread support, these predicates throw a `existence_error(http_websocket_server_service,registry)` exception.

In all cases, unsupported usage fails fast with a `existence_error/2` exceptions at validation time instead of hanging or silently misbehaving.

## Current workflow

- Use the `http_websocket_session` library 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-4` and 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-5` and let the service layer handle the opening handshake, any configured initial outbound messages, and the final connection close.

- When the client transport must support secure `wss://` URLs, use the a TLS-capable parameterization such as `http_process_transport`.

- 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 uses a non-default transport such as `http_process_transport`, use the matching parametric service object and open the listener with that same transport parameterization.

- When server-side code needs multiple active sessions plus queued broadcast delivery, create a registry with `http_websocket_service_registry::open/1` and call `http_websocket_server_service::serve_until_shutdown/5-6`.

## Current limitations

- The `run_session/3-4` and `serve_once/6-7` helpers are synchronous, single-connection callback loops.

- On backends without thread support, the timed loop options `keepalive_interval/1` and `idle_timeout/1`, registry-driven session loops, and the `serve_until_shutdown/5-6` helper are not available and throw `existence_error/2` exceptions; see the “Backends without thread support” section above.