Crates.io | twirp-rs |
lib.rs | twirp-rs |
version | 0.13.0-succinct |
source | src |
created_at | 2024-07-05 23:16:17.105519 |
updated_at | 2024-10-03 19:08:57.628554 |
description | An async-compatible library for Twirp RPC in Rust. |
homepage | |
repository | https://github.com/github/twirp-rs |
max_upload_size | |
id | 1293567 |
size | 46,558 |
Twirp is an RPC protocol based on HTTP and Protocol Buffers (proto). The protocol uses HTTP URLs to specify the RPC endpoints, and sends/receives proto messages as HTTP request/response bodies. Services are defined in a .proto file, allowing easy implementation of RPC services with auto-generated clients and servers in different languages.
The canonical implementation is in Go, this is a Rust implementation of the protocol. Rust protocol buffer support is provided by the prost
ecosystem.
Unlike prost-twirp
, the generated traits for serving and accessing RPCs are implemented atop async
functions. Because traits containing async
functions are not directly supported in Rust versions prior to 1.75, this crate uses the async_trait
macro to encapsulate the scaffolding required to make them work.
See the example for a complete example project.
Define services and messages in a .proto
file:
// service.proto
package service.haberdash.v1;
service HaberdasherAPI {
rpc MakeHat(MakeHatRequest) returns (MakeHatResponse);
}
message MakeHatRequest { }
message MakeHatResponse { }
Add the twirp-build
crate as a build dependency in your Cargo.toml
(you'll need prost-build
too):
# Cargo.toml
[build-dependencies]
twirp-build = "0.3"
prost-build = "0.13"
Add a build.rs
file to your project to compile the protos and generate Rust code:
fn main() {
let proto_source_files = ["./service.proto"];
// Tell Cargo to rerun this build script if any of the proto files change
for entry in &proto_source_files {
println!("cargo:rerun-if-changed={}", entry);
}
prost_build::Config::new()
.type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") // enable support for JSON encoding
.service_generator(twirp_build::service_generator())
.compile_protos(&proto_source_files, &["./"])
.expect("error compiling protos");
}
This generates code that you can find in target/build/your-project-*/out/example.service.rs
. In order to use this code, you'll need to implement the trait for the proto defined service and wire up the service handlers to a hyper web server. See the example main.rs
for details.
Include the generated code, create a router, register your service, and then serve those routes in the hyper server:
mod haberdash {
include!(concat!(env!("OUT_DIR"), "/service.haberdash.v1.rs"));
}
use axum::Router;
use haberdash::{MakeHatRequest, MakeHatResponse};
#[tokio::main]
pub async fn main() {
let api_impl = Arc::new(HaberdasherApiServer {});
let twirp_routes = Router::new()
.nest(haberdash::SERVICE_FQN, haberdash::router(api_impl));
let app = Router::new()
.nest("/twirp", twirp_routes)
.fallback(twirp::server::not_found_handler);
let tcp_listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
if let Err(e) = axum::serve(tcp_listener, app).await {
eprintln!("server error: {}", e);
}
}
// Define the server and implement the trait.
struct HaberdasherApiServer;
#[async_trait]
impl haberdash::HaberdasherApi for HaberdasherApiServer {
async fn make_hat(&self, ctx: twirp::Context, req: MakeHatRequest) -> Result<MakeHatResponse, TwirpErrorResponse> {
todo!()
}
}
This code creates an axum::Router
, then hands it off to axum::serve()
to handle networking.
This use of axum::serve
is optional. After building app
, you can instead invoke it from any
hyper
-based server by importing twirp::tower::Service
and doing app.call(request).await
.
On the client side, you also get a generated twirp client (based on the rpc endpoints in your proto). Include the generated code, create a client, and start making rpc calls:
mod haberdash {
include!(concat!(env!("OUT_DIR"), "/service.haberdash.v1.rs"));
}
use haberdash::{HaberdasherApiClient, MakeHatRequest, MakeHatResponse};
#[tokio::main]
pub async fn main() {
let client = Client::from_base_url(Url::parse("http://localhost:3000/twirp/")?)?;
let resp = client.make_hat(MakeHatRequest { inches: 1 }).await;
eprintln!("{:?}", resp);
}