Client - toy-rpc Book

# Client

The client side usage should feel fairly close to that of the golang's `net/rpc` package as well except some changes that makes the client API more rusty and async.

To connect a client to a "raw" TCP server, you should use the `dial` function; to connect to a HTTP server, you should then use the `dial_http` function. Once you have a connected client, you can then use the `call_blocking` and `call` methods to access the RPC functions on the server. The `#[export_impl]` and `#[export_trait]` attribute macros also generates client stub functions that allows the client to conveniently access the RPC functions without worrying about typing the wrong service or method name.

# Connecting to TCP server / HTTP server

For the examples below, we will assume running with the `tokio` runtime.

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

The example below shows how to connect to a TCP server

    use toy_rpc::Client;

    #[tokio::main]
    async fn main() {
        let client = Client::dial("127.0.0.1:23333").await
            .expect("Failed to connect to server");
    }

Connecting to the HTTP server looks very similar with some minor changes. The example below assumes that we are trying to connect to one of the HTTP servers (all three HTTP integration examples have the RPC server serving at `"ws://127.0.0.1:23333/rpc/"`). Please note that there is a "rpc" appended to the end of the path by the server integration methods, but this is automatically handled by the `Client::dial_http` method so you don't need to worry about that.

    use toy_rpc::Client;

    #[tokio::main]
    async fn main() {
        let client = Client::dial_http("ws://127.0.0.1:23333/rpc/").await
            .expect("Failed to connect to server");
    }

# Accessing RPC services

## `call_blocking(...)` and `call(...)`

There are two methods available for accessing the RPC services and methods. The `call_blocking` method blocks the execution until the response is received, and the `call` method is the asynchronous version where the execution will yield to other tasks scheduled by the runtime while waiting for the response. Cancellation is also supported on the `call` method, which is discussed with more details in the next chapter.

(Support of timeout is still work-in-progress. The book will be updated once the feature is implemented.)

## Generated client stub functions

The generated client stub functions internally uses the `call(...)` method and are thus async. The client stub functions consist of two steps. The first step is to access your service, and the second step is to access the method defined in that particular service. The method in the first step is always the name of the service but in snake case. For example, if you have a service `struct FooBar { }`, then the client method you use to access the service will be `foo_bar()`. The client method you use to access the method is identical to the method definition in the RPC service. For example, if an RPC method is defined as `fn add(&self, args(i32, i32)) -> Result<i32, String>;`, then the client method you use would be `client.bar().add((3i32, 4i32)).await;`

## Examples

We will continue the `#[export_impl]` example and the [`#[export_trait]` and `#[export_trait_impl]`] example to demonstrate how to access RPC service on the server. The methods you will use to access the RPC service are the same for a TCP connection and a HTTP connection, and for simplicity, all the examples below will assume a TCP connection. For more examples on use with HTTP connections, please checkout the GitHub examples.

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

Since the service is defined and implemented in `src/lib.rs` from the previous chapter, we are going to include everything in the `src/lib.rs` file to allow us use the generated client stub functions in our `src/bin/client.rs`.

    use toy_rpc::Client;
    use toy_rpc::Error;

    // include everything from the lib.rs file
    // assuming the name of the crate is "example"
    use example::*;

    #[tokio::main]
    async fn main() {
        let client = Client::dial("127.0.0.1:23333").await
            .expect("Failed to connect to the server");

        // Access the remote `exported_method` method of `Foo` service in a blocking manner
        let result: Result<String, Error> = client.call_blocking("Foo.exported_method", ());
        println!("{:?}", result);

        // Access the remote `add` method of `Bar` service in an asynchronous manner
        let result: Result<i32, Error> = client.call("Bar.add", (3i32, 4i32)).await;
        println!("{:?}", result);

        // You can also use the generated client stub functions
        // Access the remote `exported_method` method of `Foo` service
        // using the generated client stub functions
        let result = client
            .foo() // access `Foo` service
            .exported_method(()) // access `exported_method` of the `Foo` service
            .await;
        println!("{:?}", result);

        // Access the remote `add` method of `Bar` service
        // using the generated client stub functions
        let result = client
            .bar()
            .add((3, 4))
            .await;
        println!("{:?}", result);
    }

### `#[export_trait]` and `#[export_trait_impl]`

Again, let's just remind ourselves that the service in this example is defined in a separate crate `example-service` and the service is implemented and served by `example-server` crate. What we will be doing below is to demonstrate the client, which, not surprisingly, look pretty much the same as the example above.

Because the service definition resides in a separate crate, we will need to import that crate as well.

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

    # import our service definition, assuming we have this definition at "../example-service"
    example-service = { version = "0.1.0", path = "../example-service" }

    use toy_rpc::{Client, Error};

    // import everything to use the generated client stub functions
    use example_service::*;

    #[tokio::main]
    async fn main() {
        let client = Client::dial("127.0.0.1:23333").await
            .expect("Failed to connect to the server");

        // Access the remote method `add` of service `Arith` in a blocking manner
        let result: Result<i32, Error> = client.call_blocking("Arith.add", (3i32, 4i32));
        println!("{:?}", result);

        // Access the remote method `subtract` of service `Arith` in an asynchronous manner
        let result: Result<i32, Error> = client.call("Arith.subtract", (9i32, 6i32)).await;
        println!("{:?}", result);

        // Let's use the generated client stub functions
        let result = client
            .arith() // access `Arith` service
            .add((3i32, 4i32)) // access `add` method
            .await;
        println!("{:?}", result);

        let result = client
            .arith() // access `Arith` service
            .subtract((9i32, 6i32)) // access `subtract` method
            .await;
        println!("{:?}", result);
    }