14个版本 (7个重大变更)
0.21.0 | 2024年8月1日 |
---|---|
0.20.0 | 2024年6月27日 |
0.19.0 | 2024年6月5日 |
0.3.0 | 2024年2月5日 |
0.1.2 | 2023年6月30日 |
#50 in 异步
每月下载量232
用于 7 个crate(直接使用4个)
215KB
3K SLoC
tor-rpcbase
Arti RPC服务的后端
概述
Arti的RPC子系统围绕调用对象方法并接收这些方法调用异步响应的概念。
在这个crate中,我们定义了实现这些方法和对象的API。这是一个低级crate,因为我们希望能够在arti代码库中的最合理位置定义对象和方法。
关键概念
RPC会话作为一系列I-JSON(RFC7493)消息的编解码字节流实现。来自应用程序的每个消息都描述了对一个对象上的方法的调用。作为对这种消息的响应,Arti异步地回复零个或多个“更新消息”,以及最多一个最终的“回复”或“错误”消息。
这个crate定义了在Rust中定义这些对象和方法的机制。
对象是一个可以作为消息目标参与RPC API的值。为了成为对象,值必须实现Object
特质。应该尽可能将对象显式存储在Arc
中。
为了使用对象,RPC客户端必须有一个指向该对象的ObjectId
。我们说这样的对象在客户端会话上是“可见”的。并非所有对象都对所有客户端可见。
每种方法都被定义为Rust类型,它是`DynMethod
`类型的实例。方法的参数是该类型的字段。它的返回值是`DynMethod
`特质中的一个关联类型。每个方法通常都有一个关联的输出类型、错误类型和可选的更新类型,这些类型都通过方法实现`Method
`特质来定义。
为了从RPC会话中调用,该方法必须额外实现`DeserMethod
`,这还要求该方法及其关联类型。(没有这种属性的方法称为“特殊方法”;它们只能在Rust外部调用。)
一旦存在一个方法和一个对象,就可以在对象上定义该方法的实现。这是通过编写一个接受对象和方法类型作为参数的`async fn
`来完成的,稍后使用`static_rpc_invoke_fn!
`或`DispatchTable::extend
`来注册这个`async fn
`。
这些实现函数还接受一个Context
作为参数,该参数定义了与RPC会话的接口,以及一个可选的UpdateSink
,用于发送增量更新消息。
示例
use derive_deftly::Deftly;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tor_rpcbase as rpc;
// Here we declare that Cat is an Object.
// This lets us make Cats visible to the RPC system.
#[derive(Deftly)]
#[derive_deftly(rpc::Object)]
pub struct Cat {}
// Here we define a Speak method, reachable via the
// RPC method name "x-example:speak", taking a single argument.
#[derive(Deftly, Deserialize, Debug)]
#[derive_deftly(rpc::DynMethod)]
#[deftly(rpc(method_name = "x-example:speak"))]
pub struct Speak {
message: String,
}
// We define a type type to represent the output of the method.
#[derive(Debug, Serialize)]
pub struct SpeechReply {
speech: String,
}
// We declare that "Speak" will always have a given set of
// possible output, update, and error types.
impl rpc::RpcMethod for Speak {
type Output = SpeechReply;
type Update = rpc::NoUpdates;
}
// We write a function with this signature to implement `Speak` for `Cat`.
async fn speak_for_cat(
cat: Arc<Cat>,
method: Box<Speak>,
_context: Arc<dyn rpc::Context>
) -> Result<SpeechReply, rpc::RpcError> {
Ok(SpeechReply {
speech: format!(
"meow meow {} meow", method.message
)
})
}
// We register `speak_for_cat` as an RPC implementation function.
rpc::static_rpc_invoke_fn!{
speak_for_cat;
}
它的工作原理
这个包中的关键类型是`DispatchTable
`;它存储了一个从`(method, object)
`类型对到类型擦除调用函数(`dispatch::RpcInvocable
`的实现)的映射。当需要在一个对象上调用方法时,RPC会话使用`invoke_rpc_method
`与类型擦除的`Object
`和`DynMethod
`。然后使用`DispatchTable
`查找适当的`RpcInvocable
`并调用它。
类型擦除的`RpcInvocable
`函数是如何创建的?由于`RpcInvocable
`为具有适当类型的`Fn()`提供了泛型实现,因此它们会从适当的`async fn()
`自动创建。
注意:在RPC方法上不强制执行孤儿规则
此包允许任何其他包在RPC可见对象上定义RPC方法,即使方法类型和对象类型声明在另一个包中。
需要小心使用此功能:如果Arti后来在相同对象上定义了相同的方法,则这种外部添加的方法将导致RPC子系统损坏(并拒绝启动!)
当在Arti之外添加新的RPC方法时,最好是定义对象上的现有RPC方法,或者在Arti的对象上定义自己的RPC方法(在`arti:
`命名空间之外)。
注意:在能力系统周围要小心
Arti 的 RPC 模型假设 对象是能力:如果您有一个 Object
的有效 ObjectId
,则您有权调用其所有方法。RPC 系统通过不向客户端提供彼此的对象 Id,以及不提供访问全局状态(这会允许它们不恰当地相互影响)来将其客户端彼此隔离开。
在添加新方法时,这种做法很容易违反:Arti 的 Rust API 允许一些只有 RPC 超级用户才能执行的操作。
因此,在定义方法时,请确保您需要的是属于特定 RPC 会话的某些对象,并且您没有影响属于其他 RPC 会话的对象。
相关包
另请参阅
arti-rpcserver
,它实际上实现了 RPC 协议、会话和 objectId 映射。arti
,RPC 会话是基于对 RPC 套接字的传入连接创建的。- 在 arti 的其他包中使用了
Object
或DynMethod
。
许可证:MIT 或 Apache-2.0
依赖项
~7–15MB
~189K SLoC