AppRTC is a WebRTC reference application and signaling server in the webrtc-rs ecosystem. The current Rust
implementation provides the complete AppRTC-compatible P2P V1 room and signaling flow: HTTP join/message/leave APIs,
initiator election, two-member rooms, queued signaling messages, tokenless browser WebSocket registration, reconnect
grace, fallback POST/DELETE signaling, HTML templates, static web assets, ICE configuration, and HTTP/HTTPS plus WS/WSS
serving.
The current binaries support P2P V1. The repository also contains the Sans-I/O SFU implementation and the architecture for P2P/SFU call modes, but V2 mode transitions and SFU worker integration are not yet enabled.
The Rust implementation replaces the previous unified Go Collider. The legacy implementation is retained only on the
repository's go branch.
Architecture
The workspace has four Rust crates:
| Crate | Responsibility |
|---|---|
apprtc |
Standalone appweb and signaling binaries: CLI parsing, TLS listeners, logging, graceful shutdown, and the async WebSocket I/O driver that hosts the Sans-I/O signaling core. |
signaling-proto |
Generated Protobuf contract shared by AppWeb and signaling for their private control WebSocket. |
appweb |
AppRTC HTTP room API, configuration parameters, Jinja templates, static web assets, and the in-process client for the signaling authority. |
signaling |
Authoritative room/client state, V1 browser protocol, message queueing and relay, and reconnect deadlines — a pure Sans-I/O crate with no sockets, no threads, and no clock of its own. |
AppWeb and signaling are separate processes and may run on different machines. AppWeb serves HTTP(S) and uses a
Protobuf control WebSocket at /app to submit admit, remove, occupancy, inject, and status operations to
signaling. Browser
WebSocket traffic connects directly to signaling and never passes through AppWeb.
The signaling state is composed from Sans-I/O protocols:
Collider
└── RoomTable
└── Room
└── Client
Every layer implements the sansio::Protocol trait, so the whole signaling state machine is deterministic and
testable in memory, without sockets or a wall clock.
All I/O lives at the binary level, in apprtc/src/signaling_server.rs, keeping
the SFU chat example's architecture in async form on Tokio: an accept loop (accept_loop) performs the optional TLS
handshake and the browser /ws or private /app WebSocket upgrade, spawning one session task per connection,
while a single event-loop task
(event_loop) owns the Collider — serializing every browser input through it, firing its timeouts, and routing
its outputs back to each session over channels. Sessions and the event loop sleep on tokio::select! and wake
immediately on input, output, deadline, or shutdown — no blocking calls and no polling intervals.
Successful V1 registration is intentionally silent, and a disconnected registered client remains eligible to reconnect for 10 seconds before its membership is removed.
Current P2P V1 behavior
- Room and client IDs are opaque non-empty strings.
- A room contains at most two clients; a third join returns
FULL. - The first client is the initiator and the second is the callee.
- Removing a client promotes the survivor to initiator.
- Offers and trickle ICE candidates sent before the peer joins are queued.
- The second
/joinresponse returns queued messages inparams.messages. - The stock AppRTC asymmetric signaling flow is preserved: the initiator sends early signaling through
/message, while the callee normally sends through WebSocket. - A WebSocket disconnect starts a 10-second reconnect grace period instead of removing the client immediately.
- Root-path and
/_internalPOST/DELETE fallback routes are both supported. - V1 identifiers are not restricted to numeric values. Numeric
u64validation belongs to the future V2 protocol.
Build and test
Use a Rust toolchain with Edition 2024 support.
The integration tests are black-box clients of real standalone AppWeb and signaling TLS servers:
# 1. Start signaling.
&
# 2. Start AppWeb.
&
# 3. Run the integration tests.
# 4. Stop both servers.
||
CI performs the same sequence with a release build in .github/workflows/tests.yml and uploads the server log when the
job finishes.
Run over HTTP and WebSocket
Run signaling and AppWeb separately from the repository root:
AppWeb prints:
AppWeb listening on http://127.0.0.1:8080/
Open http://127.0.0.1:8080 in a browser.
--host-ip and --port control each local listener. AppWeb's --public-url controls the browser-facing HTTP origin;
--signaling-url controls the browser-facing WebSocket URL and must include /ws. AppWeb derives the same signaling
origin's private /app endpoint for its Protobuf control connection.
Run over HTTPS and secure WebSocket
Add --tls to serve real HTTPS and WSS from the same listener:
Without certificate options, AppRTC uses the bundled development certificate at
apprtc/cert/cert.pem. Its subject alternative names include localhost, 127.0.0.1, and
::1, but it is self-signed. Trust that certificate in the browser or operating-system trust store before opening the
page; otherwise HTTPS and WSS clients will reject it with CertificateUnknown or an equivalent certificate-authority
error.
For a deployment, supply a certificate issued by a trusted authority. Both options must be supplied together:
Command-line options
Run cargo run -p apprtc --bin appweb -- --help or cargo run -p apprtc --bin signaling -- --help for the
authoritative lists.
| Option | Default | Description |
|---|---|---|
--host-ip <HOST-IP> |
127.0.0.1 |
Local listener bind address (both binaries). |
--public-url <URL> |
listener address/scheme | Browser-facing HTTP(S) origin (appweb) or WS(S) origin (signaling). |
-p, --port <PORT> |
8080/8081 |
AppWeb HTTP(S) or signaling WS(S) listening port. |
--web-root <PATH> |
appweb |
Static asset directory (appweb). |
--tls |
off | Serve HTTPS/WSS instead of HTTP/WS. |
--certificate <PATH> |
bundled certificate | PEM certificate chain used with --tls. |
--private-key <PATH> |
bundled key | PEM private key used with --tls. |
--signaling-url <URL> |
none | Browser signaling URL ending in /ws; AppWeb derives /app. |
--signaling-insecure-tls |
off | Disable verification for local self-signed signaling TLS (appweb). |
--appid, --signaling-token |
appweb-1, empty |
AppWeb control identity and token (appweb). |
--ice-server-url <URLS> |
empty | ICE server URLs (appweb). |
--ice-server-base-url <URL> |
AppWeb origin | External ICE credential service origin (appweb). |
--ice-server-api-key <KEY> |
empty | API key for the ICE credential service (appweb). |
--header-message <TEXT> |
empty | Banner displayed by the web application (appweb). |
--bypass-join-confirmation |
off | Skip the browser ready-to-join prompt (appweb). |
-d, --debug |
off | Enable application logging (both binaries). |
-l, --level <LEVEL> |
info |
Log filter (both binaries). |
-o, --output-log-file <PATH> |
stdout | Write formatted logs to a file (both binaries). |
Example ICE configuration:
HTTP API
| Method and path | Behavior |
|---|---|
GET / |
Render the room-selection page. |
GET /r/{roomid} |
Render the call page or the full-room page when occupancy is two. |
POST /join/{roomid} |
Generate an eight-digit client ID, admit it, and return legacy AppRTC room parameters. |
POST /message/{roomid}/{clientid} |
Queue or relay the raw signaling message and return { "result": "SUCCESS" }. |
POST /leave/{roomid}/{clientid} |
Remove the client and return the legacy empty success response. |
GET /params |
Return room-independent AppRTC parameters. |
GET or POST /v1alpha/iceconfig |
Return the configured ICE server list. |
GET /status |
Return uptime and WebSocket/HTTP counters. |
POST /{roomid}/{clientid} |
V1 wss_post_url fallback: inject a raw signaling message. |
DELETE /{roomid}/{clientid} |
V1 wss_post_url fallback: remove the client. |
POST or DELETE /_internal/{roomid}/{clientid} |
Compatibility alias for the fallback bridge. |
Static files under appweb/js, appweb/css, appweb/images, and appweb/html are served by the same process.
Join response
A successful join returns the legacy shape consumed by appweb/js/call.js:
The actual params object also contains peer-connection constraints, ICE configuration, media constraints, room links,
loopback settings, and UI configuration.
V1 WebSocket protocol
Browsers connect to /ws and send a registration frame first:
A successful V1 registration has no acknowledgement. The registered client sends an opaque signaling payload with:
The peer receives the payload without the signaling authority parsing or modifying the inner JSON:
Protocol errors are sent once before the socket is closed:
The inner msg string may contain an SDP offer, SDP answer, trickle ICE candidate, end-of-candidates marker, or bye
object. V1 signaling treats it as an opaque UTF-8 string.
Status endpoint
GET /status preserves the Collider-compatible response:
License
This project is dual-licensed under the MIT and Apache-2.0 licenses.
Contributing
Contributors and pull requests are welcome.