-module(livery_ws_h2).
-moduledoc """
`ws_transport` implementation over an HTTP/2 stream.
After an extended-CONNECT WebSocket handshake (RFC 8441), the WS
session exchanges frames as HTTP/2 DATA on the same stream. This
module maps the `ws_transport` callbacks onto the `h2` library:
- `send/2` -> `h2:send_data/4` (no end_stream; frames keep flowing)
- `controlling_process/2` -> `h2:set_stream_handler/4`, so the
session process receives the stream's DATA events
- `classify/2` turns `{h2, Conn, {data, ...}}` into `{ws_data, ...}`
and stream resets into `{ws_closed, ...}`
The handle is `{Conn, StreamId}`.
""".
-behaviour(ws_transport).
-export([
send/2,
activate/1,
close/1,
controlling_process/2,
classify/2,
peername/1
]).
-type handle() :: {h2:connection(), h2:stream_id()}.
-spec send(handle(), iodata()) -> ok | {error, term()}.
send({Conn, StreamId}, IoData) ->
h2:send_data(Conn, StreamId, iolist_to_binary(IoData), false).
-spec activate(handle()) -> ok.
activate(_Handle) ->
%% h2 delivers DATA to whichever pid is the registered stream
%% handler; there is no per-batch pull to arm.
ok.
-spec close(handle()) -> ok.
close({Conn, StreamId}) ->
_ =
try
h2:send_data(Conn, StreamId, <<>>, true)
catch
_:_ -> ok
end,
ok.
-spec controlling_process(handle(), pid()) -> ok | {error, term()}.
controlling_process({Conn, StreamId}, Pid) ->
case h2:set_stream_handler(Conn, StreamId, Pid, #{drain_buffer => false}) of
ok -> ok;
{ok, _} -> ok;
{error, _} = E -> E
end.
-spec classify(term(), handle()) ->
{ws_data, handle(), binary()}
| {ws_closed, handle()}
| {ws_error, handle(), term()}
| ignore.
classify({h2, Conn, {data, StreamId, Bin, _Fin}}, {Conn, StreamId} = H) ->
{ws_data, H, Bin};
classify({h2, Conn, {stream_reset, StreamId, Reason}}, {Conn, StreamId} = H) ->
{ws_error, H, Reason};
classify({h2, Conn, {trailers, StreamId, _}}, {Conn, StreamId} = H) ->
{ws_closed, H};
classify({h2, Conn, {closed, _}}, {Conn, _} = H) ->
{ws_closed, H};
classify(_Msg, _Handle) ->
ignore.
-spec peername(handle()) -> {error, not_supported}.
peername(_Handle) ->
{error, not_supported}.