dcl-rpc

Build License Cargo Documentation

The Rust implementation of Decentraland RPC. At Decentraland, we have our own implementation of RPC for communications between the different services.

Currently, there are other implementations:

Requirements

Install Just for commands

bash cargo install just

Examples

Run the integration example

RPC Client in Rust and RPC Server in Rust running Websocket transport example, Memory Transport example and example using different types of transports

just run-integration

Run the integration example with an specific transport

RPC Client in Rust and RPC Server in Rust running the example passed to the command

just run-integration {ws|memory|dyn}

Run the multi language integration example

RPC Client in Typescript and RPC Server in Rust using WebSockets

just run-multilang

You can find the code for these examples in the examples/ directory.

Usage

Import

```toml [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

Create a file app.proto to define the messages that will be used, for example:

```proto syntax = "proto3"; package decentraland.echo;

message Text { string say_something = 1; }

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

Then, define a build.rs file to build the types of the message:

```rust 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(())

} ```

The build.rs script runs every time that your .proto changes. The script will generate a file in the OUT_DIR, named as the package field in the .proto file (if it's not declared, the name will be '_.rs'). This file will include:

To import them you must add:

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

This statement should be added to the src/lib.rs in order to make the auto-generated code part of your crate, otherwise it will treat every include as different types.

Server Side

```rust use dclrpc::{ transports::websocket::{WebSocketServer, WebSocketTransport}, server::{RpcServer, RpcServerPort}, servicemoduledefinition::{Definition, ServiceModuleDefinition, CommonPayload} };

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

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

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

let mut server = RpcServer::create(ctx); server.sethandler(|port: &mut RpcServerPort| { // The EchoServiceRegistration will be autogenerated, so you'll need to define the echoservice, which will have all the behaviors of your service. Following the example, it'll have the logic for the hello message. EchoServiceRegistration::registerservice(port, echoservice::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 servereventssender = server.getservereventssender(); tokio::spawn(async move { while let Some(Ok(connection)) = connectionlistener.recv().await { let transport = WebSocketTransport::new(connection); match servereventssender.sendattachtransport(transport) { Ok() => { println!("> RpcServer > transport attached successfully"); } Err() => { println!("> RpcServer > unable to attach transport"); panic!() } } } });

server.run().await; ```

Implement the trait for your service

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

pub struct MyEchoService;

[asynctrait::asynctrait]

impl EchoServiceServer for MyEchoService { async fn hello(&self, request: Text, _ctx: Arc) -> Text { request } } ```

Client Side

Initiate a WebSocket Client Connection and send a Hello World message to the echo server.

```rust use crate::{EchoServiceClient, RPCServiceClient} // (*3) use dclrpc::{transports::websocket::{WebSocketClient, WebSocketTransport}, client::RpcClient}; use ws_rust::Text;

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

let clienttransport = WebSocketTransport::new(clientconnection); let mut client = RpcClient::new(clienttransport).await.unwrap(); let port = client.createport("echo").await.unwrap();

let module = port.loadmodule::("EchoService").await.unwrap(); let response = module.hello(Text { saysomething: "Hello World!".to_string()}).await; ```