drfreeze
I built a WebTransport transport for Phoenix sockets and LiveView and put it on GitHub with a demo and a spec: GitHub - jfreeze/phoenix_web_transport: WebTransport (HTTP/3, QUIC) transport for Phoenix LiveView: one QUIC stream per component, shortest-message-first. Working prototype. · GitHub
The idea is small. A LiveView pushes every diff down one WebSocket, which is one ordered TCP stream, so a 50-byte counter update queued behind a 300 KB table diff waits for the whole table, and one lost packet stalls everything behind it. LiveView’s diff format already splits by component, so this library puts each component’s deltas on its own QUIC stream and sets the stream priority from the frame size, smallest first. The browser side is a WebSocket-shaped class handed to LiveSocket through the transport option. Nothing in Phoenix, LiveView or phoenix.js is patched; the server side is a Phoenix.Socket.Transport plus a serializer.
The QUIC listener runs beside your existing Bandit endpoint. It uses cowboy’s experimental HTTP/3 and WebTransport layer on top of emqx’s quicer (msquic), because nothing else on the BEAM speaks QUIC yet. Bandit stays in charge of HTTP and the WebSocket fallback.
Status: it works end to end in Chrome against a local server, with the two-component demo page showing lane 0 for the join, lane 1 carrying kilobytes for the small component and lane 2 carrying megabytes for the table. On unthrottled loopback both transports measure the same, which is expected since the browser’s DOM patch dominates there. I have not measured it over a real network. A cowboy PR adding a set_stream_priority WebTransport command is up at Add a set_stream_priority WebTransport command by jfreeze · Pull Request #1725 · ninenines/cowboy · GitHub, and cowboy’s own suite passes with it.
Where I think this pays off is LiveView on mobile: bursty loss, long round trips, and QUIC’s per-stream recovery and one-round-trip handshake on top of the split. It needs a direct UDP path with a public cert, so it will not work behind Cloudflare Tunnel, which is how I host, so I cannot run the test that matters myself.
Two asks. If you serve LiveView to phones and have a host you can open a UDP port on, I would like a phone-on-cellular measurement with the demo page. And if anyone is working on QUIC for Thousand Island or Bandit, I would rather build the listener on that than on cowboy.
Known gaps are listed in the README: automatic fallback to WebSocket on handshake failure, auth on connect (WebTransport sends no cookies), and a per-diff sequence number so events pushed alongside a component delta fire after it lands.
Trending in Discussions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ai
- #ecto-query
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #metaprogramming
- #hex










Showing Posts 1 to 9- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
mat-hek
How about GitHub - benoitc/erlang_quic: Pure Erlang QUIC implementation (RFC 9000) · GitHub?
drfreeze
Duh. I missed that, thank you.
erlang_quic turned out to have everything the listener needs.
I built a second session layer on it, about 300 lines and no native code. Chrome connects end to end with the same per-component lanes as the cowboy version.
The README now also credits bugnano’s wtransport-elixir, which I should have listed in the post.
erlang_quic is now the default backend with cowboy and quicer as optional deps.
Do you have experience running erlang_quic anywhere to see how it holds up?
P.S. I should note that the new version uses Bandit and does not require cowboy.
gtcode
I built a real time media system in Elixir with LiveView this summer. I really wanted to use QUIC since it would be superior to a traditional sockets/TCP approach in terms of ordering restriction and cancellations for that use case.
Chose not to due to maturity of the libs vs other competing languages. But, building the frontend in C++, Rust or Go would not have been as joyous.
Glad to see people working on things like this. Elixir needs more attention in areas like QUIC.
mat-hek
We built ex_moq, Elixir bindings to Media Over QUIC, and a Membrane plugin if you’re interested
Unfortunately no
gtcode
Interesting – in my generative-media data plane, committed media and replacement candidates currently share an ordered WebSocket. I extracted the splice/adoption semantics above the transport (unfinished private project kicked off from the MVP post-mortem, of course). QUIC/MoQ/
ex_moqcould give those flows independent delivery. WebSockets could remain as fallback.drfreeze
Thanks, and no worries on erlang_quic.
ex_moq doesn’t replace the server side here, but it changed my hosting statement. Cloudflare runs MoQ relays with WebTransport to the browser and an outbound QUIC connection from the origin. That is the first way I’ve found to get QUIC streams to a LiveView through Cloudflare without a public UDP port.
Have you pushed non-media tracks through the Cloudflare relay with ex_moq, and how did latency compare to a direct connection?
mat-hek
We’re mostly sending media and through our own relays
byhemechi
You should be careful with
erlang_quic. There’s no buffering on sends, if you send a large message on a high latency connection there is a very high chance you’ll pass the bytes in flight limit and crash. This probably won’t happen in most LiveView scenarios but some things (base64 encoded images are the first thing that come to mind) will break.drfreeze
You were right, thank you. I reproduced it with a 40 MB update.
erlang_quic refuses to send once 16 MB is waiting.
It can also send more than the browser has said it will accept. Chrome closed the connection for that in one run out of four.
My code now sends in small pieces and waits when erlang_quic is backed up. 125 MB per run gets through, and small updates still arrive while a big one is in flight.
That seems to work around it for now.