Server

The server API look straightforward and fairly close to that of golang's net/rpc package. First we should choose an async runtime and enable the "server" feature flag in our Cargo.toml file. Here, we will choose the tokio runtime be enabling the "tokio_runtime" feature flag.

[dependencies]
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net"] }
toy-rpc = { version = "0.7.5", features = ["tokio_runtime", "server"] }

The next step is then to build the service instances and register the service instances onto a server which will be demonstrated below.

You can definitely register services that are defined with #[export_impl] and #[export_trait] on the same server. However, for simplicity and consistency with the service definition chapter, in the discussions below, we are going to have separate examples. If you need an example where services defined with #[export_impl] and #[export_trait] are all registered on the same server, please refer to this.

Example with #[export_impl]

Let's just remind ourselves that in this example the service definition and implementation are located in the same file/project, and the file structure is as follows

./src
├── /bin
│   ├── server.rs
│   ├── client.rs
└── lib.rs

Using the service we have defined and implemented in the previous chapter, we are going instantiate those services and register those instances in src/bin/server.rs.

// src/bin/server.rs
use tokio::net::TcpListener;
use std::sync::Arc;
use toy_rpc::Server;

// Suppose the name of this crate is "example"
// Let's import the two services 
use example::{Foo, Bar}; 

#[tokio::main]
async fn main() {
    let addr = "127.0.0.1:23333";
    let foo = Arc::new(Foo { }); // instance of "Foo" service
    let bar = Arc::new(Bar { }); // instance of "Bar" service

    // Register the services onto a server
    let server = Server::builder()
        .register(foo) // register service instance with default name "Foo"
        .register(bar) // register service instance with default name "Bar"
        .build(); // build the server

    // Open a TcpListener for incoming connections
    let listener = TcpListener::bind(addr).await.unwrap();

    // Start our server and accept incoming connections and requests
    server.accept(listener).await.unwrap();
}

Example with #[export_trait] and #[export_trait_impl]

Now we are back with the example with #[export_trait] and #[export_trait_impl], let's just remind ourselves that we have three separate crates where the service is defined in "example-service" and will be implemented in "example-server" as shown below.

We will continue to use the tokio runtime so that we don't need to change our Cargo.toml file, but because we will be implementing the service below, we will need to add async-trait and our service definition crate into our dependencies.

[dependencies]
async-trait = "0.1.50"
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net"] }
toy-rpc = { version = "0.7.5", features = ["tokio_runtime", "server"] }

# our service definition 
example-service = { version = "0.1.0", path = "../example-service" }

Now, let's implement the service and start the server

// src/main.rs

use tokio::net::TcpListener;
use std::sync::Arc;
use async_trait::async_trait;
use toy_rpc::Server;
use toy_rpc::macros::export_trait_impl;

// Import our service definition
// Make sure you import everything to include the auto-generated helper 
// traits that allow convenient service registration
use example_service::*;

struct Abacus { }

#[async_trait]
#[export_trait_impl] // The default service name will be "Arith"
impl Arith for Abacus {
    // Please note that you do NOT need to mark methods with
    // `#[export_method]` another time

    async fn add(&self, args: (i32, i32)) -> Result<i32, String> {
        Ok(args.0 + args.1)
    }

    async fn subtract(&self, args: (i32, i32)) -> Result<i32, String> {
        Ok(args.0 - args.1)
    }

    fn say_hi(&self) {
        println!("hi");
    }
}

#[tokio::main]
async fn main() {
    let addr = "127.0.0.1:23333";
    let arith = Arc::new(Abacus{}); // create an instance of the `Arith` service
    let listener = TcpListener::bind(addr).await.unwrap();
    let server = Server::builder()
        .register(arith) // register service with default name "Arith"
        .build();

    println!("Starting server at {}", &addr);
    server.accept(listener).await.unwrap()
}