Crates.io | dcl-rpc |
lib.rs | dcl-rpc |
version | 2.3.5 |
source | src |
created_at | 2023-02-24 17:39:44.467604 |
updated_at | 2023-05-24 20:59:22.514319 |
description | Decentraland RPC Implementation |
homepage | |
repository | https://github.com/decentraland/rpc-rust |
max_upload_size | |
id | 793806 |
size | 180,183 |
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:
cargo install just
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
RPC Client in Rust and RPC Server in Rust running the example passed to the command
just run-integration {ws|memory|dyn}
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.
[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
Create a file app.proto
to define the messages that will be used, for example:
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:
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:
.proto
as Rust structs. *1#[cfg(feature = "server")]
) A trait, named {YOUR_RPC_SERVICE_NAME}Server: Send + Sync + 'static
, with the methods defined in your service for the server side. So you should use this trait to build an implementation with the business logic. *2#[cfg(feature = "client")]
) A trait, named {YOUR_RPC_SERVICE_NAME}ClientDefinition<T: Transport + 'static>: ServiceClient<T> + Send + Sync + 'static
, and an implementation of it for the client side, named {YOUR_RPC_SERVICE_NAME}Client
. You could use this auto-generated implementation when using the RpcClient
passing the implementation (struct with the trait implemented) as a generic in the load_module
function, which it'll be in charge of requesting the procedures of your service. But you could also have your own implementation of the {YOUR_RPC_SERVICE_NAME}ClientDefinition
trait, as long as the implementations meets with trait's and RpcClient
requirements . *3#[cfg(feature = "server")]
) A struct in charge of registering your declared service when a RpcServerPort
is created. You should use this struct and its registering function inside the RpcServer
port creation handler. *4To import them you must add:
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.
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;
Implement the trait for your service
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
}
}
Initiate a WebSocket Client Connection and send a Hello World message to the echo server.
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://localhost: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;