blob: 9391287466d93faf58534daea8f1cd302d67369e [file] [log] [blame] [view]
# Implement a FIDL server in Rust
## Prerequisites
This tutorial assumes that you are familiar with listing the FIDL Rust
bindings for a library as a dependency in GN and importing the bindings into
Rust code, which is covered in the [Rust FIDL crates][fidl-crates] tutorial.
## Overview
<!-- TODO(https://fxbug.dev/42136750) <<../../common/server/overview.md>> -->
This tutorial shows you how to implement a FIDL protocol
(`fuchsia.examples.Echo`) and run it on Fuchsia. This protocol has one method
of each kind:
* `EchoString` is a method with a response.
* `SendString` is a method without a response.
* `OnString` is an event.
```fidl
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/fuchsia.examples/echo.test.fidl" region_tag="echo" %}
```
For more on FIDL methods and messaging models, refer to the [FIDL concepts][concepts] page.
This document covers how to complete the following tasks:
* Implement a FIDL protocol.
* Build and run a package on Fuchsia.
* Serve a FIDL protocol.
The tutorial starts by creating a component that is served to a Fuchsia device
and run. Then, it gradually adds functionality to get the server up and running.
If you want to write the code yourself, delete the following directories:
```posix-terminal
rm -r examples/fidl/rust/server/*
```
## Create the component {#component}
To create a component:
1. Add a `main()` function to `examples/fidl/rust/server/src/main.rs`:
```rust
fn main() {
println!("Hello, world!");
}
```
1. Declare a target for the server in `examples/fidl/rust/server/BUILD.gn`:
```gn
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/BUILD.gn" region_tag="imports" %}
# Declare an executable for the server. This produces a binary with the
# specified output name that can run on Fuchsia.
rustc_binary("bin") {
output_name = "fidl_echo_rust_server"
edition = "2021"
sources = [ "src/main.rs" ]
}
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/BUILD.gn" region_tag="rest" %}
```
<!-- TODO(https://fxbug.dev/42136750) <<../../common/server/packages.md>> -->
To get the server component up and running, there are three targets that are
defined:
* The raw executable file for the server that is built to run on Fuchsia.
* A component that is set up to simply run the server executable,
which is described using the component's manifest file.
* The component is then put into a package, which is the unit of software
distribution on Fuchsia. In this case, the package just contains a
single component.
For more details on packages, components, and how to build them, refer to
the [Building components][building-components] page.
1. Add a component manifest in `examples/fidl/rust/server/meta/server.cml`:
Note: The binary name in the manifest must match the output name of the `executable`
defined in the previous step.
```json5
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/meta/server.cml" region_tag="example_snippet" %}
```
<!-- TODO(https://fxbug.dev/42136750) <<../../common/server/qemu.md>> -->
1. Add the server to your build configuration:
```posix-terminal
fx set core.x64 --with //examples/fidl/rust/server:echo-rust-server
```
Note: This build configuration assumes your device target is the emulator.
To run the example on a physical device, select the appropriate
[product configuration][products] for your hardware.
1. Build the Fuchsia image:
```posix-terminal
fx build
```
## Implement the server
First you'll implement the behavior of the Echo protocol. In Rust, this is expressed
as code that can handle the protocol's associated request stream type, which in this case is an
`EchoRequestStream`. This type is a stream of Echo requests, i.e. it implements
`futures::Stream<Item = Result<EchoRequest, fidl::Error>>`.
You'll implement `run_echo_server()` to handle the request stream,
which is an async function that handles incoming service requests.
It returns a future that completes once the client channel is closed.
### Add dependencies
1. Import the required dependencies:
```rust
// we'll use anyhow to propagate errors that occur when handling the request stream
use anyhow::{Context as _, Error};
// the server will need to handle an EchoRequestStream
use fidl_fuchsia_examples::{EchoRequest, EchoRequestStream};
// import the futures prelude, which includes things like the Future and Stream traits
use futures::prelude::*;
```
1. Add them as build dependencies to the `rustc_binary` target. The deps field should look like:
```gn
deps = [
"//examples/fidl/fuchsia.examples:fuchsia.examples_rust",
"//third_party/rust_crates:anyhow",
"//third_party/rust_crates:futures",
]
```
Note: The `rustdoc` for all Fuchsia crates can be found at
[https://fuchsia-docs.firebaseapp.com/rust/](https://fuchsia-docs.firebaseapp.com/rust/). You can
refer to this as necessary for documentation on the crates mentioned in this and following
tutorials.
### Define `run_echo_server`:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="impl" %}
```
The implementation consists of the following elements:
* The code converts the `fidl:Error`s from the request stream into `anyhow::Error`s, by attaching
context using the `.context()` method on each result:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="impl" highlight="3,4" %}
```
At this stage, the stream of `Result<EchoRequest, fidl::Error>` becomes a stream of
`Result<EchoRequest, anyhow::Error>`.
* Then, the function calls [try_for_each][try-for-each] on the resulting stream, which returns a
future. This method unwraps the `Result`s in the stream - any failures cause the future
to return immediately with that error, and the contents of any successes are passed to the
closure. Similarly, if the return value of the closure resolves to a failure, the resulting future
will return immediately with that error:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="impl" highlight="5,23" %}
```
* The contents of the closure handle incoming `EchoRequest`s by matching on them to
determine what kind of request they are:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="impl" highlight="6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22" %}
```
This implementation handles `EchoString` requests by echoing the input back, and it handles
`SendString` requests by sending an `OnString` event. Since `SendString` is a fire and forget
method, the request enum variant comes with a [control handle][control-handle], which can be used
to communicate back to the server.
In both cases, errors from sending messages back to the client are propagated by adding context
and using the `?` operator. If the end of the closure is reached successfully, then it returns
`Ok(())`.
* Finally, the server function `await`s the future returned from `try_for_each` to completion, which
will call the closure on every incoming request, and return when either all requests have been
handled or any error is encountered.
You can verify that the implementation is correct by running:
```posix-terminal
fx build
```
## Serve the protocol {#main}
Now that you've defined code to handle incoming requests, you'll need listen for incoming
connections to the Echo server. This is done by asking the
[component manager][component-manager] to expose the Echo protocol to other components. The
comopnent manager then routes any requests for the echo protocol to our server.
To fulfill these requests, the component manager requires the name of the protocol
as well as a handler that it should call when it has any incoming requests to
connect to a protocol matching the specified name.
### Add dependencies
1. Import the required dependencies:
```rust
// Import the Fuchsia async runtime in order to run the async main function
use fuchsia_async as fasync;
// ServiceFs is a filesystem used to connect clients to the Echo service
use fuchsia_component::server::ServiceFs;
```
1. Add them as build dependencies to the `rustc_binary` target. The full target looks like:
```gn
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/BUILD.gn" region_tag="server" %}
```
### Define the `main` function
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="main" highlight="1,2,20" %}
```
The main function is async since it consists of listening for incoming connections to the
Echo server. The [`fuchsia::main`][fuchsia-main] attribute tells the fuchsia async runtime to run
the `main` future to completion on a single thread.
`main` also returns `Result<(), Error>`. If an `Error` is returned from `main`
as a result of one of the `?` lines, the error will be `Debug` printed and
the program will return with a status code indicating failure.
### Initialize `ServiceFs`
Obtain an instance of `ServiceFs`, which represents a filesystem containing various services.
Since the server will be run singlethreaded, use
`ServiceFs::new_local()` instead of `ServiceFs::new()` (the latter is multithreaded capable).
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="main" highlight="4" %}
```
### Add the Echo FIDL service
Ask the component manager to expose the Echo FIDL service. There are two parts to this
function call:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="main" highlight="5" %}
```
* The component manager must know what to do with incoming connection requests. This is specified
by passing in a closure that accepts a `fidl::endpoints::RequestStream`, and returns some new
value with it. For example, passing in a closure of `|stream: EchoRequestStream| stream` would
be completely valid. A [common pattern](https://fuchsia-docs.firebaseapp.com/rust/fuchsia_component/server/struct.ServiceFsDir.html#method.add_fidl_service)
is to define an enum of the possible services offered by the server, in this example:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="enum" %}
```
and then passing the enum variant "constructor" as the closure. When there are multiple services
being offered, this results in a common return type (the `IncomingService` enum). The return
values of all `add_fidl_service` closures will become the elements in the `ServiceFs` stream when
[listening for incoming connections](#incoming).
* The component manager must also know *where* this service is going to be available.
Since this is an outgoing service (i.e. a service that is offered to other components),
the service must add a path inside `/svc` directory. `add_fidl_service` obtains this
path implicitly by taking the [`SERVICE_NAME`](https://fuchsia-docs.firebaseapp.com/rust/fidl/endpoints/trait.DiscoverableService.html)
associated with the closure input argument.
In this case, the closure argument (`IncomingService::Echo`) has an input argument of type
`EchoRequestStream`, which has an associated `SERVICE_NAME` of `"fuchsia.examples.Echo"`. So this
call is adding an entry at `/svc/fuchsia.examples.Echo`, and clients will need to search for a
service called `"fuchsia.examples.Echo"` to connect to this server.
### Serve the outgoing directory
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="main" highlight="7,8" %}
```
This call will bind the `ServiceFs` to the `DirectoryRequest` startup handle for the component, and
listen for incoming connection requests.
Note that since this removes the handle from the process's handle table,
this function can only be called once per process. If you wish to provide
a `ServiceFs` to a different channel, you can use the `serve_connection`
function.
This process is described further in
[Life of a protocol open][protocol-open].
### Listen for incoming connections {#incoming}
Run the `ServiceFs` to completion in order to listen for incoming connections:
```rust
{% includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/rust/server/src/main.rs" region_tag="main" highlight="10,11,12,13,14,15,16,17" %}
```
This runs the `ServiceFs` future, handling up to 10,000 incoming
requests concurrently. The closure passed to this call is the handler used for incoming requests -
`ServiceFs` will first matching incoming connections to the closure provided to `add_fidl_service`,
then call the handler on the result (which is an `IncomingService`). The handler takes
the `IncomingService`, and calls `run_echo_server` on the inner request stream to handle incoming
Echo requests.
There are two types of requests being handled here. The stream of requests handled by the
`ServiceFs` consists of requests to connect to an Echo server (i.e. each client will make this type
of request once when connecting to the server), whereas the stream of requests handled by
`run_echo_server` are requests on the Echo protocol (i.e. each client may make any number of
`EchoString` or `SendString` requests to the server). Many clients can request to connect to the
Echo server at the same time, so this stream of requests is handled concurrently. However, all
requests for a single client happen in sequence so there is no benefit to processing requests
concurrently.
## Test the server
Rebuild:
```posix-terminal
fx build
```
Then run the server component:
```posix-terminal
ffx component run /core/ffx-laboratory:echo_server fuchsia-pkg://fuchsia.com/echo-rust-server#meta/echo_server.cm
```
Note: Components are resolved using their [component URL][glossary.component-url],
which is determined with the [`fuchsia-pkg://`][glossary.fuchsia-pkg-url] scheme.
You should see output similar to the following in the device logs (`ffx log`):
```none {:.devsite-disable-click-to-copy}
[ffx-laboratory:echo_server][][I] Listening for incoming connections...
```
The server is now running and waiting for incoming requests.
The next step will be to write a client that sends `Echo` protocol requests.
For now, you can simply terminate the server component:
```posix-terminal
ffx component destroy /core/ffx-laboratory:echo_server
```
Note: Component instances are referenced by their
[component moniker][glossary.moniker], which is determined by their location in
the [component instance tree][glossary.component-instance-tree]
<!-- xrefs -->
[glossary.component-instance-tree]: /docs/glossary/README.md#component-instance-tree
[glossary.component-url]: /docs/glossary/README.md#component-url
[glossary.fuchsia-pkg-url]: /docs/glossary/README.md#fuchsia-pkg-url
[glossary.moniker]: /docs/glossary/README.md#moniker
[concepts]: /docs/concepts/fidl/overview.md
[fidl-crates]: /docs/development/languages/fidl/tutorials/rust/basics/using-fidl.md
[fuchsia-main]: /src/lib/fuchsia/macro/src/lib.rs#23
[building-components]: /docs/development/components/build.md
[products]: /docs/development/build/build_system/boards_and_products.md
[control-handle]: /docs/reference/fidl/bindings/rust-bindings.md#protocol-control-handle
[declaring-fidl]: /docs/development/languages/fidl/tutorials/fidl.md
[component-manager]: /docs/concepts/components/v2/component_manager.md
[protocol-open]: /docs/concepts/components/v2/capabilities/life_of_a_protocol_open.md#binding_to_a_component_and_sending_a_protocol_channel
[compiling-fidl]: /docs/development/languages/fidl/tutorials/fidl.md
[async-loop]: /zircon/system/ulib/async-loop/include/lib/async-loop/cpp/loop.h
[overview]: /docs/development/languages/fidl/tutorials/overview.md
[try-for-each]: https://docs.rs/futures/0.3.5/futures/stream/trait.TryStreamExt.html#method.try_for_each