7.1 KiB
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 par le chemin natif ; le client navigateur les valide mais leur matérialisation par timer reste différée à alpha.7.
Exemple de configuration plus stricte :
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, y compris côté navigateur même lorsque le timer correspondant n'est pas encore matérialisé.
Serveur natif
Créer d'abord l'identité TLS et le listener :
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 :
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 :
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. Les deadlines connect_timeout, primary_stream_timeout et send_timeout restent validées mais ne sont pas encore appliquées par un timer navigateur dans la tranche de compilation ; cette politique est fermée avec le smoke runtime de la tranche suivante.
Aucun wasm-bindgen frontend ou host Vite n'est requis pour simplement vérifier la compilation de la bibliothèque.
Contrat realtime
Une fois le stream primaire sélectionné, utiliser les traits de game-realtime-transport-lib pour le chemin fiable normal :
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 le chemin natif, 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é. Sur le chemin navigateur de alpha.6, une cancellation du send reste terminale et reset le stream, mais le timer send_timeout est différé à alpha.7.
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 :
if let Err(error) = sender.abort(42) {
return Err(error);
}
ou côté réception :
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.