rivetkit-client-rust

Par rivet-dev · skills

Guide du client Rust RivetKit. À utiliser pour les clients et backends Rust qui se connectent aux Rivet Actors avec `rivetkit::client`, créent des handles d'acteur typés, appellent des actions ou gèrent des connexions.

npx skills add https://github.com/rivet-dev/skills --skill rivetkit-client-rust

Client Rust RivetKit

Utilise cette skill lors de la création de clients Rust qui se connectent à des Rivet Actors avec rivetkit::client.

Version

Version RivetKit : 2.3.2

Premiers pas

  1. Ajoute la dépendance
    cargo add rivetkit anyhow async-trait
    cargo add serde --features derive
    cargo add tokio --features full
  2. Crée un client avec Client::new(ClientConfig::new(endpoint)) et appelle les actions typées avec get_or_create_typed_default::<A>(...).

Politique de gestion des erreurs

  • Préfère le comportement fail-fast par défaut. Propage anyhow::Result avec ?.
  • Évite d'étouffer les erreurs avec des branches match larges sauf si c'est absolument nécessaire.
  • Si une erreur est gérée inline, gère-la explicitement, au minimum en la journalisant.

Le support Rust est en bêta. L'API publique Rust supportée est rivetkit et rivetkit::client ; les crates de bas niveau sont des détails d'implémentation interne et ne portent aucune garantie de stabilité. Consulte la référence API complète sur docs.rs/rivetkit, ou l'exemple exécutable hello-world-rust.

Démarrage

Consulte le guide de démarrage rapide Rust pour commencer.

Installation

Ajoute la crate rivetkit et ses compagnons :

cargo add rivetkit anyhow async-trait
cargo add serde --features derive
cargo add tokio --features full

Le client Rust est fortement typé. Il partage les mêmes types d'actions et d'événements que ton actor, donc définis ton actor dans src/lib.rs et importe ces types depuis ton serveur et ton client. Il n'est pas nécessaire de redéfinir l'actor côté client. Consulte Define Your Actor dans le guide de démarrage rapide pour la définition d'actor sur laquelle cette page s'appuie.

Client minimal

use counter::{Counter, Increment};
use rivetkit::{
    client::{Client, ClientConfig},
    prelude::*,
    TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));

    let counter = client.get_or_create_typed_default::<Counter>("counter", ["my-counter"])?;
    let count = counter.send(Increment { amount: 1 }).await?;
    println!("New count: {count}");

    Ok(())
}

counter ici est le nom de ta crate (le name du package dans Cargo.toml, avec des traits d'union en tirets bas). Counter et Increment sont les types que tu as définis aux côtés de ton actor.

Sans état vs avec état

use counter::{Counter, Increment, NewCount};
use rivetkit::{
    client::{Client, ClientConfig},
    prelude::*,
    TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
    let counter = client.get_or_create_typed_default::<Counter>("counter", ["my-counter"])?;

    // Sans état : chaque appel est indépendant
    counter.send(Increment { amount: 1 }).await?;

    // Avec état : garde une connexion ouverte pour les événements en temps réel
    let connection = counter.connect();
    connection
        .on::<NewCount>(|event| println!("count: {}", event.count))
        .await;
    connection.send(Increment { amount: 1 }).await?;

    connection.disconnect().await;
    Ok(())
}

Un appel sans état sur le handle ouvre une requête courte durée par action. Une connexion garde un WebSocket ouvert pour que tu puisses recevoir des événements et le réutiliser entre les appels.

Obtenir les Actors

use counter::Counter;
use rivetkit::{
    client::{Client, ClientConfig, GetOrCreateOptions},
    prelude::*,
    TypedClientExt,
};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));

    // Obtiens ou crée un actor
    let room = client.get_or_create_typed_default::<Counter>("counter", ["room-42"])?;

    // Obtiens un handle d'actor existant (échoue s'il n'existe pas)
    let existing = client.get_typed_default::<Counter>("counter", ["room-42"])?;

    // Crée un nouvel actor avec l'entrée
    let created = client.get_or_create_typed::<Counter>(
        "counter",
        ["game-1"],
        GetOrCreateOptions {
            create_with_input: Some(json!({ "mode": "ranked" })),
            ..Default::default()
        },
    )?;

    // Obtiens un handle d'actor par ID
    let by_id = client.get_for_id("counter", "actor-id", Default::default())?;

    // Résous l'ID de l'actor
    let resolved_id = room.inner().resolve().await?;
    println!("Resolved ID: {resolved_id}");

    Ok(())
}

get_typed_default / get_or_create_typed_default utilisent les options par défaut. Les variantes non-défaut (get_typed / get_or_create_typed) prennent GetOptions / GetOrCreateOptions pour les paramètres de connexion, l'entrée et la région.

Paramètres de connexion

Passe les paramètres de connexion via les options du handle. Ils sont livrés au callback create_conn_state de l'actor :

use counter::Counter;
use rivetkit::{
    client::{Client, ClientConfig, GetOrCreateOptions},
    prelude::*,
    TypedClientExt,
};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));

    let chat = client.get_or_create_typed::<Counter>(
        "counter",
        ["general"],
        GetOrCreateOptions {
            params: Some(json!({ "authToken": "jwt-token-here" })),
            ..Default::default()
        },
    )?;

    let connection = chat.connect();
    connection.disconnect().await;
    Ok(())
}

S'abonner aux événements

on enregistre un callback typé pour un événement et revient une fois que l'abonnement est enregistré :

use counter::{Counter, NewCount};
use rivetkit::{
    client::{Client, ClientConfig},
    prelude::*,
    TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
    let connection = client
        .get_or_create_typed_default::<Counter>("counter", ["general"])?
        .connect();

    connection
        .on::<NewCount>(|event| println!("count changed: {}", event.count))
        .await;

    Ok(())
}

Les callbacks d'événements sont synchrones et s'exécutent pour chaque événement correspondant. Le type d'événement émis par l'actor (ici NewCount) est décodé en valeur typée pour toi.

Cycle de vie de la connexion

La connexion de bas niveau expose les callbacks de cycle de vie et le statut actuel. Accède-y avec connection.inner() :

use counter::Counter;
use rivetkit::{
    client::{Client, ClientConfig},
    prelude::*,
    TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
    let connection = client
        .get_or_create_typed_default::<Counter>("counter", ["general"])?
        .connect();
    let inner = connection.inner().clone();

    inner.on_open(|| println!("connected")).await;
    inner.on_close(|| println!("disconnected")).await;
    inner.on_error(|message| eprintln!("error: {message}")).await;
    inner
        .on_status_change(|status| println!("status: {status:?}"))
        .await;

    println!("current status: {:?}", inner.conn_status());

    connection.disconnect().await;
    Ok(())
}

ConnectionStatus est l'un de Idle, Connecting, Connected, ou Disconnected. Les connexions se reconnectent automatiquement avec backoff jusqu'à ce que tu appelles disconnect.

HTTP et WebSocket de bas niveau

Pour les actors qui implémentent on_request ou on_websocket, appelle-les directement sur le handle non typé (handle.inner()). fetch retourne une reqwest::Response, et web_socket retourne un stream tokio_tungstenite. Cet exemple a aussi besoin de quelques crates supplémentaires :

cargo add futures-util tokio-tungstenite
cargo add reqwest --features json
use counter::Counter;
use futures_util::{SinkExt, StreamExt};
use reqwest::{header::HeaderMap, Method};
use rivetkit::{
    client::{Client, ClientConfig},
    prelude::*,
    TypedClientExt,
};
use tokio_tungstenite::tungstenite::Message;

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
    let handle = client.get_or_create_typed_default::<Counter>("counter", ["general"])?;

    // Requête HTTP brute
    let response = handle
        .inner()
        .fetch("history", Method::GET, HeaderMap::new(), None)
        .await?;
    let history: Vec<String> = response.json().await?;
    println!("history: {history:?}");

    // Connexion WebSocket brute
    let mut ws = handle.inner().web_socket("stream", None).await?;
    ws.send(Message::text("hello")).await?;
    if let Some(message) = ws.next().await {
        println!("received: {:?}", message?);
    }

    Ok(())
}

Appeler depuis le Backend

Le client est un type Tokio normal, tu peux donc le tenir dans ton backend (Axum, Actix, etc.) et appeler des actors depuis des gestionnaires de requêtes. Le client est Clone et bon marché à partager :

use counter::{Counter, Increment};
use rivetkit::{
    client::{Client, ClientConfig},
    prelude::*,
    TypedClientExt,
};

async fn increment(client: Client) -> Result<i64> {
    let counter = client.get_or_create_typed_default::<Counter>("counter", ["server-counter"])?;
    let count = counter.send(Increment { amount: 1 }).await?;
    Ok(count)
}

Gestion des erreurs

Les appels d'actions et de connexions retournent anyhow::Result. Les erreurs côté actor font surface comme une anyhow::Error portant le groupe d'erreur, le code, le message et les métadonnées :

use counter::{Counter, Increment};
use rivetkit::{
    client::{Client, ClientConfig},
    prelude::*,
    TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));
    let counter = client.get_or_create_typed_default::<Counter>("counter", ["my-counter"])?;

    match counter.send(Increment { amount: 1 }).await {
        Ok(count) => println!("count: {count}"),
        Err(error) => eprintln!("action failed: {error:#}"),
    }

    Ok(())
}

Concepts

Clés

Les clés identifient de manière unique les instances d'actor. Utilise des clés composées (tableaux) pour l'adressage hiérarchique :

use counter::Counter;
use rivetkit::{
    client::{Client, ClientConfig},
    prelude::*,
    TypedClientExt,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new(ClientConfig::new("http://localhost:6420").namespace("default"));

    // Clé composée : [org, room]
    let room = client.get_or_create_typed_default::<Counter>("counter", ["org-acme", "general"])?;
    let actor_id = room.inner().resolve().await?;
    println!("Actor ID: {actor_id}");

    Ok(())
}

Les clés acceptent des tableaux de &str ou String (["org-acme", "general"]). Ne construis pas de clés avec l'interpolation de chaîne comme format!("org:{user_id}") quand user_id contient des données utilisateur. Utilise plutôt des tableaux pour éviter les attaques par injection de clé.

Configuration

ClientConfig::new(endpoint) est un builder. L'endpoint est toujours requis ; il n'y a pas de défaut. Options courantes :

use rivetkit::client::ClientConfig;

let config = ClientConfig::new("http://localhost:6420")
    .namespace("default")
    .token("pk_...")
    .pool_name("my-pool")
    .header("x-custom", "value");
  • namespace - namespace cible (par défaut celui configuré par le moteur).
  • token - token d'authentification pour le moteur.
  • pool_name - pool de runner à cibler.
  • header / headers - en-têtes HTTP supplémentaires envoyés avec chaque requête.
  • max_input_size - limite sur la taille de l'entrée d'action encodée.

Pour les déploiements serverless, définis l'endpoint à l'URL /api/rivet de ton application. Consulte Endpoints pour les détails.

Référence API

Consulte la documentation API client complète sur docs.rs/rivetkit-client.

Besoin de plus que le Client ?

Si tu as besoin de plus sur les Rivet Actors, les registries, ou le RivetKit côté serveur, ajoute la skill principale :

npx skills add rivet-dev/skills

Puis utilise la skill rivetkit pour des conseils sur le backend.

Skills similaires