160 lines
7.1 KiB
Markdown
160 lines
7.1 KiB
Markdown
<!-- file: crates/common/game-realtime-webtransport-lib/USAGE.md -->
|
|
<!-- version: 4 -->
|
|
|
|
# Utilisation de game-realtime-webtransport-lib
|
|
|
|
Ce guide décrit les chemins fiables natif et client navigateur/WASM exposés par `game-realtime-webtransport-lib`. Il ne décrit ni gameplay, ni protocole wire métier, ni datagrams.
|
|
|
|
## Configuration transport
|
|
|
|
`WebTransportConfig` porte les limites et deadlines du chemin fiable. La configuration par défaut garde une limite de 1 MiB par message. Les deadlines sont appliquées sur les chemins natif et navigateur pour la connexion, la sélection du stream primaire et chaque envoi complet.
|
|
|
|
Exemple de configuration plus stricte :
|
|
|
|
```rust
|
|
let transport = game_realtime_webtransport_lib::WebTransportConfig::default()
|
|
.with_max_message_size(256 * 1024)
|
|
.with_connect_timeout(std::time::Duration::from_secs(5))
|
|
.with_primary_stream_timeout(std::time::Duration::from_secs(2))
|
|
.with_send_timeout(std::time::Duration::from_secs(2));
|
|
```
|
|
|
|
La validation effective se fait lors du bind serveur ou de la connexion client. Une limite nulle, une limite non représentable en `u32` ou une deadline nulle est rejetée comme `InvalidConfiguration`. Côté navigateur, les deadlines doivent également tenir dans la plage `u32` millisecondes imposée par le timer WASM.
|
|
|
|
## Serveur natif
|
|
|
|
Créer d'abord l'identité TLS et le listener :
|
|
|
|
```rust
|
|
let identity = match game_realtime_webtransport_lib::WebTransportServerIdentity::generate_loopback() {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let config = game_realtime_webtransport_lib::WebTransportServerConfig::new(
|
|
std::net::SocketAddr::from(([127, 0, 0, 1], 4433)),
|
|
identity,
|
|
)
|
|
.with_transport_config(transport);
|
|
let mut listener = match game_realtime_webtransport_lib::WebTransportListener::bind(config) {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let session = match listener.accept().await {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let connection = match session.accept_primary_connection().await {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
```
|
|
|
|
L'attente du prochain client dans `listener.accept()` n'a pas de timeout périodique. Une fois une requête WebTransport surfacée, sa réponse finale utilise la deadline de connexion configurée.
|
|
|
|
`open_primary_connection()` écrit l'en-tête WebTransport requis pour identifier le stream avant de retourner. Le serveur peut donc attendre `accept_primary_connection()` puis commencer les échanges applicatifs ; aucune frame artificielle n'est nécessaire pour rendre le stream visible.
|
|
|
|
## Client natif
|
|
|
|
Le client doit connaître le SHA-256 exact du certificat serveur :
|
|
|
|
```rust
|
|
let config = match game_realtime_webtransport_lib::WebTransportClientConfig::new(
|
|
"https://127.0.0.1:4433/game",
|
|
certificate_hash,
|
|
) {
|
|
Ok(value) => value.with_transport_config(transport),
|
|
Err(error) => return Err(error),
|
|
};
|
|
let session = match game_realtime_webtransport_lib::connect(&config).await {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let connection = match session.open_primary_connection().await {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
```
|
|
|
|
Le pinning est obligatoire dans cette API native ; il n'existe pas de variante qui désactive globalement la vérification TLS.
|
|
|
|
## Client navigateur/WASM
|
|
|
|
Pour `wasm32-unknown-unknown`, les mêmes noms `WebTransportCertificateHash`, `WebTransportClientConfig`, `connect` et `WebTransportSession::open_primary_connection()` sont disponibles. Le serveur reste natif ; le navigateur est uniquement client.
|
|
|
|
Exemple de séquence Rust côté WASM :
|
|
|
|
```rust
|
|
let certificate_hash = game_realtime_webtransport_lib::WebTransportCertificateHash::from_sha256(server_sha256);
|
|
let config = match game_realtime_webtransport_lib::WebTransportClientConfig::new(
|
|
"https://127.0.0.1:4433/game",
|
|
certificate_hash,
|
|
) {
|
|
Ok(value) => value.with_transport_config(transport),
|
|
Err(error) => return Err(error),
|
|
};
|
|
let session = match game_realtime_webtransport_lib::connect(&config).await {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
let connection = match session.open_primary_connection().await {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
```
|
|
|
|
Le build workspace fournit `web_sys_unstable_apis` uniquement à `wasm32-unknown-unknown`. Le hash SHA-256 est transmis au navigateur comme `serverCertificateHashes`. La limite de message configurée est appliquée au framing WASM. `connect_timeout`, `primary_stream_timeout` et `send_timeout` sont réalisés par des timers WASM ; une expiration retourne `TransportErrorKind::Timeout`, et un send expiré reset le stream comme sur le chemin natif.
|
|
|
|
Le host Vite et l'adapter `wasm-bindgen` de `game-realtime-webtransport-browser-smoke` servent uniquement de preuve runtime. Ils ne sont pas nécessaires à un consommateur qui intègre déjà la bibliothèque dans son propre frontend.
|
|
|
|
## Contrat realtime
|
|
|
|
Une fois le stream primaire sélectionné, utiliser les traits de `game-realtime-transport-lib` pour le chemin fiable normal :
|
|
|
|
```rust
|
|
let (mut sender, mut receiver) = game_realtime_transport_lib::RealtimeConnection::split(connection);
|
|
|
|
let message = game_realtime_transport_lib::TransportMessage::new(vec![1, 2, 3, 4]);
|
|
if let Err(error) = game_realtime_transport_lib::RealtimeSender::send(&mut sender, message).await {
|
|
return Err(error);
|
|
}
|
|
|
|
let received = match game_realtime_transport_lib::RealtimeReceiver::receive(&mut receiver).await {
|
|
Ok(value) => value,
|
|
Err(error) => return Err(error),
|
|
};
|
|
|
|
if let Err(error) = game_realtime_transport_lib::RealtimeSender::close(&mut sender).await {
|
|
return Err(error);
|
|
}
|
|
```
|
|
|
|
Le backend encode chaque `TransportMessage` sous la forme `u32` big-endian + payload. Le consommateur ne doit pas reproduire ce framing lui-même.
|
|
|
|
La flow-control QUIC est respectée naturellement par l'écriture asynchrone. Sur les chemins natif et navigateur, un send qui dépasse sa deadline est considéré terminal : le stream est reset et le même sender ne doit pas être réutilisé.
|
|
|
|
## Fermeture et abort
|
|
|
|
`RealtimeSender::close()` termine proprement la direction d'émission du stream primaire. Le pair observe ensuite `TransportReceive::Closed` lorsqu'il atteint le FIN après les messages déjà écrits.
|
|
|
|
Pour abandonner explicitement une direction WebTransport :
|
|
|
|
```rust
|
|
if let Err(error) = sender.abort(42) {
|
|
return Err(error);
|
|
}
|
|
```
|
|
|
|
ou côté réception :
|
|
|
|
```rust
|
|
if let Err(error) = receiver.abort(43) {
|
|
return Err(error);
|
|
}
|
|
```
|
|
|
|
Ces deux méthodes sont backend-spécifiques : elles ne sont pas ajoutées au contrat commun car WebSocket n'expose pas la même primitive QUIC de reset/stop.
|
|
|
|
Dropper un sender actif ou annuler une future `send()` en cours provoque un reset explicite. Dropper un receiver actif provoque un stop explicite. Cela évite qu'une cancellation d'écriture partielle soit interprétée comme une fermeture propre ou qu'une frame suivante reprenne au mauvais offset.
|
|
|
|
Une future `receive()` peut en revanche être annulée puis relancée : le backend conserve l'état partiel du header/payload et reprend le framing à l'octet correct.
|