rivetkit-client-rust
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRivetKit Rust Client
RivetKit Rust客户端
Use this skill when building Rust clients that connect to Rivet Actors with .
rivetkit::client当构建通过连接到Rivet Actors的Rust客户端时,请使用本指南。
rivetkit::clientVersion
版本
RivetKit version: 2.3.2
RivetKit版本:2.3.2
First Steps
入门步骤
- Add the dependency
sh
cargo add rivetkit anyhow async-trait cargo add serde --features derive cargo add tokio --features full - Create a client with and call typed actions with
Client::new(ClientConfig::new(endpoint)).get_or_create_typed_default::<A>(...)
- 添加依赖
sh
cargo add rivetkit anyhow async-trait cargo add serde --features derive cargo add tokio --features full - 使用创建客户端,并通过
Client::new(ClientConfig::new(endpoint))调用类型化操作。get_or_create_typed_default::<A>(...)
Error Handling Policy
错误处理策略
- Prefer fail-fast behavior by default. Propagate with
anyhow::Result.? - Avoid swallowing errors with broad arms unless absolutely needed.
match - If an error is handled inline, handle it explicitly, at minimum by logging it.
Rust support is in beta. The supported public Rust API is and ; lower-level crates are internal implementation details and do not carry a stability guarantee. See the full API reference on docs.rs/rivetkit, or the runnable example.
rivetkitrivetkit::clienthello-world-rust- 默认优先采用快速失败机制。通过传播
?。anyhow::Result - 除非绝对必要,否则避免使用宽泛的分支掩盖错误。
match - 如果在代码内处理错误,请显式处理,至少要记录日志。
Rust支持目前处于测试版。受支持的公开Rust API为和;底层crate属于内部实现细节,不提供稳定性保证。请查看docs.rs/rivetkit上的完整API参考,或可运行的示例。
rivetkitrivetkit::clienthello-world-rustGetting Started
快速开始
See the Rust quickstart guide for getting started.
请查看Rust快速入门指南以开始使用。
Install
安装
Add the crate and its companions:
rivetkitsh
cargo add rivetkit anyhow async-trait
cargo add serde --features derive
cargo add tokio --features fullThe Rust client is strongly typed. It shares the same action and event types as your actor, so define your actor in and import those types from both your server and your client. There is no need to redefine the actor on the client. See Define Your Actor in the quickstart for the actor definition this page builds on.
src/lib.rs添加 crate及其配套依赖:
rivetkitsh
cargo add rivetkit anyhow async-trait
cargo add serde --features derive
cargo add tokio --features fullRust客户端是强类型的。它与你的actor共享相同的操作和事件类型,因此请在中定义你的actor,并从服务器和客户端导入这些类型。无需在客户端重新定义actor。请查看快速入门中的定义你的Actor部分,了解本页面所基于的actor定义。
src/lib.rsMinimal Client
最小化客户端
rust
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(())
}counternameCargo.tomlCounterIncrementrust
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(())
}此处的是你的crate名称(中的package,短横线替换为下划线)。和是你与actor一起定义的类型。
counterCargo.tomlnameCounterIncrementStateless vs Stateful
无状态与有状态
rust
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"])?;
// Stateless: each call is independent
counter.send(Increment { amount: 1 }).await?;
// Stateful: keep a connection open for realtime events
let connection = counter.connect();
connection
.on::<NewCount>(|event| println!("count: {}", event.count))
.await;
connection.send(Increment { amount: 1 }).await?;
connection.disconnect().await;
Ok(())
}A stateless call on the handle opens a short-lived request per action. A connection keeps a WebSocket open so you can receive events and reuse it across calls.
rust
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"])?;
// Stateless: each call is independent
counter.send(Increment { amount: 1 }).await?;
// Stateful: keep a connection open for realtime events
let connection = counter.connect();
connection
.on::<NewCount>(|event| println!("count: {}", event.count))
.await;
connection.send(Increment { amount: 1 }).await?;
connection.disconnect().await;
Ok(())
}句柄上的无状态调用会为每个操作打开一个短连接。而连接会保持WebSocket打开,以便你接收事件并在多次调用中复用它。
Getting Actors
获取Actor
rust
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"));
// Get or create an actor
let room = client.get_or_create_typed_default::<Counter>("counter", ["room-42"])?;
// Get an existing actor handle (fails when used if the actor does not exist)
let existing = client.get_typed_default::<Counter>("counter", ["room-42"])?;
// Create a new actor with input
let created = client.get_or_create_typed::<Counter>(
"counter",
["game-1"],
GetOrCreateOptions {
create_with_input: Some(json!({ "mode": "ranked" })),
..Default::default()
},
)?;
// Get an actor handle by ID
let by_id = client.get_for_id("counter", "actor-id", Default::default())?;
// Resolve the actor ID
let resolved_id = room.inner().resolve().await?;
println!("Resolved ID: {resolved_id}");
Ok(())
}get_typed_defaultget_or_create_typed_defaultget_typedget_or_create_typedGetOptionsGetOrCreateOptionsrust
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"));
// Get or create an actor
let room = client.get_or_create_typed_default::<Counter>("counter", ["room-42"])?;
// Get an existing actor handle (fails when used if the actor does not exist)
let existing = client.get_typed_default::<Counter>("counter", ["room-42"])?;
// Create a new actor with input
let created = client.get_or_create_typed::<Counter>(
"counter",
["game-1"],
GetOrCreateOptions {
create_with_input: Some(json!({ "mode": "ranked" })),
..Default::default()
},
)?;
// Get an actor handle by ID
let by_id = client.get_for_id("counter", "actor-id", Default::default())?;
// Resolve the actor ID
let resolved_id = room.inner().resolve().await?;
println!("Resolved ID: {resolved_id}");
Ok(())
}get_typed_defaultget_or_create_typed_defaultget_typedget_or_create_typedGetOptionsGetOrCreateOptionsConnection Parameters
连接参数
Pass connection parameters through the handle options. They are delivered to the actor's callback:
create_conn_staterust
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(())
}通过句柄选项传递连接参数。它们会被传递到actor的回调中:
create_conn_staterust
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(())
}Subscribing to Events
订阅事件
onrust
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(())
}Event callbacks are synchronous and run for every matching event. The actor's emitted event type (here ) is decoded into the typed value for you.
NewCountonrust
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(())
}事件回调是同步的,会为每个匹配的事件运行。actor发出的事件类型(此处为)会被解码为类型化值供你使用。
NewCountConnection Lifecycle
连接生命周期
The lower-level connection exposes lifecycle callbacks and the current status. Reach it with :
connection.inner()rust
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(())
}ConnectionStatusIdleConnectingConnectedDisconnecteddisconnect底层连接暴露了生命周期回调和当前状态。通过获取它:
connection.inner()rust
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(())
}ConnectionStatusIdleConnectingConnectedDisconnecteddisconnectLow-Level HTTP & WebSocket
底层HTTP与WebSocket
For actors that implement or , call them directly on the untyped handle (). returns a , and returns a stream. This example also needs a few extra crates:
on_requeston_websockethandle.inner()fetchreqwest::Responseweb_sockettokio_tungstenitesh
cargo add futures-util tokio-tungstenite
cargo add reqwest --features jsonrust
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"])?;
// Raw HTTP request
let response = handle
.inner()
.fetch("history", Method::GET, HeaderMap::new(), None)
.await?;
let history: Vec<String> = response.json().await?;
println!("history: {history:?}");
// Raw WebSocket connection
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(())
}对于实现了或的actor,可以在非类型化句柄()上直接调用它们。返回,返回流。本示例还需要几个额外的crate:
on_requeston_websockethandle.inner()fetchreqwest::Responseweb_sockettokio_tungstenitesh
cargo add futures-util tokio-tungstenite
cargo add reqwest --features jsonrust
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"])?;
// Raw HTTP request
let response = handle
.inner()
.fetch("history", Method::GET, HeaderMap::new(), None)
.await?;
let history: Vec<String> = response.json().await?;
println!("history: {history:?}");
// Raw WebSocket connection
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(())
}Calling from Backend
从后端调用
The client is a normal Tokio type, so you can hold it in your backend (Axum, Actix, etc.) and call actors from request handlers. The client is and cheap to share:
Clonerust
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)
}客户端是标准的Tokio类型,因此你可以在后端(Axum、Actix等)中持有它,并从请求处理程序中调用actor。客户端支持,且共享成本低:
Clonerust
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)
}Error Handling
错误处理
Action and connection calls return . Actor-side errors surface as an carrying the error group, code, message, and metadata:
anyhow::Resultanyhow::Errorrust
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(())
}操作和连接调用返回。Actor端的错误会以的形式呈现,包含错误组、代码、消息和元数据:
anyhow::Resultanyhow::Errorrust
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
核心概念
Keys
键
Keys uniquely identify actor instances. Use compound keys (arrays) for hierarchical addressing:
rust
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"));
// Compound key: [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(())
}Keys accept arrays of or (). Don't build keys with string interpolation like when contains user data. Use arrays instead to prevent key injection attacks.
&strString["org-acme", "general"]format!("org:{user_id}")user_id键用于唯一标识actor实例。使用复合键(数组)进行分层寻址:
rust
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"));
// Compound key: [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(())
}键接受或数组(如)。当包含用户数据时,不要使用字符串插值(如)来构建键。请改用数组以防止键注入攻击。
&strString["org-acme", "general"]user_idformat!("org:{user_id}")Configuration
配置
ClientConfig::new(endpoint)rust
use rivetkit::client::ClientConfig;
let config = ClientConfig::new("http://localhost:6420")
.namespace("default")
.token("pk_...")
.pool_name("my-pool")
.header("x-custom", "value");- - target namespace (defaults to the engine's configured namespace).
namespace - - authentication token for the engine.
token - - runner pool to target.
pool_name - /
header- extra HTTP headers sent with each request.headers - - cap on encoded action input size.
max_input_size
For serverless deployments, set the endpoint to your app's URL. See Endpoints for details.
/api/rivetClientConfig::new(endpoint)rust
use rivetkit::client::ClientConfig;
let config = ClientConfig::new("http://localhost:6420")
.namespace("default")
.token("pk_...")
.pool_name("my-pool")
.header("x-custom", "value");- - 目标命名空间(默认为引擎配置的命名空间)。
namespace - - 引擎的认证令牌。
token - - 目标运行池。
pool_name - /
header- 随每个请求发送的额外HTTP头。headers - - 编码后操作输入的大小上限。
max_input_size
对于无服务器部署,请将endpoint设置为你的应用的 URL。详情请查看Endpoints。
/api/rivetAPI Reference
API参考
See the full client API documentation on docs.rs/rivetkit-client.
请查看docs.rs/rivetkit-client上的完整客户端API文档。
Need More Than the Client?
除了客户端还需要更多?
If you need more about Rivet Actors, registries, or server-side RivetKit, add the main skill:
bash
npx skills add rivet-dev/skillsThen use the skill for backend guidance.
rivetkit如果你需要了解更多关于Rivet Actors、注册表或服务器端RivetKit的信息,请添加主技能:
bash
npx skills add rivet-dev/skills然后使用技能获取后端相关指南。
rivetkit