## Prerequisites

In this tutorial, you'll learn about the request pipelining pattern and its
benefits. This tutorial expects you to already be familiar with the basics
of writing and running FIDL clients and servers, which is covered in the
[Rust getting started tutorials](https://fuchsia.dev/docs/development/languages/fidl/tutorials/rust/README).

## Overview

A common aspect of using FIDL on Fuchsia is passing protocols themselves across
protocols. Many FIDL messages include either the client end or
the server end of a channel, where the channel is used to communicate over a
different FIDL protocol. In this case, client end means that the remote end of the
channel implements the specified protocol, whereas server end means that the
remote end is making requests for the specified protocol. An alternate set of
terms for client end and server end are protocol and protocol request.

This tutorial covers:

- The usage of these client and server ends, both in FIDL and in the Rust FIDL bindings.
- The request pipelining pattern and its benefits.

The full example code for this tutorial is located at
[`//examples/fidl/rust/request_pipelining`](https://cs.opensource.google/fuchsia/fuchsia/+/main:/examples/fidl/rust/request_pipelining).

### The FIDL protocol

This tutorial implements the `EchoLauncher` protocol from the
[fuchsia.examples library](https://cs.opensource.google/fuchsia/fuchsia/+/main:/examples/fidl/fuchsia.examples/):

    @discoverable
    closed protocol EchoLauncher {
        strict GetEcho(struct {
            echo_prefix string:MAX_STRING_LENGTH;
        }) -> (resource struct {
            response client_end:Echo;
        });
        strict GetEchoPipelined(resource struct {
            echo_prefix string:MAX_STRING_LENGTH;
            request server_end:Echo;
        });
    };

This is a protocol that lets clients retrieve an instance of the `Echo`
protocol. Clients can specify a prefix, and the resulting `Echo` instance
adds that prefix to every response.

There are two methods that can be used to accomplish this:

- `GetEcho`: Takes the prefix as a request, and responds with the *client end* of a channel connected to an implementation of the `Echo` protocol. After receiving the client end in the response, the client can start making requests on the `Echo` protocol using the client end.
- `GetEchoPipelined`: Takes the prefix and the *server end* of a channel as a request, and binds an implementation of `Echo` to it. The client that made the request is assumed to already hold the client end, and will start making `Echo` requests on that channel after calling `GetEchoPipeliend`.

As the name suggests, the latter uses a pattern called protocol request
pipelining, and is the preferred approach. This tutorial implements both
approaches.

## Implement the server

### Implement the Echo protocol

This implementation of `Echo` allows specifying a prefix in order to
distinguish between the different instances of `Echo` servers:

    // An Echo implementation that adds a prefix to every response
    async fn run_echo_server(stream: EchoRequestStream, prefix: &str) -> Result<(), Error> {
        stream
            .map(|result| result.context("failed request"))
            .try_for_each(|request| async move {
                match request {
                    // The SendString request is not used in this example, so just
                    // ignore it
                    EchoRequest::SendString { value: _, control_handle: _ } => {}
                    EchoRequest::EchoString { value, responder } => {
                        println!("Got echo request for prefix {}", prefix);
                        let response = format!("{}: {}", prefix, value);
                        responder.send(&response).context("error sending response")?;
                    }
                }
                Ok(())
            })
            .await
    }

The `SendString` handler is empty as the client just uses `EchoString`.

### Implement the EchoLauncher protocol

The general structure is similar to the `Echo` implementation, but one difference is that the
`try_for_each_concurrent` is used instead of `try_for_each`. The client in this example launches
two instances of `Echo`, so, using the concurrent version allows the two calls to
`run_echo_server` to be run concurrently:

    // The EchoLauncher implementation that launches Echo servers with the specified
    // prefix
    async fn run_echo_launcher_server(stream: EchoLauncherRequestStream) -> Result<(), Error> {
        // Currently the client only connects at most two Echo clients for each EchoLauncher
        stream
            .map(|result| result.context("request error"))
    .try_for_each_concurrent(2, \|request\| async move {
                let (echo_prefix, server_end) = match request {
                    // In the non pipelined case, we need to initialize the
                    // communication channel ourselves
                    EchoLauncherRequest::GetEcho { echo_prefix, responder } => {
                        println!("Got non pipelined request");
                        let (client_end, server_end) = create_endpoints::<EchoMarker>();
                        responder.send(client_end)?;
                        (echo_prefix, server_end)
                    }
                    // In the pipelined case, the client is responsible for
                    // initializing the channel, and passes the server its end of
                    // the channel
                    EchoLauncherRequest::GetEchoPipelined {
                        echo_prefix,
                        request,
                        control_handle: _,
                    } => {
                        println!("Got pipelined request");
                        (echo_prefix, request)
                    }
                };
                // Run the Echo server with the specified prefix
                run_echo_server(server_end.into_stream(), &echo_prefix).await
    })
            .await
    }

Both of the `EchoLauncher` methods are handled by calling `run_echo_server` on the server end of
the channel. The difference is that in `GetEcho`, the server is responsible for initializing the
channel - it uses one end as the server end and sends the other end back to the client. In
`GetEchoPipelined`, the server end is provided as part of the request, so no additional work needs
to be done by the server, and no response is necessary.

    // The EchoLauncher implementation that launches Echo servers with the specified
    // prefix
    async fn run_echo_launcher_server(stream: EchoLauncherRequestStream) -> Result<(), Error> {
        // Currently the client only connects at most two Echo clients for each EchoLauncher
        stream
            .map(|result| result.context("request error"))
            .try_for_each_concurrent(2, |request| async move {
    let (echo_prefix, server_end) = match request {
    // In the non pipelined case, we need to initialize the
    // communication channel ourselves
    EchoLauncherRequest::GetEcho { echo_prefix, responder } =\> {
    println!("Got non pipelined request");
    let (client_end, server_end) = create_endpoints::\<EchoMarker\>();
    responder.send(client_end)?;
    (echo_prefix, server_end)
    }
    // In the pipelined case, the client is responsible for
    // initializing the channel, and passes the server its end of
    // the channel
    EchoLauncherRequest::GetEchoPipelined {
    echo_prefix,
    request,
    control_handle: _,
    } =\> {
    println!("Got pipelined request");
    (echo_prefix, request)
    }
    };
    // Run the Echo server with the specified prefix
    run_echo_server(server_end.into_stream(), \&echo_prefix).await
            })
            .await
    }

### Serve the EchoLauncher protocol

The main loop should is the same as in the
[server tutorial](https://fuchsia.dev/docs/development/languages/fidl/tutorials/rust/basics/server#main) but serves an `EchoLauncher` instead of `Echo`.

    enum IncomingService {
        EchoLauncher(EchoLauncherRequestStream),
    }

    #[fuchsia::main]
    async fn main() -> Result<(), Error> {
        let mut fs = ServiceFs::new_local();
        fs.dir("svc").add_fidl_service(IncomingService::EchoLauncher);
        fs.take_and_serve_directory_handle()?;

        const MAX_CONCURRENT: usize = 1000;
        let fut = fs.for_each_concurrent(MAX_CONCURRENT, |IncomingService::EchoLauncher(stream)| {
            run_echo_launcher_server(stream).unwrap_or_else(|e| println!("{:?}", e))
        });

        println!("Running echo launcher server");
        fut.await;
        Ok(())
    }

## Build the server

Optionally, to check that things are correct, try building the server:

1. Configure your GN build to include the server:

       fx set core.x64 --with //examples/fidl/rust/request_pipelining/server:echo-server

2. Build the Fuchsia image:

       fx build

## Implement the client

> [!NOTE]
> **Note:** Most of the client code in `client/src/main.rs` should be familiar after having followed the [client tutorial](https://fuchsia.dev/docs/development/languages/fidl/tutorials/rust/basics/client). The different parts of the code are covered in more detail here.

After connecting to the `EchoLauncher` server, the client
code connects to one instance of `Echo` using `GetEcho` and another using
`GetEchoPipelined` and then makes an `EchoString` request on each instance.

This is the non-pipelined code:

    #[fuchsia::main]
    async fn main() -> Result<(), Error> {
        let echo_launcher =
            connect_to_protocol::<EchoLauncherMarker>().context("Failed to connect to echo service")?;

    // Create a future that obtains an Echo protocol using the non-pipelined
    // GetEcho method
    let non_pipelined_fut = async {
    let client_end = echo_launcher.get_echo("not pipelined").await?;
    // "Upgrade" the client end in the response into an Echo proxy, and
    // make an EchoString request on it
    let proxy = client_end.into_proxy();
    proxy.echo_string("hello").map_ok(\|val\| println!("Got echo response {}", val)).await
    };

        // Create a future that obtains an Echo protocol using the pipelined GetEcho
        // method
        let (proxy, server_end) = create_proxy::<EchoMarker>();
        echo_launcher.get_echo_pipelined("pipelined", server_end)?;
        // We can make a request to the server right after sending the pipelined request
        let pipelined_fut =
            proxy.echo_string("hello").map_ok(|val| println!("Got echo response {}", val));

        // Run the two futures to completion
        let (non_pipelined_result, pipelined_result) = join!(non_pipelined_fut, pipelined_fut);
        pipelined_result?;
        non_pipelined_result?;
        Ok(())
    }

This code chains together two futures. First, it makes the `GetEcho` request to the client. It then
takes the result of that future, and then uses it to create a client object (the `proxy`), calls
`EchoString`, and then blocks on the result using `await`.

Despite having to initialize the channel first, the pipelined code is much simpler:

    #[fuchsia::main]
    async fn main() -> Result<(), Error> {
        let echo_launcher =
            connect_to_protocol::<EchoLauncherMarker>().context("Failed to connect to echo service")?;

        // Create a future that obtains an Echo protocol using the non-pipelined
        // GetEcho method
        let non_pipelined_fut = async {
            let client_end = echo_launcher.get_echo("not pipelined").await?;
            // "Upgrade" the client end in the response into an Echo proxy, and
            // make an EchoString request on it
            let proxy = client_end.into_proxy();
            proxy.echo_string("hello").map_ok(|val| println!("Got echo response {}", val)).await
        };

    // Create a future that obtains an Echo protocol using the pipelined GetEcho
    // method
    let (proxy, server_end) = create_proxy::\<EchoMarker\>();
    echo_launcher.get_echo_pipelined("pipelined", server_end)?;
    // We can make a request to the server right after sending the pipelined request
    let pipelined_fut =
    proxy.echo_string("hello").map_ok(\|val\| println!("Got echo response {}", val));

        // Run the two futures to completion
        let (non_pipelined_result, pipelined_result) = join!(non_pipelined_fut, pipelined_fut);
        pipelined_result?;
        non_pipelined_result?;
        Ok(())
    }

`create_proxy` is used, which is a shortcut for creating the two ends of a channel and converting
one end into a proxy. After the call to `GetEchoPipelined`, the client can immediately make the
`EchoString` request.

Finally, the two futures corresponding to the non-pipelined and pipelined calls are run to
completion concurrently, to see which one completes first:

    #[fuchsia::main]
    async fn main() -> Result<(), Error> {
        let echo_launcher =
            connect_to_protocol::<EchoLauncherMarker>().context("Failed to connect to echo service")?;

        // Create a future that obtains an Echo protocol using the non-pipelined
        // GetEcho method
        let non_pipelined_fut = async {
            let client_end = echo_launcher.get_echo("not pipelined").await?;
            // "Upgrade" the client end in the response into an Echo proxy, and
            // make an EchoString request on it
            let proxy = client_end.into_proxy();
            proxy.echo_string("hello").map_ok(|val| println!("Got echo response {}", val)).await
        };

        // Create a future that obtains an Echo protocol using the pipelined GetEcho
        // method
        let (proxy, server_end) = create_proxy::<EchoMarker>();
        echo_launcher.get_echo_pipelined("pipelined", server_end)?;
        // We can make a request to the server right after sending the pipelined request
        let pipelined_fut =
            proxy.echo_string("hello").map_ok(|val| println!("Got echo response {}", val));

    // Run the two futures to completion
    let (non_pipelined_result, pipelined_result) = join!(non_pipelined_fut, pipelined_fut);
    pipelined_result?;
    non_pipelined_result?;
    Ok(())
    }

## Build the client

Optionally, to check that things are correct, try building the client:

1. Configure your GN build to include the server:

       fx set core.x64 --with //examples/fidl/rust/request_pipelining/client:echo-client

2. Build the Fuchsia image:

       fx build

## Run the example code

For this tutorial, a


[](https://fuchsia.dev/docs/glossary#realm)


component is
provided to declare the appropriate capabilities and routes for
`fuchsia.examples.Echo` and `fuchsia.examples.EchoLauncher`.

> [!NOTE]
> **Note:** You can explore the full source for the realm component at [`//examples/fidl/echo-realm`](https://cs.opensource.google/fuchsia/fuchsia/+/main:/examples/fidl/echo-realm)

1. Configure your build to include the provided package that includes the
   echo realm, server, and client:

       fx set core.x64 --with //examples/fidl/rust:echo-launcher-rust

2. Build the Fuchsia image:

       fx build

3. Run the `echo_realm` component. This creates the client and server component
   instances and routes the capabilities:

       ffx component run /core/ffx-laboratory:echo_realm fuchsia-pkg://fuchsia.com/echo-launcher-rust#meta/echo_realm.cm

4. Start the `echo_client` instance:

       ffx component start /core/ffx-laboratory:echo_realm/echo_client

The server component starts when the client attempts to connect to the
`EchoLauncher` protocol. You should see output similar to the following
in the device logs (`ffx log`):

    [echo_server][][I] Running echo launcher server
    [echo_server][][I] Got pipelined request
    [echo_server][][I] Got echo request for prefix pipelined
    [echo_server][][I] Got non pipelined request
    [echo_client][][I] Got echo response pipelined: hello
    [echo_server][][I] Got echo request for prefix not pipelined
    [echo_client][][I] Got echo response not pipelined: hello

Based on the print order, you can see that the pipelined case is faster. The
echo response for the pipelined case arrives first, even though the non
pipelined request is sent first, since request pipelining saves a roundtrip
between the client and server. Request pipelining also simplifies the code.

For further reading about protocol request pipelining, including how to handle
protocol requests that may fail, see the [FIDL API rubric](https://fuchsia.dev/docs/development/api/fidl#request-pipelining).

Terminate the realm component to stop execution and clean up the component
instances:

    ffx component destroy /core/ffx-laboratory:echo_realm

[](https://issues.fuchsia.dev/issues/new?component=1439230&template=1881640&description=Issue+on:+https://fuchsia.dev/docs/development/languages/fidl/tutorials/rust/topics/request-pipelining.md "Report a bug or suggest improvements on this page") [](https://cs.opensource.google/fuchsia/fuchsia/+/main:docs/development/languages/fidl/tutorials/rust/topics/request-pipelining.md "View the source code of this page") [](https://ci.android.com/edit?repo=fuchsia/fuchsia/main&file=docs/development/languages/fidl/tutorials/rust/topics/request-pipelining.md&cloneRepo=fuchsia/fuchsia:refs/heads/main&fullPath=fuchsia/docs/development/languages/fidl/tutorials/rust/topics/request-pipelining.md "Edit the source code of this page")