mirror of
https://github.com/OMGeeky/tarpc.git
synced 2025-12-27 14:52:18 +01:00
160 lines
6.2 KiB
Markdown
160 lines
6.2 KiB
Markdown
[![Crates.io][crates-badge]][crates-url]
|
|
[![MIT licensed][mit-badge]][mit-url]
|
|
[![Build status][gh-actions-badge]][gh-actions-url]
|
|
[![Discord chat][discord-badge]][discord-url]
|
|
|
|
[crates-badge]: https://img.shields.io/crates/v/tarpc.svg
|
|
[crates-url]: https://crates.io/crates/tarpc
|
|
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
|
[mit-url]: LICENSE
|
|
[gh-actions-badge]: https://github.com/google/tarpc/workflows/Continuous%20integration/badge.svg
|
|
[gh-actions-url]: https://github.com/google/tarpc/actions?query=workflow%3A%22Continuous+integration%22
|
|
[discord-badge]: https://img.shields.io/discord/647529123996237854.svg?logo=discord&style=flat-square
|
|
[discord-url]: https://discord.gg/gXwpdSt
|
|
|
|
# tarpc
|
|
|
|
*Disclaimer*: This is not an official Google product.
|
|
|
|
tarpc is an RPC framework for rust with a focus on ease of use. Defining a
|
|
service can be done in just a few lines of code, and most of the boilerplate of
|
|
writing a server is taken care of for you.
|
|
|
|
[Documentation](https://docs.rs/crate/tarpc/)
|
|
|
|
### What is an RPC framework?
|
|
"RPC" stands for "Remote Procedure Call," a function call where the work of
|
|
producing the return value is being done somewhere else. When an rpc function is
|
|
invoked, behind the scenes the function contacts some other process somewhere
|
|
and asks them to evaluate the function instead. The original function then
|
|
returns the value produced by the other process.
|
|
|
|
RPC frameworks are a fundamental building block of most microservices-oriented
|
|
architectures. Two well-known ones are [gRPC](http://www.grpc.io) and
|
|
[Cap'n Proto](https://capnproto.org/).
|
|
|
|
tarpc differentiates itself from other RPC frameworks by defining the schema in code,
|
|
rather than in a separate language such as .proto. This means there's no separate compilation
|
|
process, and no context switching between different languages.
|
|
|
|
Some other features of tarpc:
|
|
- Pluggable transport: any type impling `Stream<Item = Request> + Sink<Response>` can be
|
|
used as a transport to connect the client and server.
|
|
- `Send` optional: if the transport doesn't require it, neither does tarpc!
|
|
- Cascading cancellation: dropping a request will send a cancellation message to the server.
|
|
The server will cease any unfinished work on the request, subsequently cancelling any of its
|
|
own requests, repeating for the entire chain of transitive dependencies.
|
|
- Configurable deadlines and deadline propagation: request deadlines default to 10s if
|
|
unspecified. The server will automatically cease work when the deadline has passed. Any
|
|
requests sent by the server that use the request context will propagate the request deadline.
|
|
For example, if a server is handling a request with a 10s deadline, does 2s of work, then
|
|
sends a request to another server, that server will see an 8s deadline.
|
|
- Serde serialization: enabling the `serde1` Cargo feature will make service requests and
|
|
responses `Serialize + Deserialize`. It's entirely optional, though: in-memory transports can
|
|
be used, as well, so the price of serialization doesn't have to be paid when it's not needed.
|
|
|
|
### Usage
|
|
Add to your `Cargo.toml` dependencies:
|
|
|
|
```toml
|
|
tarpc = { version = "0.21.0", features = ["full"] }
|
|
```
|
|
|
|
The `tarpc::service` attribute expands to a collection of items that form an rpc service.
|
|
These generated types make it easy and ergonomic to write servers with less boilerplate.
|
|
Simply implement the generated service trait, and you're off to the races!
|
|
|
|
### Example
|
|
|
|
For this example, in addition to tarpc, also add two other dependencies to
|
|
your `Cargo.toml`:
|
|
|
|
```toml
|
|
futures = "0.3"
|
|
tokio = "0.2"
|
|
```
|
|
|
|
In the following example, we use an in-process channel for communication between
|
|
client and server. In real code, you will likely communicate over the network.
|
|
For a more real-world example, see [example-service](example-service).
|
|
|
|
First, let's set up the dependencies and service definition.
|
|
|
|
```rust
|
|
use futures::{
|
|
future::{self, Ready},
|
|
prelude::*,
|
|
};
|
|
use tarpc::{
|
|
client, context,
|
|
server::{self, Handler},
|
|
};
|
|
use std::io;
|
|
|
|
// This is the service definition. It looks a lot like a trait definition.
|
|
// It defines one RPC, hello, which takes one arg, name, and returns a String.
|
|
#[tarpc::service]
|
|
trait World {
|
|
/// Returns a greeting for name.
|
|
async fn hello(name: String) -> String;
|
|
}
|
|
```
|
|
|
|
This service definition generates a trait called `World`. Next we need to
|
|
implement it for our Server struct.
|
|
|
|
```rust
|
|
// This is the type that implements the generated World trait. It is the business logic
|
|
// and is used to start the server.
|
|
#[derive(Clone)]
|
|
struct HelloServer;
|
|
|
|
#[tarpc::server]
|
|
impl World for HelloServer {
|
|
async fn hello(self, _: context::Context, name: String) -> String {
|
|
format!("Hello, {}!", name)
|
|
}
|
|
}
|
|
```
|
|
|
|
Lastly let's write our `main` that will start the server. While this example uses an
|
|
[in-process
|
|
channel](https://docs.rs/tarpc/0.18.0/tarpc/transport/channel/struct.UnboundedChannel.html),
|
|
tarpc also ships bincode and JSON
|
|
tokio-net based TCP transports that are generic over all serializable types.
|
|
|
|
```rust
|
|
#[tokio::main]
|
|
async fn main() -> io::Result<()> {
|
|
let (client_transport, server_transport) = tarpc::transport::channel::unbounded();
|
|
|
|
let server = server::new(server::Config::default())
|
|
// incoming() takes a stream of transports such as would be returned by
|
|
// TcpListener::incoming (but a stream instead of an iterator).
|
|
.incoming(stream::once(future::ready(server_transport)))
|
|
.respond_with(HelloServer.serve());
|
|
|
|
tokio::spawn(server);
|
|
|
|
// WorldClient is generated by the macro. It has a constructor `new` that takes a config and
|
|
// any Transport as input
|
|
let mut client = WorldClient::new(client::Config::default(), client_transport).spawn()?;
|
|
|
|
// The client has an RPC method for each RPC defined in the annotated trait. It takes the same
|
|
// args as defined, with the addition of a Context, which is always the first arg. The Context
|
|
// specifies a deadline and trace information which can be helpful in debugging requests.
|
|
let hello = client.hello(context::current(), "Stim".to_string()).await?;
|
|
|
|
println!("{}", hello);
|
|
|
|
Ok(())
|
|
}
|
|
```
|
|
|
|
### Service Documentation
|
|
|
|
Use `cargo doc` as you normally would to see the documentation created for all
|
|
items expanded by a `service!` invocation.
|
|
|
|
License: MIT
|