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.
"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 and Cap'n Proto.
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 cognitive context switching between different languages. Additionally, it works with the community-backed library serde: any serde-serializable type can be used as arguments to tarpc fns.
NB: this example is for master. Are you looking for other versions?
Add to your Cargo.toml
dependencies:
toml
tarpc = { git = "https://github.com/google/tarpc" }
tarpc-plugins = { git = "https://github.com/google/tarpc" }
tarpc has two APIs: sync
for blocking code and future
for asynchronous
code. Here's how to use the sync api.
```rust
extern crate tarpc;
use std::sync::mpsc; use std::thread; use tarpc::sync::{client, server}; use tarpc::sync::client::ClientExt; use tarpc::util::{FirstSocketAddr, Never};
service! { rpc hello(name: String) -> String; }
struct HelloServer;
impl SyncService for HelloServer {
fn hello(&self, name: String) -> Result
fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let mut handle = HelloServer.listen("localhost:0", server::Options::default()) .unwrap(); tx.send(handle.addr()).unwrap(); handle.run(); }); let client = SyncClient::connect(rx.recv().unwrap(), client::Options::default()).unwrap(); println!("{}", client.hello("Mom".to_string()).unwrap()); } ```
The service!
macro expands to a collection of items that form an
rpc service. In the above example, the macro is called within the
hello_service
module. This module will contain SyncClient
, AsyncClient
,
and FutureClient
types, and SyncService
and AsyncService
traits. There is
also a ServiceExt
trait that provides starter fn
s for services, with an
umbrella impl for all services. These generated types make it easy and
ergonomic to write servers without dealing with sockets or serialization
directly. Simply implement one of the generated traits, and you're off to the
races! See the tarpc_examples
package for more examples.
Here's the same service, implemented using futures.
```rust
extern crate futures;
extern crate tarpc; extern crate tokio_core;
use futures::Future; use tarpc::future::{client, server}; use tarpc::future::client::ClientExt; use tarpc::util::{FirstSocketAddr, Never}; use tokio_core::reactor;
service! { rpc hello(name: String) -> String; }
struct HelloServer;
impl FutureService for HelloServer {
type HelloFut = Result
fn hello(&self, name: String) -> Self::HelloFut {
Ok(format!("Hello, {}!", name))
}
}
fn main() { let mut reactor = reactor::Core::new().unwrap(); let (handle, server) = HelloServer.listen("localhost:10000".firstsocketaddr(), &reactor.handle(), server::Options::default()) .unwrap(); reactor.handle().spawn(server); let options = client::Options::default().handle(reactor.handle()); reactor.run(FutureClient::connect(handle.addr(), options) .maperr(tarpc::Error::from) .andthen(|client| client.hello("Mom".to_string())) .map(|resp| println!("{}", resp))) .unwrap(); } ```
By default, tarpc internally uses a [TcpStream
] for communication between your clients and
servers. However, TCP by itself has no encryption. As a result, your communication will be sent in
the clear. If you want your RPC communications to be encrypted, you can choose to use [TLS]. TLS
operates as an encryption layer on top of TCP. When using TLS, your communication will occur over a
[TlsStream<TcpStream>
]. You can add the ability to make TLS clients and servers by adding tarpc
with the tls
feature flag enabled.
When using TLS, some additional information is required. You will need to make [TlsAcceptor
] and
client::tls::Context
structs; client::tls::Context
requires a [TlsConnector
]. The
[TlsAcceptor
] and [TlsConnector
] types are defined in the [native-tls]. tarpc re-exports
external TLS-related types in its native_tls
module (tarpc::native_tls
).
Both TLS streams and TCP streams are supported in the same binary when the tls
feature is enabled.
However, if you are working with both stream types, ensure that you use the TLS clients with TLS
servers and TCP clients with TCP servers.
```rust,no_run
extern crate futures;
extern crate tarpc; extern crate tokio_core;
use futures::Future; use tarpc::future::{client, server}; use tarpc::future::client::ClientExt; use tarpc::tls; use tarpc::util::{FirstSocketAddr, Never}; use tokiocore::reactor; use tarpc::nativetls::{Pkcs12, TlsAcceptor};
service! { rpc hello(name: String) -> String; }
struct HelloServer;
impl FutureService for HelloServer {
type HelloFut = Result
fn hello(&self, name: String) -> Self::HelloFut {
Ok(format!("Hello, {}!", name))
}
}
fn getacceptor() -> TlsAcceptor { let buf = includebytes!("test/identity.p12"); let pkcs12 = Pkcs12::from_der(buf, "password").unwrap(); TlsAcceptor::builder(pkcs12).unwrap().build().unwrap() }
fn main() { let mut reactor = reactor::Core::new().unwrap(); let acceptor = getacceptor(); let (handle, server) = HelloServer.listen("localhost:10000".firstsocketaddr(), &reactor.handle(), server::Options::default().tls(acceptor)).unwrap(); reactor.handle().spawn(server); let options = client::Options::default() .handle(reactor.handle()) .tls(tls::client::Context::new("foobar.com").unwrap()); reactor.run(FutureClient::connect(handle.addr(), options) .maperr(tarpc::Error::from) .andthen(|client| client.hello("Mom".tostring())) .map(|resp| println!("{}", resp))) .unwrap(); } ```
A single service!
invocation generates code for both synchronous and future-based applications.
It's up to the user whether they want to implement the sync API or the futures API. The sync API has
the simplest programming model, at the cost of some overhead - each RPC is handled in its own
thread. The futures API is based on tokio and can run on any tokio-compatible executor. This mean a
service that implements the futures API for a tarpc service can run on a single thread, avoiding
context switches and the memory overhead of having a thread per RPC.
All generated tarpc RPC methods return either tarpc::Result<T, E>
or something like Future<T,
E>
. The error type defaults to tarpc::util::Never
(a wrapper for !
which implements
std::error::Error
) if no error type is explicitly specified in the service!
macro invocation. An
error type can be specified like so:
```rust,ignore use tarpc::util::Message;
service! { rpc hello(name: String) -> String | Message } ```
tarpc::util::Message
is just a wrapper around string that implements std::error::Error
provided
for service implementations that don't require complex error handling. The pipe is used as syntax
for specifying the error type in a way that's agnostic of whether the service implementation is
synchronous or future-based. Note that in the simpler examples in the readme, no pipe is used, and
the macro automatically chooses tarpc::util::Never
as the error type.
The above declaration would produce the following synchronous service trait:
rust,ignore
trait SyncService {
fn hello(&self, name: String) -> Result<String, Message>;
}
and the following future-based trait:
```rust,ignore
trait FutureService {
type HelloFut = IntoFuture
fn hello(&mut self, name: String) -> Self::HelloFut;
} ```
Use cargo doc
as you normally would to see the documentation created for all
items expanded by a service!
invocation.
impl
s serde
's Serialize
and Deserialize
can be used in
rpc signatures.To contribute to tarpc, please see CONTRIBUTING.
tarpc is distributed under the terms of the MIT license.
See LICENSE for details.