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 异步

Download history 9/week @ 2024-05-04 8/week @ 2024-05-11 20/week @ 2024-05-18 23/week @ 2024-05-25 166/week @ 2024-06-01 27/week @ 2024-06-08 12/week @ 2024-06-15 132/week @ 2024-06-22 19/week @ 2024-06-29 1/week @ 2024-07-06 4/week @ 2024-07-13 1/week @ 2024-07-20 178/week @ 2024-07-27 30/week @ 2024-08-03 13/week @ 2024-08-10 11/week @ 2024-08-17

每月下载量232
用于 7 个crate(直接使用4个)

MIT/Apache

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 的其他包中使用了 ObjectDynMethod

许可证:MIT 或 Apache-2.0

依赖项

~7–15MB
~189K SLoC