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 Golang, this is a Rust implementation of the protocol.
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.1"
prost-build = "0.11"
Add a build.rs
file to your project to compile the protos and generate Rust code:
fn main() {
prost_build::Config::new()
.service_generator(twirp_build::service_generator())
.compile_protos(&["./service.proto"], &["./"])
.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 haberdash
#[tokio::main]
pub async fn main() {
let mut router = Router::default();
let server = Arc::new(HaberdasherAPIServer {});
haberdash::add_service(&mut router, server.clone());
let router = Arc::new(router);
let service = make_service_fn(move |_| {
let router = router.clone();
async { Ok::<_, GenericError>(service_fn(move |req| twirp::serve(router.clone(), req))) }
});
let addr = ([127, 0, 0, 1], 3000).into();
let server = Server::bind(&addr).serve(service);
server.await.expect("server error")
}
// Define the server and implement the trait.
struct HaberdasherAPIServer;
#[async_trait]
impl haberdash::HaberdasherAPI for HaberdasherAPIServer {
async fn make_hat(&self, req: MakeHatRequest) -> Result<MakeHatResponse, TwirpErrorResponse> {
todo!()
}
}
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);
}