Server - toy-rpc Book

# 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()
    }