15 个版本 (稳定)

2.3.5 2023 年 5 月 24 日
2.3.4 2023 年 5 月 16 日
2.0.1 2023 年 4 月 28 日
1.0.0 2023 年 3 月 21 日
0.1.3 2023 年 2 月 28 日

WebSocket 类别中排名 57

Download history 1/week @ 2024-05-28 6/week @ 2024-06-04 2/week @ 2024-06-11 1/week @ 2024-06-18 73/week @ 2024-07-02 80/week @ 2024-07-23 2/week @ 2024-07-30

每月下载量 82

Apache-2.0

180KB
3K SLoC

dcl-rpc

Build License Cargo Documentation

Decentraland RPC 的 Rust 实现。在 Decentraland 中,我们对不同服务之间的通信有自己实现的 RPC。

目前,还有其他实现

要求

  • 安装 Just

仅安装 Just 以用于命令

cargo install just

示例

运行集成示例

Rust 中的 RPC 客户端和 Rust 中的 RPC 服务器运行 WebSocket 传输示例、内存传输示例以及使用不同类型传输的示例

只需运行-integration

使用特定传输运行集成示例

Rust 中的 RPC 客户端和 Rust 中的 RPC 服务器运行通过命令传递的示例

只需运行-integration{ws|memory|dyn}

运行多语言集成示例

使用 WebSockets 的 TypeScript RPC 客户端和 Rust RPC 服务器

只需运行-multilang

这些示例的代码可以在 examples/ 目录中找到。

用法

导入

[dependencies]
dcl-rpc = "*"

[build-dependencies]
prost-build = "*"
dcl-rpc = "*" # As a build depency as well because we need the codegen module for the code-generation of the defined RPC Service in the .proto

Protobuf

创建一个文件 app.proto 来定义将要使用的消息,例如

syntax = "proto3";
package decentraland.echo;

message Text {
  string say_something = 1;
}

service EchoService {
  rpc Hello(Text) returns (Text) {}
}

然后,定义一个 build.rs 文件来构建消息的类型

use std::io::Result;

fn main() -> Result<()> {
    // Tell Cargo that if the given file changes, to rerun this build script.
    println!("cargo:rerun-if-changed=src/echo.proto");

    let mut conf = prost_build::Config::new();
    conf.service_generator(Box::new(dcl_rpc::codegen::RPCServiceGenerator::new()));
    conf.compile_protos(&["src/echo.proto"], &["src"])?;
    Ok(())
}

build.rs 脚本会在你的 .proto 发生变化时运行。该脚本将在 OUT_DIR 中生成一个文件,其名称与 .proto 文件中的 package 字段相同(如果未声明,名称将为 '_.rs')。该文件将包含

  • .proto 中声明的所有消息作为 Rust 结构体。 *1
  • (#[cfg(feature = "server")]) 一个名为 {YOUR_RPC_SERVICE_NAME}Server 的特质,其中定义了服务端的方法。因此,您应该使用此特质来构建具有业务逻辑的实现。*2
  • (#[cfg(feature = "client")]) 一个名为 {YOUR_RPC_SERVICE_NAME}ClientDefinition<T: Transport + 'static>: ServiceClient<T> + Send + Sync + 'static 的特质,以及客户端的实现,命名为 {YOUR_RPC_SERVICE_NAME}Client。您可以在使用 RpcClient 时使用此自动生成的实现,通过在 load_module 函数中传递实现(实现了特质的结构体)作为泛型,它将负责请求您的服务的程序。但是,您也可以自行实现 {YOUR_RPC_SERVICE_NAME}ClientDefinition 特质,只要实现满足特质的和 RpcClient 的要求。*3
  • (#[cfg(feature = "server")]) 当创建 RpcServerPort 时,负责注册您声明的服务的一个结构体。您应该在创建 RpcServer 端口处理程序内部使用此结构体及其注册函数。*4

为了导入它们,您必须添加

include!(concat!(env!("OUT_DIR"), "/decentraland.echo.rs"));

此语句应添加到 src/lib.rs 中,以便将自动生成的代码部分包含到您的包中,否则它将把每个导入视为不同的类型。

服务器端

use dcl_rpc::{
    transports::web_socket::{WebSocketServer, WebSocketTransport},
    server::{RpcServer, RpcServerPort},
    service_module_definition::{Definition, ServiceModuleDefinition, CommonPayload}
};

use crate::{
    EchoServiceRegistration, // (*4)
};

// Define the IP and Port where the WebSocket Server will run
let ws_server = WebSocketServer::new("localhost:8080");
// Start listening on that IP:PORT
let mut connection_listener = ws_server.listen().await.unwrap();

// Add here any data that the server needs to solve the messages, for example db.
let ctx = MyExampleContext {
    hardcoded_database: create_db(),
};

let mut server = RpcServer::create(ctx);
server.set_handler(|port: &mut RpcServerPort<MyExampleContext>| {
  // The EchoServiceRegistration will be autogenerated, so you'll need to define the echo_service, which will have all the behaviors of your service. Following the example, it'll have the logic for the `hello` message.
  EchoServiceRegistration::register_service(port, echo_service::MyEchoService {})
});

// The WebSocket Server listens for incoming connections, when a connection is established, it creates a new WebSocketTransport with that connection and attaches it to the server event sender. The loop continues to listen for incoming connections and attach transports until it is stopped.
// and keep waiting for new ones
let server_events_sender = server.get_server_events_sender();
tokio::spawn(async move {
    while let Some(Ok(connection)) = connection_listener.recv().await {
        let transport = WebSocketTransport::new(connection);
        match server_events_sender.send_attach_transport(transport) {
            Ok(_) => {
                println!("> RpcServer > transport attached successfully");
            }
            Err(_) => {
                println!("> RpcServer > unable to attach transport");
                panic!()
            }
        }
    }
});

server.run().await;

为您服务实现特质

use crate::{
    MyExampleContext,
    EchoServiceServer, // (*2)
    Text // (*1) message
};

pub struct MyEchoService;

#[async_trait::async_trait]
impl EchoServiceServer<MyExampleContext> for MyEchoService {
    async fn hello(&self, request: Text, _ctx: Arc<MyExampleContext>) -> Text {
        request
    }
}

客户端

启动一个 WebSocket 客户端连接并向回声服务器发送一个 Hello World 消息。

use crate::{EchoServiceClient, RPCServiceClient} // (*3)
use dcl_rpc::{transports::web_socket::{WebSocketClient, WebSocketTransport}, client::RpcClient};
use ws_rust::Text;

let client_connection = WebSocketClient::connect("ws://127.0.0.1:8080")
    .await
    .unwrap();

let client_transport = WebSocketTransport::new(client_connection);
let mut client = RpcClient::new(client_transport).await.unwrap();
let port = client.create_port("echo").await.unwrap();

let module = port.load_module::<EchoServiceClient>("EchoService").await.unwrap();
let response = module.hello(Text { say_something: "Hello World!".to_string()}).await;

依赖项

~12–25MB
~363K SLoC